[
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--  Thanks for submitting a pull request! Here are some tips for you:\n1. Please make sure you have read and understood the contributing guidelines: https://github.com/chaosblade-io/chaosblade/blob/master/CONTRIBUTING.md\n2. Please make sure the PR has a corresponding issue.\n-->\n\n### Describe what this PR does / why we need it\n\n\n### Does this pull request fix one issue?\n\n<!--If that, add \"Fixes #xxxx\" below in the next line. For example, Fixes #15. Otherwise, add \"NONE\" -->\n\n### Describe how you did it\n\n\n### Describe how to verify it\n\n\n### Special notes for reviews\n"
  },
  {
    "path": ".github/issue_template.md",
    "content": "<!-- \n\nPlease try to use English to describe your issue, or at least provide a snippet of English translation.\n\n-->\n\n## Issue Description\n\nType: *bug report* or *feature request*\n\n### Describe what happened (or what feature you want)\n\n\n### Describe what you expected to happen\n\n\n### How to reproduce it (as minimally and precisely as possible)\n\n1. \n2. \n3. \n\n### Tell us your environment\n\n\n### Anything else we need to know?\n\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: CI\n\non:\n  push:\n    branches: [ main, master ]\n  pull_request:\n    branches: [ main, master ]\n  workflow_dispatch:\n\njobs:\n  test:\n    name: ${{ matrix.os }} - Test - Go ${{ matrix.go_version }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        go_version:\n          - \"1.25\"\n        os:\n          - ubuntu-latest\n    steps:\n      - name: Set Up Go ${{ matrix.go_version }}\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go_version }}\n        id: go\n\n      - name: Checkout\n        id: checkout\n        uses: actions/checkout@v4\n\n      - name: Cache Go modules\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n      - name: Run code style and import order verification\n        run: make verify\n      - name: Check License Header\n        uses: korandoru/hawkeye@v6\n      - name: Tests\n        id: test\n        run: |\n          make test\n\n  build-matrix:\n    name: Build on ${{ matrix.os }} (${{ matrix.goos }}/${{ matrix.goarch }})\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-latest\n            goos: linux\n            goarch: amd64\n            platform: linux_amd64\n            runner_arch: x64\n          - os: ubuntu-latest\n            goos: linux\n            goarch: arm64\n            platform: linux_arm64\n            runner_arch: arm64\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0  # 获取完整的 Git 历史用于版本信息\n\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '1.25'\n\n      - name: Cache Go modules\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n\n      - name: Install build dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y musl-tools build-essential\n          # Install ARM64 cross-compilation tools if needed\n          if [ \"${{ matrix.goarch }}\" = \"arm64\" ] && [ \"$(uname -m)\" != \"aarch64\" ]; then\n            sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu\n          fi\n\n      - name: Build ${{ matrix.platform }}\n        id: build\n        run: |\n          make ${{ matrix.platform }}\n\n      - name: Verify Build Output\n        id: verify\n        run: |\n          echo \"Verifying build output for ${{ matrix.platform }}...\"\n          echo \"Build architecture: $(uname -m)\"\n          \n          # 检查 chaosblade-operator 二进制文件\n          if [ -f \"build/_output/bin/chaosblade-operator\" ]; then\n            echo \"✅ chaosblade-operator binary found\"\n            file build/_output/bin/chaosblade-operator\n            ls -lh build/_output/bin/chaosblade-operator\n          else\n            echo \"❌ chaosblade-operator binary not found\"\n            exit 1\n          fi\n          \n          # 检查 chaos_fuse 二进制文件\n          BLADE_VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo '0.0.0')\n          BUILD_DIR=\"target/chaosblade-${BLADE_VERSION}\"\n          if [ -f \"${BUILD_DIR}/bin/chaos_fuse\" ]; then\n            echo \"✅ chaos_fuse binary found in ${BUILD_DIR}/bin/\"\n            file \"${BUILD_DIR}/bin/chaos_fuse\"\n            ls -lh \"${BUILD_DIR}/bin/chaos_fuse\"\n          else\n            echo \"❌ chaos_fuse binary not found in ${BUILD_DIR}/bin/\"\n            ls -la \"${BUILD_DIR}/bin/\" || echo \"Build directory not found\"\n            exit 1\n          fi\n          \n          # 检查 YAML 文件\n          if [ -f \"${BUILD_DIR}/yaml/chaosblade-k8s-spec-${BLADE_VERSION}.yaml\" ]; then\n            echo \"✅ YAML specification file found\"\n            ls -lh \"${BUILD_DIR}/yaml/\"\n          else\n            echo \"❌ YAML specification file not found\"\n            ls -la \"${BUILD_DIR}/yaml/\" || echo \"YAML directory not found\"\n            exit 1\n          fi\n          \n          echo \"=== Build Summary for ${{ matrix.platform }} ===\"\n          echo \"All required files built successfully!\"\n\n      - name: Upload build artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.platform }}-binaries\n          path: |\n            build/_output/bin/chaosblade-operator\n            target/chaosblade-*/bin/\n            target/chaosblade-*/yaml/\n          retention-days: 7\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nname: Release\n\non:\n  push:\n    tags:\n      - 'v*'\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'Version number (e.g: 1.8.0)'\n        required: true\n        default: '1.8.0'\n      draft:\n        description: 'Create as draft release'\n        required: false\n        type: boolean\n        default: false\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: chaosblade-io/chaosblade-operator\n  GO_VERSION: '1.25'\n\npermissions:\n  contents: write\n  packages: write\n\njobs:\n  build-and-push-images:\n    name: Build and Push Images\n    runs-on: ${{ matrix.runs-on }}\n    strategy:\n      matrix:\n        include:\n          - os: linux\n            arch: amd64\n            runs-on: ubuntu-latest\n            dockerfile: build/image/amd/Dockerfile\n            image_suffix: \"\"\n            image_tag: \"ghcr.io/chaosblade-io/chaosblade-operator\"\n          - os: linux\n            arch: arm64\n            runs-on: ubuntu-24.04-arm\n            dockerfile: build/image/arm/Dockerfile\n            image_suffix: \"-arm64\"\n            image_tag: \"ghcr.io/chaosblade-io/chaosblade-operator-arm64\"\n    \n    outputs:\n      version: ${{ steps.version.outputs.version }}\n    \n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Extract version from tag\n        id: version\n        run: |\n          if [ \"${{ github.event_name }}\" = \"workflow_dispatch\" ]; then\n            VERSION=\"${{ github.event.inputs.version }}\"\n          else\n            VERSION=\"${GITHUB_REF#refs/tags/}\"\n            VERSION=\"${VERSION#v}\"\n          fi\n          echo \"version=${VERSION}\" >> $GITHUB_OUTPUT\n          echo \"tag=v${VERSION}\" >> $GITHUB_OUTPUT\n\n      - name: Set up Go\n        uses: actions/setup-go@v4\n        with:\n          go-version: ${{ env.GO_VERSION }}\n          cache: true\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Log in to Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ secrets.GHCR_USER }}\n          password: ${{ secrets.GHCR_PASSWORD }}\n\n      - name: Build and Push ${{ matrix.arch }} image\n        env:\n          BLADE_VERSION: ${{ steps.version.outputs.version }}\n        run: |\n          echo \"Building ${{ matrix.arch }} image...\"\n          echo \"BLADE_VERSION: $BLADE_VERSION\"\n          echo \"Image tag: ${{ matrix.image_tag }}:${{ steps.version.outputs.version }}\"\n          make build_linux_${{ matrix.arch }}_image\n          \n          echo \"Built images:\"\n          podman images | grep chaosblade-operator\n          \n          echo \"Pushing ${{ matrix.arch }} image...\"\n          podman push ${{ matrix.image_tag }}:${{ steps.version.outputs.version }}\n          \n          # Only push latest tag if version doesn't contain 'dev'\n          if [[ \"${{ steps.version.outputs.version }}\" != *\"dev\"* ]]; then\n            echo \"Pushing latest tag...\"\n            podman tag ${{ matrix.image_tag }}:${{ steps.version.outputs.version }} ${{ matrix.image_tag }}:latest\n            podman push ${{ matrix.image_tag }}:latest\n          else\n            echo \"Skipping latest tag push for dev version: ${{ steps.version.outputs.version }}\"\n          fi\n\n  package-and-upload-helm:\n    name: Package and Upload Helm Charts\n    runs-on: ubuntu-latest\n    needs: build-and-push-images\n    \n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Get version from previous job\n        id: version\n        run: |\n          echo \"version=${{ needs.build-and-push-images.outputs.version }}\" >> $GITHUB_OUTPUT\n\n      - name: Install Helm\n        uses: azure/setup-helm@v3\n        with:\n          version: v3.9.3\n\n      - name: Package Helm Charts\n        env:\n          BLADE_VERSION: ${{ steps.version.outputs.version }}\n        run: |\n          echo \"Packaging Helm charts for version ${BLADE_VERSION}...\"\n          # Use Makefile tasks to build and package Helm charts\n          make build_linux_amd64_helm\n          make build_linux_arm64_helm\n          \n          echo \"Generated Helm packages:\"\n          ls -la target/*.tgz\n\n      - name: Setup OSSUTIL environment\n        uses: yizhoumo/setup-ossutil@v1.1.3\n        env:\n          BINARY_TAG: ${{ steps.version.outputs.version }}\n        with:\n          endpoint: ${{ secrets.OSS_ENDPOINT }}\n          access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }}\n          access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}\n          ossutil-version: '1.7.14'\n\n      - name: Upload Helm packages to OSS\n        env:\n          BINARY_TAG: ${{ steps.version.outputs.version }}\n        run: |\n          echo \"Uploading Helm packages to OSS...\"\n          ossutil cp -f target/chaosblade-operator-amd64-${BINARY_TAG}.tgz oss://chaosblade/agent/github/${BINARY_TAG}/chaosblade-operator-amd64-${BINARY_TAG}.tgz\n          ossutil cp -f target/chaosblade-operator-arm64-${BINARY_TAG}.tgz oss://chaosblade/agent/github/${BINARY_TAG}/chaosblade-operator-arm64-${BINARY_TAG}.tgz\n          echo \"Helm packages uploaded successfully\"\n\n      - name: Upload Helm packages as artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: helm-packages\n          path: |\n            target/chaosblade-operator-amd64-${{ steps.version.outputs.version }}.tgz\n            target/chaosblade-operator-arm64-${{ steps.version.outputs.version }}.tgz\n          retention-days: 30\n\n  create-release:\n    name: Create GitHub Release\n    needs: [build-and-push-images, package-and-upload-helm]\n    runs-on: ubuntu-latest\n    \n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Get version from previous job\n        id: version\n        run: |\n          echo \"version=${{ needs.build-and-push-images.outputs.version }}\" >> $GITHUB_OUTPUT\n\n      - name: Download Helm packages\n        uses: actions/download-artifact@v4\n        with:\n          name: helm-packages\n\n      - name: List downloaded files for debugging\n        run: |\n          echo \"Current directory contents:\"\n          ls -la\n          echo \"Looking for .tgz files:\"\n          find . -name \"*.tgz\" -type f\n\n      - name: Create GitHub Release\n        uses: softprops/action-gh-release@v1\n        with:\n          tag_name: v${{ steps.version.outputs.version }}\n          name: v${{ steps.version.outputs.version }}\n          generate_release_notes: true\n          body: |\n            \n            ### Helm Package Downloads\n            \n            - [chaosblade-operator-amd64 (AMD64)](https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/chaosblade-operator-amd64-${{ steps.version.outputs.version }}.tgz)\n            - [chaosblade-operator-arm64 (ARM64)](https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/chaosblade-operator-arm64-${{ steps.version.outputs.version }}.tgz)\n            \n            ### Docker Images\n            \n            ```bash\n            # AMD64\n            docker pull ghcr.io/chaosblade-io/chaosblade-operator:v${{ steps.version.outputs.version }}\n            \n            # ARM64\n            docker pull ghcr.io/chaosblade-io/chaosblade-operator-arm64:v${{ steps.version.outputs.version }}\n            ```\n            \n            ### Helm Installation\n            \n            ```bash\n            # AMD64\n            helm install chaosblade-operator ./chaosblade-operator-amd64-${{ steps.version.outputs.version }}.tgz\n            \n            # ARM64\n            helm install chaosblade-operator ./chaosblade-operator-arm64-${{ steps.version.outputs.version }}.tgz\n            ```\n            \n            ### OSS Download Links\n            \n            - [chaosblade-operator-amd64-${{ steps.version.outputs.version }}.tgz](https://chaosblade.oss-cn-hangzhou.aliyuncs.com/agent/github/${{ steps.version.outputs.version }}/chaosblade-operator-amd64-${{ steps.version.outputs.version }}.tgz)\n            - [chaosblade-operator-arm64-${{ steps.version.outputs.version }}.tgz](https://chaosblade.oss-cn-hangzhou.aliyuncs.com/agent/github/${{ steps.version.outputs.version }}/chaosblade-operator-arm64-${{ steps.version.outputs.version }}.tgz)\n            \n          files: |\n            chaosblade-operator-amd64-${{ steps.version.outputs.version }}.tgz\n            chaosblade-operator-arm64-${{ steps.version.outputs.version }}.tgz\n          draft: ${{ github.event.inputs.draft == 'true' }}\n          prerelease: false\n"
  },
  {
    "path": ".gitignore",
    "content": "# Temporary Build Files\nbuild/_output\nbuild/_test\ntarget\n# Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode\n### Emacs ###\n# -*- mode: gitignore; -*-\n*~\n\\#*\\#\n/.emacs.desktop\n/.emacs.desktop.lock\n*.elc\nauto-save-list\ntramp\n.\\#*\n# Org-mode\n.org-id-locations\n*_archive\n# flymake-mode\n*_flymake.*\n# eshell files\n/eshell/history\n/eshell/lastdir\n# elpa packages\n/elpa/\n# reftex files\n*.rel\n# AUCTeX auto folder\n/auto/\n# cask packages\n.cask/\ndist/\n# Flycheck\nflycheck_*.el\n# server auth directory\n/server/\n# projectiles files\n.projectile\nprojectile-bookmarks.eld\ndeploy/helm/chaosblade-operator-*.tgz\n# directory configuration\n.dir-locals.el\n# saveplace\nplaces\n# url cache\nurl/cache/\n# cedet\nede-projects.el\n# smex\nsmex-items\n# company-statistics\ncompany-statistics-cache.el\n# anaconda-mode\nanaconda-mode/\n### Go ###\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n.DS_Store\n# Test binary, build with 'go test -c'\n*.test\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\nvendor\ncoverage.txt\n### Vim ###\n# swap\n.sw[a-p]\n.*.sw[a-p]\n# session\nSession.vim\n# temporary\n.netrwhist\n# auto-generated tag files\ntags\n### VisualStudioCode ###\n.vscode/*\n.history\n# End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode\n\n### Goland ###\n.idea\n\n### vendor ###\nvendor\n\n### project ###\nbuild/cache\n"
  },
  {
    "path": "BUILD.md",
    "content": "# ChaosBlade Operator Build Guide\n\nThis document describes how to build the ChaosBlade Operator project. The project supports multi-platform builds, including Linux AMD64 and ARM64 architectures.\n\n## Table of Contents\n\n- [Prerequisites](#prerequisites)\n- [Environment Variables](#environment-variables)\n- [Build Targets](#build-targets)\n- [Container Image Building](#container-image-building)\n- [Testing](#testing)\n- [Cleanup](#cleanup)\n- [Troubleshooting](#troubleshooting)\n\n## Prerequisites\n\n### Basic Requirements\n\n- **Go 1.21+**: For compiling Go code\n- **Git**: For retrieving version information\n- **Make**: For executing build scripts\n\n### Container Runtime (Optional)\n\nThe project automatically detects available container runtimes:\n- **Docker**: Recommended\n- **Podman**: As an alternative to Docker\n\n### Cross-compilation Toolchain (Optional)\n\nFor Linux builds of the `chaos_fuse` component, one of the following tools is required:\n\n#### Linux Systems\n```bash\n# Ubuntu/Debian - Basic build tools\nsudo apt-get install musl-tools build-essential\n\n# For ARM64 cross-compilation (when building on x86_64 for ARM64)\nsudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu\n\n# For native ARM64 compilation (when building on ARM64 for ARM64)\n# The standard gcc from build-essential is sufficient\n```\n\n#### macOS Systems\n```bash\n# Install musl cross-compiler using Homebrew\nbrew install FiloSottile/musl-cross/musl-cross\n```\n\n### Platform Compatibility\n\n- **Build Platforms**: macOS, Linux\n- **Target Platforms**: Linux AMD64, Linux ARM64\n- **Container Platforms**: Multi-architecture container builds supported\n\n## Environment Variables\n\n### Version-related\n\n| Variable | Description | Default | Example |\n|----------|-------------|---------|----------|\n| `BLADE_VERSION` | Version number | Git tag (e.g., `1.7.4`) | `1.8.0` |\n| `BLADE_VENDOR` | Vendor identifier | `community` | `enterprise` |\n\n### Build-related\n\n| Variable | Description | Default | Example |\n|----------|-------------|---------|----------|\n| `CONTAINER_RUNTIME` | Container runtime | Auto-detected | `docker`, `podman` |\n| `JVM_SPEC_PATH` | JVM specification file path | None | `/path/to/jvm/spec` |\n\n### Example Configuration\n```bash\nexport BLADE_VERSION=1.8.0\nexport BLADE_VENDOR=community\nexport CONTAINER_RUNTIME=docker\n```\n\n## Build Targets\n\n### View Help Information\n\n```bash\nmake help\n```\n\nDisplays all available build targets and environment variable descriptions.\n\n### View Version Information\n\n```bash\nmake show-version\n```\n\nDisplays current build version information, including:\n- Version number\n- Vendor\n- Git commit hash\n- Git branch\n- Build time\n- Go version\n- Target platform\n\n### Complete Platform Builds\n\n#### Linux AMD64 Platform\n```bash\nmake linux_amd64\n```\n\nBuilds:\n- `chaosblade-operator` (Linux AMD64)\n- `chaos_fuse` (Linux AMD64)\n- YAML specification files\n\n#### Linux ARM64 Platform\n```bash\nmake linux_arm64\n```\n\nBuilds:\n- `chaosblade-operator` (Linux ARM64)\n- `chaos_fuse` (Linux ARM64)\n- YAML specification files\n\n### Individual Component Builds\n\n#### Build Operator\n```bash\nmake operator GOOS=linux GOARCH=amd64\n```\n\n#### Build chaos_fuse\n```bash\nmake chaos_fuse GOOS=linux GOARCH=amd64\n```\n\n#### Generate YAML Specifications\n```bash\nmake yaml\n# Or generate YAML only\nmake only_yaml\n```\n\n#### Build Local Binary\n```bash\nmake build_binary\n```\n\n## Container Image Building\n\n### Linux AMD64 Image\n```bash\nmake build_linux_amd64_image\n```\n\nBuilds and tags as: `ghcr.io/chaosblade-io/chaosblade-operator:${BLADE_VERSION}`\n\n### Linux ARM64 Image\n```bash\nmake build_linux_arm64_image\n```\n\nBuilds and tags as: `ghcr.io/chaosblade-io/chaosblade-operator-arm64:${BLADE_VERSION}`\n\n### Push Images\n```bash\nmake push_image\n```\n\nPushes the built images to GitHub Container Registry.\n\n## Testing\n\nRun the project test suite:\n\n```bash\nmake test\n```\n\nThis command will:\n- Run all test cases\n- Enable race detection\n- Generate code coverage report (`coverage.txt`)\n\n## Cleanup\n\nClean all build artifacts:\n\n```bash\nmake clean\n```\n\nCleans:\n- Go build cache\n- `target/` directory\n- Build image directories\n\n## Build Output\n\n### Directory Structure\n\n```\ntarget/chaosblade-${BLADE_VERSION}/\n├── bin/\n│   └── chaos_fuse             # File system hook tool\n└── yaml/\n    └── chaosblade-k8s-spec-${BLADE_VERSION}.yaml  # Kubernetes specification file\n```\n\n### Temporary Build Files\n\n```\nbuild/_output/bin/\n├── chaosblade-operator        # Temporarily built operator file\n└── spec                       # Specification generator tool\n```\n\n### File Description\n* `chaos_fuse` and `chaosblade-k8s-spec-${BLADE_VERSION}.yaml` need to be packaged into chaosblade for use (can be compiled and packaged directly in the chaosblade project);\n* `chaosblade-operator` needs to be packaged into the chaosblade-operator image for use (can be compiled directly using build_linux_xxx_image tasks);\n\n## Troubleshooting\n\n### chaos_fuse Build Failure\n\n**Issue**: Missing cross-compilation toolchain\n\n**Solutions**:\n1. **Preferred approach**: Install appropriate cross-compiler\n   ```bash\n   # macOS\n   brew install FiloSottile/musl-cross/musl-cross\n   \n   # Linux\n   sudo apt-get install musl-tools\n   ```\n\n2. **Alternative approach**: Use container build\n   ```bash\n   # Ensure Docker/Podman is running\n   docker info  # or podman info\n   ```\n\n3. **Manually specify container runtime**:\n   ```bash\n   CONTAINER_RUNTIME=podman make chaos_fuse GOOS=linux GOARCH=amd64\n   ```\n\n### Container Build Failure\n\n**Issue**: Container runtime unavailable\n\n**Solutions**:\n1. Check Docker/Podman status\n   ```bash\n   docker info\n   # or\n   podman info\n   ```\n\n2. Start Docker service\n   ```bash\n   # macOS/Linux\n   sudo systemctl start docker\n   # or use Docker Desktop\n   ```\n\n### Version Information Retrieval Failure\n\n**Issue**: Git repository information unavailable\n\n**Solutions**:\n1. Ensure running build within Git repository\n2. Manually specify version:\n   ```bash\n   BLADE_VERSION=1.8.0 make linux_amd64\n   ```\n\n### Permission Issues\n\n**Issue**: File permission errors\n\n**Solutions**:\n1. Check directory permissions\n2. Run build with appropriate user permissions\n3. For container builds, ensure SELinux compatibility (`:Z` flag)\n\n## Advanced Usage\n\n### Custom Build Flags\n\n```bash\n# Add custom ldflags\nGO_FLAGS=\"-ldflags '-X main.customFlag=value'\" make operator\n```\n\n### Parallel Builds\n\n```bash\n# Use parallel builds for acceleration\nmake -j4 linux_amd64\n```\n\n### Debug Builds\n\n```bash\n# Enable verbose output\nmake V=1 linux_amd64\n```\n\n## Contributing Guidelines\n\nWhen building new features or fixes:\n\n1. Ensure all build targets work properly\n2. Run complete test suite: `make test`\n3. Verify cross-platform builds: `make linux_amd64 linux_arm64`\n4. Check code coverage reports\n\n## Related Documentation\n\n- [Contributing Guide](CONTRIBUTING.md)\n- [Changelog](CHANGELOG.md)\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# ChaosBlade Operator 变更日志\n\n本文档记录了 ChaosBlade Operator 的所有重要变更。\n\n格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)，\n并且本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。\n\n## [未发布]\n\n### 新增\n- 支持 Git Tag 自动化构建流程\n- 增强版本信息展示，包含构建时间、Git提交等\n- 新增版本命令支持 (`--version` 标志)\n\n### 变更\n- 优化 Makefile 构建流程\n- 改进版本信息注入机制\n\n### 修复\n- 修复版本信息显示问题\n\n## [1.7.4] - 2024-01-01\n\n### 新增\n- 初始版本发布\n- 支持基本的混沌工程功能\n- 支持 Kubernetes 环境\n\n### 变更\n- 基础架构搭建\n- 核心功能实现\n\n---\n\n## 变更类型说明\n\n- **新增**: 新功能\n- **变更**: 现有功能的变更\n- **弃用**: 即将移除的功能\n- **移除**: 已移除的功能\n- **修复**: Bug修复\n- **安全**: 安全相关修复\n\n## 如何贡献\n\n1. 在 `## [未发布]` 部分添加你的变更\n2. 使用上述变更类型标签\n3. 提供清晰的变更描述\n4. 如果是重大变更，请提供迁移指南\n\n## 版本发布流程\n\n1. 创建 Git 标签: `git tag v1.8.0`\n2. 推送标签: `git push origin v1.8.0`\n3. GitHub Actions 自动触发构建\n4. 自动创建 Release 和上传构建产物\n5. 更新 CHANGELOG.md 中的版本信息\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to chaosblade\n\nWelcome to ChaosBlade world, here is a list of contributing guide for you. If you find something incorrect or missing\n content in the page, please submit an issue or PR to fix it.\n\n\n## What can you do \nEvery action to make the project better is encouraged. On GitHub, every improvement for the project could be via a PR \n(short for pull request).\n\n* If you find a typo, try to fix it!\n* If you find a bug, try to fix it!\n* If you find some redundant codes, try to remove them!\n* If you find some test cases missing, try to add them!\n* If you could enhance a feature, please **DO NOT** hesitate!\n* If you find code implicit, try to add comments to make it clear!\n* If you find code ugly, try to refactor that!\n* If you can help to improve documents, it could not be better!\n* If you find document incorrect, just do it and fix that!\n* ...\n\nActually, it is impossible to list them completely. Just remember one principle:\n\n**WE ARE LOOKING FORWARD TO ANY PR FROM YOU.**\n\n\n## Contributing\n### Preparation\nBefore you contribute, you need to register a Github ID. Prepare the following environment:\n* go\n* git\n\n### Workflow\nWe use the `master` branch as the development branch, which indicates that this is an unstable branch.\n\nHere is the workflow for contributors:\n\n1. Fork to your own\n2. Clone fork to the local repository\n3. Create a new branch and work on it\n4. Keep your branch in sync\n5. Commit your changes (make sure your commit message concise)\n6. Push your commits to your forked repository\n7. Create a pull request\n\nPlease follow [the pull request template](./.github/PULL_REQUEST_TEMPLATE.md).\nPlease make sure the PR has a corresponding issue.\n\nAfter creating a PR, one or more reviewers will be assigned to the pull request.\nThe reviewers will review the code.\n\nBefore merging a PR, squash any fix review feedback, typo, merged, and rebased sorts of commits.\nThe final commit message should be clear and concise.\n\n### Compile\nGo to the project root directory which you cloned and execute compile:\n```bash\nmake\n```\n\nIf you compile the Linux package on the Mac operating system, you can do:\n```bash\nmake build_linux\n```\n\nIf you compile the chaosblade image, you can do:\n```bash\nmake build_image\n```\nclean compilation:\n```bash\nmake clean\n```\n\n### Code Style\nSee details of [CODE STYLE](./docs/code_styles.md)\n\n### Commit Rules\n#### Commit Message\n\nCommit message could help reviewers better understand what is the purpose of submitted PR. It could help accelerate the code review procedure as well. We encourage contributors to use **EXPLICIT** commit message rather than an ambiguous message. In general, we advocate the following commit message type:\n\n* feat: A new feature\n* fix: A bug fix\n* docs: Documentation only changes\n* style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)\n* refactor: A code change that neither fixes a bug or adds a feature\n* perf: A code change that improves performance\n* test: Adding missing or correcting existing tests\n* chore: Changes to the build process or auxiliary tools and libraries such as documentation generation\n\nOn the other side, we discourage contributors from committing message like the following ways:\n\n* ~~fix bug~~\n* ~~update~~\n* ~~add doc~~\n\nIf you get lost, please see [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) for a start.\n\n#### Commit Content\n\nCommit content represents all content changes included in one commit. We had better include things in one single commit which could support the reviewer's complete review without any other commits' help. In other word, contents in one single commit can pass the CI to avoid code mess. In brief, there are two minor rules for us to keep in mind:\n\n* avoid very large change in a commit;\n* complete and reviewable for each commit.\n\nNo matter commit message or commit content, we do take more emphasis on code review.\n\n\n### Pull Request\nWe use [GitHub Issues](https://github.com/chaosblade-io/chaosblade-exec-jvm/issues) and [Pull Requests](https://github.com/chaosblade-io/chaosblade-exec-jvm/pulls) for trackers.\n\nIf you find a typo in document, find a bug in code, or want new features, or want to give suggestions,\nyou can [open an issue on GitHub](https://github.com/chaosblade-io/chaosblade-exec-jvm/issues/new) to report it.\nPlease follow the guideline message in the issue template.\n\nIf you want to contribute, please follow the [contribution workflow](#Workflow) and create a new pull request.\nIf your PR contains large changes, e.g. component refactor or new components, please write detailed documents\nabout its design and usage.\n\nNote that a single PR should not be too large. If heavy changes are required, it's better to separate the changes\nto a few individual PRs.\n\n\n### Code Review\nAll code should be well reviewed by one or more committers. Some principles:\n\n- Readability: Important code should be well-documented. Comply with our code style.\n- Elegance: New functions, classes or components should be well designed.\n- Testability: Important code should be well-tested (high unit test coverage).\n\n## Others\n### Code of Conduct\n*\"In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make \nparticipation in our project and our community a harassment-free experience for everyone, regardless of age, body \nsize, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, \nsocio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation...\"*\n\nSee details of [CONTRIBUTOR COVENANT CODE OF CONDUCT](https://github.com/chaosblade-io/chaosblade-exec-jvm/blob/master/CODE_OF_CONDUCT.md)\n\n### Sign your work\nThe sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch.\nThe rules are pretty simple: if you can certify the below (from [developercertificate.org](http://developercertificate.org/)):\n\n```\nDeveloper Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n660 York Street, Suite 102,\nSan Francisco, CA 94110 USA\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\nDeveloper's Certificate of Origin 1.1\n\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I\n    have the right to submit it under the open source license\n    indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best\n    of my knowledge, is covered under an appropriate open source\n    license and I have the right under that license to submit that\n    work with modifications, whether created in whole or in part\n    by me, under the same open source license (unless I am\n    permitted to submit under a different license), as indicated\n    in the file; or\n\n(c) The contribution was provided directly to me by some other\n    person who certified (a), (b) or (c) and I have not modified\n    it.\n\n(d) I understand and agree that this project and the contribution\n    are public and that a record of the contribution (including all\n    personal information I submit with it, including my sign-off) is\n    maintained indefinitely and may be redistributed consistent with\n    this project or the open source license(s) involved.\n```\n\nThen you just add a line to every git commit message:\n\n```\nSigned-off-by: Joe Smith <joe.smith@email.com>\n```\n\nUse your real name (sorry, no pseudonyms or anonymous contributions.)\n\nIf you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 1999-2019 Alibaba Group Holding Ltd.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n.PHONY: show-version linux_amd64 linux_arm64 pre_build operator chaos_fuse yaml build_binary build_linux_amd64_image build_linux_arm64_image build_linux_amd64_helm build_linux_arm64_helm build_linux_amd64_release build_linux_arm64_release push_image test clean help\n\n# Default target - show help when no target is specified\n.DEFAULT_GOAL := help\n\n# Container runtime configuration - compatible with Docker and Podman\n# Auto-detect available container runtime\nifeq ($(CONTAINER_RUNTIME),)\n    ifeq ($(shell command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1 && echo \"podman\"),podman)\n        CONTAINER_RUNTIME := podman\n    else ifeq ($(shell command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1 && echo \"docker\"),docker)\n        CONTAINER_RUNTIME := docker\n    else\n        CONTAINER_RUNTIME := docker\n    endif\nendif\n\n# Get current platform information\nCURRENT_OS := $(shell uname -s | tr '[:upper:]' '[:lower:]')\nCURRENT_ARCH := $(shell uname -m)\nifeq ($(CURRENT_ARCH),x86_64)\nCURRENT_ARCH := amd64\nelse ifeq ($(CURRENT_ARCH),aarch64)\nCURRENT_ARCH := arm64\nendif\n\nGOOS := $(shell go env GOOS)\nGOARCH := $(shell go env GOARCH)\n\nUNAME := $(shell uname)\n\n# Version information retrieval\nifeq ($(BLADE_VERSION), )\n\tBLADE_VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo \"0.0.0\")\nendif\nifeq ($(BLADE_VENDOR), )\n\tBLADE_VENDOR=community\nendif\n\n# Dynamically get Git information\nGIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo \"unknown\")\nGIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"unknown\")\nBUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')\nGO_VERSION := $(shell go version | awk '{print $$3}')\nPLATFORM := $(shell echo \"$(GOOS)/$(GOARCH)\")\n\nBUILD_TARGET=target\nBUILD_TARGET_DIR_NAME=chaosblade-$(BLADE_VERSION)\nBUILD_TARGET_PKG_DIR=$(BUILD_TARGET)/chaosblade-$(BLADE_VERSION)\nBUILD_TARGET_BIN=$(BUILD_TARGET_PKG_DIR)/bin\nBUILD_TARGET_YAML=$(BUILD_TARGET_PKG_DIR)/yaml\nBUILD_IMAGE_PATH=build/image/blade\n\nOS_YAML_FILE_NAME=chaosblade-k8s-spec-$(BLADE_VERSION).yaml\nOS_YAML_FILE_PATH=$(BUILD_TARGET_YAML)/$(OS_YAML_FILE_NAME)\n\nVERSION_PKG=github.com/chaosblade-io/chaosblade-operator/version\n\n# Complete version information ldflags\nVERSION_LDFLAGS=-X=$(VERSION_PKG).Version=$(BLADE_VERSION) \\\n\t-X=$(VERSION_PKG).Product=$(BLADE_VENDOR) \\\n\t-X=$(VERSION_PKG).BuildTime=$(BUILD_TIME) \\\n\t-X=$(VERSION_PKG).GitCommit=$(GIT_COMMIT) \\\n\t-X=$(VERSION_PKG).GitBranch=$(GIT_BRANCH) \\\n\t-X=$(VERSION_PKG).GoVersion=$(GO_VERSION) \\\n\t-X=$(VERSION_PKG).Platform=$(PLATFORM) \\\n\t-X=$(VERSION_PKG).CombinedVersion=$(BLADE_VERSION),$(BLADE_VENDOR)\n\nGO_FLAGS=-ldflags \"$(VERSION_LDFLAGS)\"\nGO_FLAGS_WITH_STATIC=-ldflags=\"-linkmode external -extldflags -static $(VERSION_LDFLAGS)\"\n\n# Cross-compilation CC detection for chaos_fuse\ndefine detect_cc\n$(strip $(if $(and $(filter amd64,$(GOARCH)),$(shell command -v musl-gcc 2>/dev/null)),musl-gcc,\\\n$(if $(and $(filter amd64,$(GOARCH)),$(wildcard /usr/local/musl/bin/musl-gcc)),/usr/local/musl/bin/musl-gcc,\\\n$(if $(and $(filter amd64,$(GOARCH)),$(shell command -v x86_64-linux-musl-gcc 2>/dev/null)),x86_64-linux-musl-gcc,\\\n$(if $(and $(filter arm64,$(GOARCH)),$(shell command -v aarch64-linux-musl-gcc 2>/dev/null)),aarch64-linux-musl-gcc,\\\n$(if $(and $(filter amd64,$(GOARCH)),$(shell command -v gcc 2>/dev/null)),gcc,\\\n$(if $(and $(filter arm64,$(GOARCH)),$(shell command -v aarch64-linux-gnu-gcc 2>/dev/null)),aarch64-linux-gnu-gcc,\\\n$(if $(and $(filter arm64,$(GOARCH)),$(shell command -v gcc 2>/dev/null)),gcc,\\\ncontainer))))))))\nendef\n\nCC_FOR_CHAOS_FUSE := $(call detect_cc)\n\n# Display version information\nshow-version:\n\t@echo \"=== Build Version Information ===\"\n\t@echo \"Version: $(BLADE_VERSION)\"\n\t@echo \"Vendor: $(BLADE_VENDOR)\"\n\t@echo \"Git Commit: $(GIT_COMMIT)\"\n\t@echo \"Git Branch: $(GIT_BRANCH)\"\n\t@echo \"Build Time: $(BUILD_TIME)\"\n\t@echo \"Go Version: $(GO_VERSION)\"\n\t@echo \"Platform: $(PLATFORM)\"\n\t@echo \"==================\"\n\n# Linux AMD64 platform build\nlinux_amd64: show-version pre_build\n\t@echo \"Building Linux AMD64 platform components...\"\n\t$(MAKE) operator GOOS=linux GOARCH=amd64\n\t@echo \"chaosblade-operator build completed\"\n\t$(MAKE) chaos_fuse GOOS=linux GOARCH=amd64\n\t@echo \"chaos_fuse build completed\"\n\t$(MAKE) yaml GOOS=linux GOARCH=arm64\n\t@echo \"YAML specification file generation completed\"\n\t@echo \"Linux AMD64 platform build completed\"\n\n# Linux ARM64 platform build\nlinux_arm64: show-version pre_build\n\t@echo \"Building Linux ARM64 platform components...\"\n\t$(MAKE) operator GOOS=linux GOARCH=arm64\n\t@echo \"chaosblade-operator build completed\"\n\t$(MAKE) chaos_fuse GOOS=linux GOARCH=arm64\n\t@echo \"chaos_fuse build completed\"\n\t$(MAKE) yaml GOOS=linux GOARCH=arm64\n\t@echo \"YAML specification file generation completed\"\n\t@echo \"Linux ARM64 platform build completed\"\n\npre_build:\n\t@mkdir -p $(BUILD_TARGET_BIN) $(BUILD_TARGET_YAML) build/_output/bin\n\noperator:\n\t@echo \"Building chaosblade-operator for $(GOOS)/$(GOARCH)...\"\n\t@CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build $(GO_FLAGS) -o build/_output/bin/chaosblade-operator cmd/manager/main.go\n\nchaos_fuse: ## Build chaos_fuse for Linux (supports cross-compilation from macOS)\nifeq ($(GOOS),linux)\n\t@echo \"Detected CC for chaos_fuse: $(CC_FOR_CHAOS_FUSE)\"\n\t@if [ \"$(CC_FOR_CHAOS_FUSE)\" != \"container\" ]; then \\\n\t\techo \"Building chaos_fuse for Linux $(GOARCH) using $(CC_FOR_CHAOS_FUSE)...\"; \\\n\t\tCC=$(CC_FOR_CHAOS_FUSE) CGO_ENABLED=1 go build $(GO_FLAGS) -o $(BUILD_TARGET_BIN)/chaos_fuse cmd/hookfs/main.go; \\\n\telif command -v $(CONTAINER_RUNTIME) >/dev/null 2>&1 && $(CONTAINER_RUNTIME) info >/dev/null 2>&1; then \\\n\t\techo \"Building chaos_fuse for Linux $(GOARCH) using $(CONTAINER_RUNTIME)...\"; \\\n\t\tif [ \"$(GOARCH)\" = \"amd64\" ]; then \\\n\t\t\t$(CONTAINER_RUNTIME) run --rm -v $(PWD):/src:Z -w /src --platform linux/amd64 golang:1.21-alpine sh -c \"apk add --no-cache musl-dev gcc && cd /src && CGO_ENABLED=1 go build $(GO_FLAGS) -o /src/$(BUILD_TARGET_BIN)/chaos_fuse cmd/hookfs/main.go\" >/dev/null 2>&1; \\\n\t\telif [ \"$(GOARCH)\" = \"arm64\" ]; then \\\n\t\t\t$(CONTAINER_RUNTIME) run --rm -v $(PWD):/src:Z -w /src golang:1.21-alpine sh -c \"apk add --no-cache musl-dev gcc && cd /src && CGO_ENABLED=1 GOARCH=arm64 GOOS=linux go build $(GO_FLAGS) -o /src/$(BUILD_TARGET_BIN)/chaos_fuse cmd/hookfs/main.go\" >/dev/null 2>&1; \\\n\t\telse \\\n\t\t\techo \"Unsupported architecture $(GOARCH) for chaos_fuse\"; \\\n\t\tfi; \\\n\telse \\\n\t\techo \"Warning: No suitable cross-compilation toolchain found for chaos_fuse\"; \\\n\t\techo \"Available options:\"; \\\n\t\techo \"  1. Install musl-tools: apt-get install musl-tools (Ubuntu/Debian)\"; \\\n\t\techo \"  2. Install musl-gcc: brew install FiloSottile/musl-cross/musl-cross (macOS)\"; \\\n\t\techo \"  3. Install specific cross-compilers for ARM64: apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu\"; \\\n\t\techo \"  4. Use Docker/Podman with proper platform emulation\"; \\\n\tfi\nelse\n\t@echo \"Skipping chaos_fuse build on $(GOOS) for target - Linux only\"\nendif\n\n\nyaml: build/spec.go\n\t@echo \"Building spec generator...\"\n\t@GOOS=$(CURRENT_OS) GOARCH=$(CURRENT_ARCH) go build $(GO_FLAGS) -o build/_output/bin/spec $<\n\t@echo \"Generating YAML specifications...\"\n\t@GOOS=$(CURRENT_OS) GOARCH=$(CURRENT_ARCH) build/_output/bin/spec $(OS_YAML_FILE_PATH) $(if $(JVM_SPEC_PATH),$(JVM_SPEC_PATH),)\n\nonly_yaml: pre_build yaml\n\n# Build binary files and display version information\nbuild_binary: show-version\n\tCGO_ENABLED=0 go build $(GO_FLAGS) -o $(BUILD_TARGET_BIN)/chaosblade-operator cmd/manager/main.go\n\t@echo \"Binary file build completed: $(BUILD_TARGET_BIN)/chaosblade-operator\"\n\t@echo \"Version information:\"\n\t@$(BUILD_TARGET_BIN)/chaosblade-operator version 2>/dev/null || echo \"Unable to get version information\"\n\n\n##----------------------------------------------------------------------------\n# build image\n\nbuild_linux_amd64_image:\n\tCGO_ENABLED=0 GOOS=\"linux\" GOARCH=\"amd64\" go build $(GO_FLAGS) -o build/_output/bin/chaosblade-operator cmd/manager/main.go\n\t$(CONTAINER_RUNTIME) buildx build -f build/image/amd/Dockerfile --platform=linux/amd64 -t ghcr.io/chaosblade-io/chaosblade-operator:${BLADE_VERSION} .\n\nbuild_linux_arm64_image:\n\tCGO_ENABLED=0 GOOS=\"linux\" GOARCH=\"arm64\" go build $(GO_FLAGS) -o build/_output/bin/chaosblade-operator cmd/manager/main.go\n\t$(CONTAINER_RUNTIME) buildx build -f build/image/arm/Dockerfile  --platform=linux/arm64  -t ghcr.io/chaosblade-io/chaosblade-operator-arm64:${BLADE_VERSION} .\n\npush_image:\n\t$(CONTAINER_RUNTIME) push ghcr.io/chaosblade-io/chaosblade-operator:${BLADE_VERSION}\n\t$(CONTAINER_RUNTIME) push ghcr.io/chaosblade-io/chaosblade-operator-arm64:${BLADE_VERSION}\n\n# Build Helm packages with version updates\nbuild_linux_amd64_helm: show-version pre_build\n\t@echo \"Building Linux AMD64 Helm package...\"\n\t@# Update Chart.yaml versions\n\t@sed -i.bak 's/^appVersion: \".*\"/appVersion: \"$(BLADE_VERSION)\"/' deploy/helm/chaosblade-operator/Chart.yaml\n\t@sed -i.bak 's/^version: .*/version: $(BLADE_VERSION)/' deploy/helm/chaosblade-operator/Chart.yaml\n\t@# Update values.yaml versions\n\t@sed -i.bak 's/^  version: .*/  version: $(BLADE_VERSION)/' deploy/helm/chaosblade-operator/values.yaml\n\t@sed -i.bak 's/^  version: .*/  version: $(BLADE_VERSION)/' deploy/helm/chaosblade-operator/values.yaml\n\t@# Clean up backup files\n\t@rm -f deploy/helm/chaosblade-operator/Chart.yaml.bak deploy/helm/chaosblade-operator/values.yaml.bak\n\t@# Package Helm chart\n\t@helm package deploy/helm/chaosblade-operator --destination $(BUILD_TARGET) --version $(BLADE_VERSION) --app-version $(BLADE_VERSION)\n\t@# Rename the package to include architecture\n\t@mv $(BUILD_TARGET)/chaosblade-operator-$(BLADE_VERSION).tgz $(BUILD_TARGET)/chaosblade-operator-amd64-$(BLADE_VERSION).tgz\n\t@echo \"Linux AMD64 Helm package created: $(BUILD_TARGET)/chaosblade-operator-amd64-$(BLADE_VERSION).tgz\"\n\nbuild_linux_arm64_helm: show-version pre_build\n\t@echo \"Building Linux ARM64 Helm package...\"\n\t@# Update Chart.yaml versions\n\t@sed -i.bak 's/^appVersion: \".*\"/appVersion: \"$(BLADE_VERSION)\"/' deploy/helm/chaosblade-operator-arm64/Chart.yaml\n\t@sed -i.bak 's/^version: .*/version: $(BLADE_VERSION)/' deploy/helm/chaosblade-operator-arm64/Chart.yaml\n\t@# Update values.yaml versions\n\t@sed -i.bak 's/^  version: .*/  version: $(BLADE_VERSION)/' deploy/helm/chaosblade-operator-arm64/values.yaml\n\t@sed -i.bak 's/^  version: .*/  version: $(BLADE_VERSION)/' deploy/helm/chaosblade-operator-arm64/values.yaml\n\t@# Clean up backup files\n\t@rm -f deploy/helm/chaosblade-operator-arm64/Chart.yaml.bak deploy/helm/chaosblade-operator-arm64/values.yaml.bak\n\t@# Package Helm chart\n\t@helm package deploy/helm/chaosblade-operator-arm64 --destination $(BUILD_TARGET) --version $(BLADE_VERSION) --app-version $(BLADE_VERSION)\n\t@echo \"Linux ARM64 Helm package created: $(BUILD_TARGET)/chaosblade-operator-arm64-$(BLADE_VERSION).tgz\"\n\n##----------------------------------------------------------------------------\n\n\nbuild_linux_amd64_release: build_linux_amd64_image build_linux_amd64_helm\nbuild_linux_arm64_release: build_linux_arm64_image build_linux_arm64_helm\n\n# test\ntest:\n\tgo test -race -coverprofile=coverage.txt -covermode=atomic ./...\n\n# clean all build result\nclean:\n\tgo clean ./...\n\trm -rf $(BUILD_TARGET)\n\trm -rf $(BUILD_IMAGE_PATH)/$(BUILD_TARGET_DIR_NAME)\n\n.PHONY: format\nformat: license-format\n\t@echo \"Running goimports and gofumpt to format Go code...\"\n\t@./hack/update-imports.sh\n\t@./hack/update-gofmt.sh\n\n.PHONY: verify\nverify:\n\t@echo \"Verifying Go code formatting and import order...\"\n\t@./hack/verify-gofmt.sh\n\t@./hack/verify-imports.sh\n\n.PHONY: license-check\nlicense-check:\n\t@echo \"Checking license headers...\"\n\tdocker run -it --rm -v $(shell pwd):/github/workspace ghcr.io/korandoru/hawkeye check\n\n.PHONY: license-format\nlicense-format:\n\t@echo \"Formatting license headers...\"\n\tdocker run -it --rm -v $(shell pwd):/github/workspace ghcr.io/korandoru/hawkeye format\n\n# Help information\nhelp:\n\t@echo \"Available build targets:\"\n\t@echo \"  linux_amd64    - Build Linux AMD64 platform components (operator + chaos_fuse + yaml)\"\n\t@echo \"  linux_arm64    - Build Linux ARM64 platform components (operator + chaos_fuse + yaml)\"\n\t@echo \"  build_linux_amd64_image - Build Linux AMD64 Docker image\"\n\t@echo \"  build_linux_arm64_image - Build Linux ARM64 Docker image\"\n\t@echo \"  build_linux_amd64_helm - Build and package Linux AMD64 Helm chart\"\n\t@echo \"  build_linux_arm64_helm - Build and package Linux ARM64 Helm chart\"\n\t@echo \"  build_linux_amd64_release - Build image and Helm package for AMD64\"\n\t@echo \"  build_linux_arm64_release - Build image and Helm package for ARM64\"\n\t@echo \"  push_image     - Push images to image registry\"\n\t@echo \"  show-version   - Display current version information\"\n\t@echo \" format \t  - Format Go code using goimports and gofumpt\"\n\t@echo \" verify \t  - Verify Go code formatting and import order\"\n\t@echo \" license-check  - Check license headers in source files\"\n\t@echo \"  clean          - Clean build artifacts\"\n\t@echo \"\"\n\t@echo \"Version-related environment variables:\"\n\t@echo \"  BLADE_VERSION  - Specify version number (default: Git tag)\"\n\t@echo \"  BLADE_VENDOR  - Specify vendor (default: community)\"\n\t@echo \"\"\n\t@echo \"Build-related environment variables:\"\n\t@echo \"  JVM_SPEC_PATH - Specify JVM specification file path (for container.JvmSpecFileForYaml)\"\n\t@echo \"  CONTAINER_RUNTIME - Specify container runtime (docker or podman, auto-detected by default)\"\n\t@echo \"\"\n"
  },
  {
    "path": "README.md",
    "content": "# Chaosblade-operator: A Chaos Engineering Tool for Cloud-native \n![license](https://img.shields.io/github/license/chaosblade-io/chaosblade.svg)\n\n中文版 [README](README_CN.md)\n## Introduction\nChaosblade Operator is a chaos experiments injection tool for cloud-native on kubernetes platform. By defining Kubernetes CRD to manage chaos experiments, each experiment has a very clear execution status. The tool has the characteristics of simple deployment, convenient execution, standardized implementation, and rich experiments. The chaos experimental model in chaosblade is well integrated with Kubernetes, which can realize the reuse of experiments such as basic resources, application services, and containers on the Kubernetes platform, which facilitates the expansion of resource experiments under Kubernetes, and can be executed uniformly through chaosblade cli tool.\n\n## Supported experiments (continuously adding ...)\nThe current experimental scenarios involve resources including Node, Pod, and Container. The specific supported experimental scenarios are as follows:\n* Node:\n    * CPU: specify CPU usage\n    * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n    * Process: specify process Hang, kill process, etc.\n    * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n    * Memory: specify memory usage\n* Pod:\n    * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n    * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n    * Memory: specify memory usage\n    * Pod: kill pod, make pod stuck in ContainerCreating state by PVC mount failure, make pod stuck in ContainerCreating state by cloud disk PVC creation failure, make pod stuck in Terminating state by finalizer, make pod scheduling fail by injecting unreachable affinity rules, modify workload (Deployment/DaemonSet/StatefulSet) CPU/Memory resource limits to simulate bad resource sizing, mount non-existent ConfigMap/Secret/PVC volume to workload (Deployment/DaemonSet/StatefulSet) to simulate volume mount failure\n    * IO: specify the file system io exception. Supports 31 file operations and 11 exception scenarios, such as \"Too many open files\", \"Device or resource busy\" and so on.\n* Container:\n    * CPU: specify CPU usage\n    * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n    * Process: specify process Hang, kill process, etc.\n    * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n    * Memory: specify memory usage\n    * Container: remove container\n* Service:\n    * Service: create, modify service\n\n## Local Build & Installation\n\n## Build images\n\n```shell\n# Under operator's root directory\n\n# For linux/amd64\nmake build_all\n\n# For linux/arm64\nmake build_all_amr64\n```\n\n### Build and install Helm Chart\n\n```shell\n# Under operator's root directory\ncd deploy/helm\n\n# For linux/amd64\nhelm package ./chaosblade-operator\nkubectl create ns chaosblade\nhelm install chaosblade chaosblade-operator-${version}.tgz --namespace chaosblade\n\n# For linux/arm64\nhelm package ./chaosblade-operator-arm64\nkubectl create ns chaosblade\nhelm install chaosblade chaosblade-operator-arm64-${version}.tgz --namespace chaosblade\n```\n\n## Install and uninstall\nThe lowest version of kubernetes supported is 1.12. Chaosblade operator can be installed through kubectl or helm, the installation method is as follows:\n\nNote: For the following `VERSION`, please use the latest version number instead\n\n### Helm v2\n* Download the latest `chaosblade-operator-VERSION-v2.tgz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n* Install using `helm install --namespace chaosblade --name chaosblade-operator chaosblade-operator-VERSION-v2.tgz`\n* Use `kubectl get pod -l part-of=chaosblade -n chaosblade` to check the installation status of the Pod. If both are running, the installation was successful\n* Use the following command to uninstall, pay attention to the execution order:\n```shell script\nkubectl delete crd chaosblades.chaosblade.io\nhelm del --purge chaosblade-operator\n```\n### Helm v3\n* Download the latest `chaosblade-operator-VERSION-v3.tgz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n* Use `helm install chaosblade-operator chaosblade-operator-VERSION-v3.tgz --namespace chaosblade` command to install\n* Use `kubectl get pod -l part-of=chaosblade -n chaosblade` to check the installation status of the Pod. If both are running, the installation was successful\n* Use the following command to uninstall, pay attention to the execution order:\n```shell script\nkubectl delete crd chaosblades.chaosblade.io\nhelm uninstall chaosblade-operator -n chaosblade\n```\n### Kubectl\n* Download the latest `chaosblade-operator-yaml-VERSION.tar.gz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n* After decompression, execute `kubectl apply -f chaosblade-operator-yaml-VERSION/` installation\n* Use `kubectl get pod -l part-of=chaosblade -n chaosblade` to check the installation status of the Pod. If both are running, the installation was successful\n* Use the following command to uninstall, pay attention to the execution order:\n```shell script\nkubectl delete crd chaosblades.chaosblade.io\nkubectl delete -f chaosblade-operator-yaml-VERSION/\n```\n\n## How to use\nYou can run chaos experiments after installing the chaosblade operator. There are three ways to execute chaos experiments:\n* By configuring yaml file, use kubectl to execute\n* Executed using chaosblade cli tool\n* Use Kubernetes API to execute by writing code\n\nThe following uses a specific case to illustrate the use of chaosblade-operator: simulate cn-hangzhou.192.168.0.205 node local port 40690 60% network packet loss.\n\n### By configuring the yaml file, use kubectl to execute\n```\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: loss-node-network-by-names\nspec:\n  experiments:\n  - scope: node\n    target: network\n    action: loss\n    desc: \"node network loss\"\n    matchers:\n    - name: names\n      value: [\"cn-hangzhou.192.168.0.205\"]\n    - name: percent\n      value: [\"60\"]\n    - name: interface\n      value: [\"eth0\"]\n    - name: local-port\n      value: [\"40690\"]\n```\nExecute experiment：\n```\nkubectl apply -f loss-node-network-by-names.yaml\n```\nQuery the experimental status, the returned information is as follows (spec and other contents are omitted):\n```\n~ » kubectl get blade loss-node-network-by-names -o json                                                            \n{\n    \"apiVersion\": \"chaosblade.io/v1alpha1\",\n    \"kind\": \"ChaosBlade\",\n    \"metadata\": {\n        \"creationTimestamp\": \"2019-11-04T09:56:36Z\",\n        \"finalizers\": [\n            \"finalizer.chaosblade.io\"\n        ],\n        \"generation\": 1,\n        \"name\": \"loss-node-network-by-names\",\n        \"resourceVersion\": \"9262302\",\n        \"selfLink\": \"/apis/chaosblade.io/v1alpha1/chaosblades/loss-node-network-by-names\",\n        \"uid\": \"63a926dd-fee9-11e9-b3be-00163e136d88\"\n    },\n        \"status\": {\n        \"expStatuses\": [\n            {\n                \"action\": \"loss\",\n                \"resStatuses\": [\n                    {\n                        \"id\": \"057acaa47ae69363\",\n                        \"kind\": \"node\",\n                        \"name\": \"cn-hangzhou.192.168.0.205\",\n                        \"nodeName\": \"cn-hangzhou.192.168.0.205\",\n                        \"state\": \"Success\",\n                        \"success\": true,\n                        \"uid\": \"e179b30d-df77-11e9-b3be-00163e136d88\"\n                    }\n                ],\n                \"scope\": \"node\",\n                \"state\": \"Success\",\n                \"success\": true,\n                \"target\": \"network\"\n            }\n        ],\n        \"phase\": \"Running\"\n    }\n}\n```\nFrom the above, you can clearly see the running status of the chaos experiment. Run the following command to stop the experiment:\n```\nkubectl delete -f loss-node-network-by-names.yaml\n```\nOr delete this blade resource directly:\n```\nkubectl delete blade loss-node-network-by-names\n```\nYou can also edit the yaml file to update the content of the experiment and the chaosblade operator will complete the update of the experiment. See more examples: [Examples](https://github.com/chaosblade-io/chaosblade-operator/tree/master/examples)\n\n### Execute with chaosblade cli tool\n```\nblade create k8s node-network loss --percent 60 --interface eth0 --local-port 40690 --names cn-hangzhou.192.168.0.205 --kubeconfig config\n```\nIf the execution fails, a detailed error message is returned; if the execution is successful, the experiment UID is returned:\n```\n{\"code\":200,\"success\":true,\"result\":\"e647064f5f20953c\"}\n```\nYou can query the status of the experiment with the following command:\n```\nblade query k8s create e647064f5f20953c --kubeconfig config\n\n{\n  \"code\": 200,\n  \"success\": true,\n  \"result\": {\n    \"uid\": \"e647064f5f20953c\",\n    \"success\": true,\n    \"error\": \"\",\n    \"statuses\": [\n      {\n        \"id\": \"fa471a6285ec45f5\",\n        \"uid\": \"e179b30d-df77-11e9-b3be-00163e136d88\",\n        \"name\": \"cn-hangzhou.192.168.0.205\",\n        \"state\": \"Success\",\n        \"kind\": \"node\",\n        \"success\": true,\n        \"nodeName\": \"cn-hangzhou.192.168.0.205\"\n      }\n    ]\n  }\n}\n```\nDestroy experiment:\n```\nblade destroy e647064f5f20953c\n```\nIn addition to the above two methods, you can also use the kubernetes client-go api for execution. For details, please refer to: [executor.go](https://github.com/chaosblade-io/chaosblade/blob/master/exec/kubernetes/executor.go) code implementation.\n\n[Chinese documentation](https://chaosblade-io.gitbook.io/chaosblade-help-zh-cn/blade-create-k8s)\n\n## Questions & Suggestions\nIf you encounter problems during installation and use, or suggestions and new features, all projects (including other projects) can be submitted to [Github Issues](https://github.com/chaosblade-io/chaosblade/issues)\n\nYou can also contact us via:\n* Dingding group: 23177705\n* Gitter room: [chaosblade community](https://gitter.im/chaosblade-io/community)\n* Email: chaosblade.io.01@gmail.com\n* Twitter: [chaosblade.io](https://twitter.com/ChaosbladeI)\n\n## Contributions\nWe welcome every issue and PR. Even a punctuation mark, how to participate in the contribution please read the [CONTRIBUTING](CONTRIBUTING.md) document, or contact us through the above method.\n\n## Open source license\nChaosblade-operator is licensed under the Apache 2.0 license. For details, please read [LICENSE](LICENSE)\n"
  },
  {
    "path": "README_CN.md",
    "content": "# Chaosblade Operator: 面向云原生的混沌工程执行工具\n![license](https://img.shields.io/github/license/chaosblade-io/chaosblade.svg)\n\n## 介绍\nChaosblade Operator 是混沌工程实验工具 ChaosBlade 下的一款面向云原生领域的混沌实验注入工具，可单独部署使用。通过定义 Kubernetes CRD 来管理混沌实验，每个实验都有非常明确的执行状态。工具具有部署简单、执行便捷、标准化实现、场景丰富等特点。将 ChaosBlade 混沌实验模型与 Kubernetes CRD 很好的结合在一起，可以实现基础资源、应用服务、容器等场景在 Kubernetes 平台上场景复用，方便了 Kubernetes 下资源场景的扩展，而且可通过 chaosblade cli 统一执行调用。\n\n## 支持的场景(持续新增中...)\n目前实验场景涉及到资源包含 Node、Pod、Container，具体支持的场景如下：\n* Node：\n    * CPU: 指定 CPU 使用率\n    * 网络: 指定网卡、端口、IP 等包延迟、丢包、包阻塞、包重复、包乱序、包损坏等\n    * 进程：指定进程 Hang、强杀指定进程等\n    * 磁盘：指定目录磁盘填充、磁盘 IO 读写负载等\n    * 内存：指定内存使用率\n* Pod：\n    * 网络：指定网卡、端口、IP 等包延迟、丢包、包阻塞、包重复、包乱序、包损坏等\n    * 磁盘：指定目录磁盘填充、磁盘 IO 读写负载等\n    * 内存：指定内存使用率\n    * Pod：杀 Pod、通过 PVC 挂载失败使 Pod 卡在 ContainerCreating 状态、通过云盘 PVC 创建失败使 Pod 卡在 ContainerCreating 状态、通过 finalizer 使 Pod 卡在 Terminating 状态、通过注入无法满足的亲和性规则使 Pod 调度失败、修改工作负载（Deployment/DaemonSet/StatefulSet）的 CPU/Memory 资源限制模拟资源配置异常、挂载不存在的 ConfigMap/Secret/PVC 类型 Volume 到工作负载（Deployment/DaemonSet/StatefulSet）模拟卷挂载失败\n* Container：\n    * CPU: 指定 CPU 使用率\n    * 网络: 指定网卡、端口、IP 等包延迟、丢包、包阻塞、包重复、包乱序、包损坏等\n    * 进程：指定进程 Hang、强杀指定进程等\n    * 磁盘：指定目录磁盘填充、磁盘 IO 读写负载等\n    * 内存：指定内存使用率\n    * Container: 杀 Container\n* Service：\n    * Service: 创建、修改Service\n\n## 本地构建&安装\n\n### 构造镜像\n\n```shell\n# operator 根目录下\n# linux/amd64\nmake build_all\n# linux/arm64\nmake build_all_amr64\n```\n\n### 构造并安装 Helm Chart\n\n```shell\n# operator 根目录下\ncd deploy/helm\n# linux/amd64\nhelm package ./chaosblade-operator\nkubectl create ns chaosblade\nhelm install chaosblade chaosblade-operator-${version}.tgz --namespace chaosblade\n# linux/arm64\nhelm package ./chaosblade-operator-arm64\nkubectl create ns chaosblade\nhelm install chaosblade chaosblade-operator-arm64-${version}.tgz --namespace chaosblade\n```\n\n## 安装&卸载\n支持的 Kubernetes 最小版本是 v1.12，chaosblade operator 可通过 kubectl 或者 helm 进行安装，安装方式如下：\n注意：以下的 `VERSION` 请使用最新的版本号替代\n### Helm v2\n* 在 [Release](https://github.com/chaosblade-io/chaosblade-operator/releases) 地址下载最新的 `chaosblade-operator-VERSION-v2.tgz` 包\n* 使用 `helm install --namespace kube-system --name chaosblade-operator chaosblade-operator-VERSION-v2.tgz` 命令安装\n* 使用 `kubectl get pod -l part-of=chaosblade -n kube-system` 查看 Pod 的安装状态，如果都是 running 状态，说明安装成功\n* 使用以下命令进行卸载，注意执行顺序：\n```shell script\nkubectl delete crd chaosblades.chaosblade.io\nhelm del --purge chaosblade-operator\n```\n### Helm v3\n* 在 [Release](https://github.com/chaosblade-io/chaosblade-operator/releases) 地址下载最新的 `chaosblade-operator-VERSION-v3.tgz` 包\n* 使用 `helm install chaosblade-operator chaosblade-operator-VERSION-v3.tgz --namespace kube-system` 命令安装\n* 使用 `kubectl get pod -l part-of=chaosblade -n kube-system` 查看 Pod 的安装状态，如果都是 running 状态，说明安装成功\n* 使用以下命令卸载，注意执行顺序:\n```shell script\nkubectl delete crd chaosblades.chaosblade.io\nhelm uninstall chaosblade-operator -n kube-system\n```\n\n### Kubectl\n* 在 [Release](https://github.com/chaosblade-io/chaosblade-operator/releases) 地址下载最新的 `chaosblade-operator-yaml-VERSION.tar.gz` 包\n* 解压后执行 `kubectl apply -f chaosblade-operator-yaml-VERSION/` 安装\n* 使用 `kubectl get pod -l part-of=chaosblade -n kube-system` 查看 Pod 的安装状态，如果都是 running 状态，说明安装成功\n* 使用以下命令卸载，注意执行顺序：\n```shell script\nkubectl delete crd chaosblades.chaosblade.io\nkubectl delete -f chaosblade-operator-yaml-VERSION/\n```\n\n## 使用\n安装 chaosblade operator 后即可执行混沌实验，执行方式有以下三种：\n* 通过配置 yaml 方式，使用 kubectl 执行\n* 使用 chaosblade cli 工具执行\n* 通过编写代码调用 Kubernetes API 执行\n\n下面通过一个具体的案例来说明 chaosblade-operator 的使用：模拟 cn-hangzhou.192.168.0.205 节点本地端口 40690 60% 的网络丢包。\n\n### 通过配置 yaml 方式，使用 kubectl 执行\n```\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: loss-node-network-by-names\nspec:\n  experiments:\n  - scope: node\n    target: network\n    action: loss\n    desc: \"node network loss\"\n    matchers:\n    - name: names\n      value: [\"cn-hangzhou.192.168.0.205\"]\n    - name: percent\n      value: [\"60\"]\n    - name: interface\n      value: [\"eth0\"]\n    - name: local-port\n      value: [\"40690\"]\n```\n执行实验：\n```\nkubectl apply -f loss-node-network-by-names.yaml\n```\n查询实验状态，返回信息如下（省略了 spec 等内容）：\n```\n~ » kubectl get blade loss-node-network-by-names -o json                                                            \n{\n    \"apiVersion\": \"chaosblade.io/v1alpha1\",\n    \"kind\": \"ChaosBlade\",\n    \"metadata\": {\n        \"creationTimestamp\": \"2019-11-04T09:56:36Z\",\n        \"finalizers\": [\n            \"finalizer.chaosblade.io\"\n        ],\n        \"generation\": 1,\n        \"name\": \"loss-node-network-by-names\",\n        \"resourceVersion\": \"9262302\",\n        \"selfLink\": \"/apis/chaosblade.io/v1alpha1/chaosblades/loss-node-network-by-names\",\n        \"uid\": \"63a926dd-fee9-11e9-b3be-00163e136d88\"\n    },\n        \"status\": {\n        \"expStatuses\": [\n            {\n                \"action\": \"loss\",\n                \"resStatuses\": [\n                    {\n                        \"id\": \"057acaa47ae69363\",\n                        \"kind\": \"node\",\n                        \"name\": \"cn-hangzhou.192.168.0.205\",\n                        \"nodeName\": \"cn-hangzhou.192.168.0.205\",\n                        \"state\": \"Success\",\n                        \"success\": true,\n                        \"uid\": \"e179b30d-df77-11e9-b3be-00163e136d88\"\n                    }\n                ],\n                \"scope\": \"node\",\n                \"state\": \"Success\",\n                \"success\": true,\n                \"target\": \"network\"\n            }\n        ],\n        \"phase\": \"Running\"\n    }\n}\n```\n通过以上内容可以很清晰的看出混沌实验的运行状态，执行以下命令停止实验：\n```\nkubectl delete -f loss-node-network-by-names.yaml\n```\n或者直接删除此 blade 资源\n```\nkubectl delete blade loss-node-network-by-names\n```\n还可以编辑 yaml 文件，更新实验内容执行，chaosblade operator 会完成实验的更新操作。更多案例请查看 [Examples](https://github.com/chaosblade-io/chaosblade-operator/tree/master/examples)\n\n### 使用 chaosblade cli 工具执行\n```\nblade create k8s node-network loss --percent 60 --interface eth0 --local-port 40690 --names cn-hangzhou.192.168.0.205 --kubeconfig config\n```\n如果执行失败，会返回详细的错误信息；如果执行成功，会返回实验的 UID：\n```\n{\"code\":200,\"success\":true,\"result\":\"e647064f5f20953c\"}\n```\n可通过以下命令查询实验状态：\n```\nblade query k8s create e647064f5f20953c --kubeconfig config\n\n{\n  \"code\": 200,\n  \"success\": true,\n  \"result\": {\n    \"uid\": \"e647064f5f20953c\",\n    \"success\": true,\n    \"error\": \"\",\n    \"statuses\": [\n      {\n        \"id\": \"fa471a6285ec45f5\",\n        \"uid\": \"e179b30d-df77-11e9-b3be-00163e136d88\",\n        \"name\": \"cn-hangzhou.192.168.0.205\",\n        \"state\": \"Success\",\n        \"kind\": \"node\",\n        \"success\": true,\n        \"nodeName\": \"cn-hangzhou.192.168.0.205\"\n      }\n    ]\n  }\n}\n```\n销毁实验：\n```\nblade destroy e647064f5f20953c\n```\n除了上述两种方式调用外，还可以使用 kubernetes client-go 方式执行，具体可参考：[executor.go](https://github.com/chaosblade-io/chaosblade/blob/master/exec/kubernetes/executor.go) 代码实现。\n\n[中文使用文档](https://chaosblade-io.gitbook.io/chaosblade-help-zh-cn/blade-create-k8s)\n\n## 问题&建议\n如果在安装使用过程中遇到问题，或者建议和新功能，所有项目（包含其他项目）的问题都可以提交到[Github Issues](https://github.com/chaosblade-io/chaosblade/issues) \n\n你也可以通过以下方式联系我们：\n* 钉钉群（推荐）：23177705\n* Gitter room: [chaosblade community](https://gitter.im/chaosblade-io/community)\n* 邮箱：chaosblade.io.01@gmail.com\n* Twitter: [chaosblade.io](https://twitter.com/ChaosbladeI)\n\n## 参与贡献\n我们非常欢迎每个 Issue 和 PR，即使一个标点符号，如何参加贡献请阅读 [CONTRIBUTING](CONTRIBUTING.md) 文档，或者通过上述的方式联系我们。\n\n## 开源许可证\nChaosblade-operator 遵循 Apache 2.0 许可证，详细内容请阅读 [LICENSE](LICENSE)\n"
  },
  {
    "path": "build/bin/entrypoint",
    "content": "#!/bin/sh -e\n\n# This is documented here:\n# https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines\n\n#if ! whoami &>/dev/null; then\n#  if [ -w /etc/passwd ]; then\n#    echo \"${USER_NAME:-chaosblade-operator}:x:$(id -u):$(id -g):${USER_NAME:-chaosblade-operator} user:${HOME}:/sbin/nologin\" >> /etc/passwd\n#  fi\n#fi\n\nexec ${OPERATOR} $@\n"
  },
  {
    "path": "build/bin/user_setup",
    "content": "#!/bin/sh\nset -x\n\n# ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be)\nmkdir -p ${HOME}\nchown ${USER_UID}:0 ${HOME}\nchmod ug+rwx ${HOME}\n\n# runtime user will need to be able to self-insert in /etc/passwd\nchmod g+rw /etc/passwd\n\n# no need for this script to remain in the image after running\nrm $0\n"
  },
  {
    "path": "build/image/amd/Dockerfile",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM --platform=linux/amd64 alpine:latest as builder\n\nENV OPERATOR=/usr/local/bin/chaosblade-operator\nCOPY build/_output/bin/chaosblade-operator /usr/local/bin/\n\n# Build platform-specific scripts for AMD64\nFROM --platform=linux/amd64 alpine:latest as script_builder\nRUN apk add --no-cache bash\nCOPY build/bin /tmp/scripts\nRUN chmod +x /tmp/scripts/*\n\nFROM --platform=linux/amd64 registry.access.redhat.com/ubi8/ubi-minimal:latest\n\nENV OPERATOR=/usr/local/bin/chaosblade-operator \\\n    CHAOSBLADE_HOME=/opt/chaosblade\n\nCOPY --from=builder ${OPERATOR} /usr/local/bin/\nCOPY --from=script_builder /tmp/scripts /usr/local/bin\n\nRUN chmod 777 /usr/local/bin/user_setup\nRUN /usr/local/bin/user_setup\n\nENTRYPOINT [\"/usr/local/bin/entrypoint\"]\n"
  },
  {
    "path": "build/image/arm/Dockerfile",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM alpine:latest as builder\n\nENV OPERATOR=/usr/local/bin/chaosblade-operator\nCOPY build/_output/bin/chaosblade-operator /usr/local/bin/\n\n# Build platform-specific scripts for ARM64\nFROM alpine:latest as script_builder\nRUN apk add --no-cache bash\nCOPY build/bin /tmp/scripts\nRUN chmod +x /tmp/scripts/*\n\nFROM registry.access.redhat.com/ubi8/ubi-minimal:latest\n\nENV OPERATOR=/usr/local/bin/chaosblade-operator \\\n    CHAOSBLADE_HOME=/opt/chaosblade\n\nCOPY --from=builder ${OPERATOR} /usr/local/bin/\nCOPY --from=script_builder /tmp/scripts /usr/local/bin\n\nRUN chmod 777 /usr/local/bin/user_setup\nRUN /usr/local/bin/user_setup\n\nENTRYPOINT [\"/usr/local/bin/entrypoint\"]\n"
  },
  {
    "path": "build/musl/Dockerfile",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM golang:1.20.13\nLABEL maintainer=\"Changjun Xiao\"\n\n# # The image is used to build chaosblade for musl\nRUN wget http://www.musl-libc.org/releases/musl-1.1.21.tar.gz \\\n    && tar -zxvf musl-1.1.21.tar.gz \\\n    && rm musl-1.1.21.tar.gz \\\n    && cd musl* \\\n    && ./configure \\\n    && make \\\n    && make install \\\n    && rm -rf musl*\n\nENV CC /usr/local/musl/bin/musl-gcc\nENV GOOS linux\n\nENTRYPOINT [ \"make\" ]\n"
  },
  {
    "path": "build/spec.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/container\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/node\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/pod\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/service\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n)\n\n// main creates the yaml file of the experiments about kubernetes\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tlog.Panicln(\"less yaml file path\")\n\t}\n\tif len(os.Args) == 3 {\n\t\tcontainer.JvmSpecPathForYaml = os.Args[2]\n\t}\n\terr := util.CreateYamlFile(getModels(), os.Args[1])\n\tif err != nil {\n\t\tlog.Panicf(\"create yaml file error, %v\", err)\n\t}\n}\n\nfunc getModels() *spec.Models {\n\tmodels := make([]*spec.Models, 0)\n\tnodeResourceModelSpec := node.NewResourceModelSpec(nil)\n\tfor _, modelSpec := range nodeResourceModelSpec.ExpModels() {\n\t\tmodel := util.ConvertSpecToModels(modelSpec, spec.ExpPrepareModel{}, nodeResourceModelSpec.Scope())\n\t\tmodels = append(models, model)\n\t}\n\tpodResourceModelSpec := pod.NewResourceModelSpec(nil)\n\tfor _, modelSpec := range podResourceModelSpec.ExpModels() {\n\t\tmodel := util.ConvertSpecToModels(modelSpec, spec.ExpPrepareModel{}, podResourceModelSpec.Scope())\n\t\tmodels = append(models, model)\n\t}\n\tcontainerResourceModelSpec := container.NewResourceModelSpec(nil)\n\tfor _, modelSpec := range containerResourceModelSpec.ExpModels() {\n\t\tmodel := util.ConvertSpecToModels(modelSpec, spec.ExpPrepareModel{}, containerResourceModelSpec.Scope())\n\t\tmodels = append(models, model)\n\t}\n\tserviceResourceModelSpec := service.NewResourceModelSpec(nil)\n\tfor _, modelSpec := range serviceResourceModelSpec.ExpModels() {\n\t\tmodel := util.ConvertSpecToModels(modelSpec, spec.ExpPrepareModel{}, serviceResourceModelSpec.Scope())\n\t\tmodels = append(models, model)\n\t}\n\treturn util.MergeModels(models...)\n}\n"
  },
  {
    "path": "channel/client.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage channel\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/remotecommand\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\n// Client contains the kubernetes client, operator client and kubeconfig\ntype Client struct {\n\tkubernetes.Interface\n\tclient.Client\n\tConfig *rest.Config\n}\n\n// NewClientFunc returns the controller client\nfunc NewClientFunc() client.NewClientFunc {\n\treturn func(config *rest.Config, options client.Options) (client.Client, error) {\n\t\tc, err := client.New(config, options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &Client{\n\t\t\tInterface: kubernetes.NewForConfigOrDie(config),\n\t\t\tClient:    c,\n\t\t\tConfig:    config,\n\t\t}, nil\n\t}\n}\n\ntype IOStreams struct {\n\tIn     io.Reader\n\tOut    io.Writer\n\tErrOut io.Writer\n}\n\ntype StreamOptions struct {\n\tIOStreams\n\tStdin      bool\n\tTTY        bool\n\tOutDecoder func(bytes []byte) interface{}\n\tErrDecoder func(bytes []byte) interface{}\n}\n\ntype ExecOptions struct {\n\tStreamOptions\n\tPodName       string\n\tPodNamespace  string\n\tContainerName string\n\tCommand       []string\n\tIgnoreOutput  bool\n}\n\n// Exec command in pod\nfunc (c *Client) Exec(options *ExecOptions) interface{} {\n\tlogFields := logrus.WithFields(logrus.Fields{\n\t\t\"command\":      options.Command,\n\t\t\"podName\":      options.PodName,\n\t\t\"podNamespace\": options.PodNamespace,\n\t\t\"container\":    options.ContainerName,\n\t})\n\tlogFields.Infof(\"Exec command in pod\")\n\trequest := c.CoreV1().RESTClient().Post().\n\t\tResource(\"pods\").\n\t\tName(options.PodName).\n\t\tNamespace(options.PodNamespace).\n\t\tSubResource(\"exec\").\n\t\tVersionedParams(\n\t\t\t&corev1.PodExecOptions{\n\t\t\t\tContainer: options.ContainerName,\n\t\t\t\tCommand:   options.Command,\n\t\t\t\tStdin:     options.Stdin,\n\t\t\t\tStdout:    true,\n\t\t\t\tStderr:    true,\n\t\t\t\tTTY:       options.TTY,\n\t\t\t}, scheme.ParameterCodec,\n\t\t)\n\toutput := bytes.NewBuffer([]byte{})\n\toptions.Out = output\n\terrput := bytes.NewBuffer([]byte{})\n\toptions.ErrOut = errput\n\n\terr := execute(\"POST\", request.URL(), c.Config, options)\n\terrMsg := strings.TrimSpace(errput.String())\n\toutMsg := strings.TrimSpace(output.String())\n\texecLog := logFields.WithFields(logrus.Fields{\n\t\t\"err\": errMsg,\n\t\t\"out\": outMsg,\n\t})\n\tif errMsg != \"\" {\n\t\texecLog.Infof(\"get err message\")\n\t\treturn options.ErrDecoder(errput.Bytes())\n\t}\n\tif err != nil {\n\t\texecLog.WithError(err).Errorln(\"Invoke exec command error\")\n\t\treturn options.ErrDecoder([]byte(err.Error()))\n\t}\n\tif outMsg != \"\" {\n\t\texecLog.Infof(\"get output message\")\n\t\treturn options.OutDecoder(output.Bytes())\n\t}\n\tif options.IgnoreOutput {\n\t\treturn nil\n\t}\n\treturn options.ErrDecoder([]byte(fmt.Sprintf(\"cannot get output of pods/%s/exec, maybe kubelet cannot be accessed or container not found\",\n\t\toptions.PodName)))\n}\n\n// \"172.21.1.11:8080/api/v1/namespaces/default/pods/my-nginx-3855515330-l1uqk/exec\n// ?container=my-nginx&stdin=1&stdout=1&stderr=1&tty=1&command=%2Fbin%2Fbash\"\nfunc execute(method string, url *url.URL, config *rest.Config, options *ExecOptions) error {\n\texec, err := remotecommand.NewSPDYExecutor(config, method, url)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn exec.Stream(remotecommand.StreamOptions{\n\t\tStdin:  options.StreamOptions.In,\n\t\tStdout: options.StreamOptions.Out,\n\t\tStderr: options.StreamOptions.ErrOut,\n\t\tTty:    options.StreamOptions.TTY,\n\t})\n}\n"
  },
  {
    "path": "channel/client_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage channel\n\nimport (\n\t\"testing\"\n)\n\nfunc TestClient_Exec(t *testing.T) {\n\t// Skip this test as it requires a real Kubernetes cluster connection\n\tt.Skip(\"Skipping TestClient_Exec: requires Kubernetes cluster connectivity\")\n}\n"
  },
  {
    "path": "cmd/hookfs/main.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"os/exec\"\n\t\"time\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/ethercflow/hookfs/hookfs\"\n\t\"github.com/sirupsen/logrus\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager/signals\"\n\n\tchaosbladehook \"github.com/chaosblade-io/chaosblade-operator/pkg/hookfs\"\n)\n\nvar (\n\taddress    string\n\tpidFile    string\n\toriginal   string\n\tmountpoint string\n)\n\nfunc main() {\n\tflag.StringVar(&address, \"address\", \":65534\", \"The address to bind\")\n\tflag.StringVar(&original, \"original\", \"\", \"Mapping of the original disk, not affected by the drill\")\n\tflag.StringVar(&mountpoint, \"mountpoint\", \"\", \"The disk of the drill. The affected directories are controlled by the path flag.\")\n\trand.Seed(time.Now().UnixNano())\n\tflag.Parse()\n\n\tlogFields := logrus.WithFields(logrus.Fields{\n\t\t\"address\":    address,\n\t\t\"original\":   original,\n\t\t\"mountpoint\": mountpoint,\n\t})\n\tstopCtx := signals.SetupSignalHandler()\n\tchaosbladeHookServer := chaosbladehook.NewChaosbladeHookServer(address)\n\tlogFields.Infoln(\"Start chaosblade hook server.\")\n\tgo chaosbladeHookServer.Start(stopCtx)\n\n\tlogFields.Infoln(\"Start fuse server.\")\n\tif err := startFuseServer(stopCtx); err != nil {\n\t\tlogFields.WithError(err).Fatalln(\"Start fuse server failed\")\n\t}\n}\n\n// startFuseServer starts hookfs server\nfunc startFuseServer(stop context.Context) error {\n\tif !util.IsExist(original) {\n\t\tif err := os.MkdirAll(original, os.FileMode(755)); err != nil {\n\t\t\treturn fmt.Errorf(\"create original directory error, %v\", err)\n\t\t}\n\t}\n\tif !util.IsExist(mountpoint) {\n\t\tif err := os.MkdirAll(mountpoint, os.FileMode(755)); err != nil {\n\t\t\treturn fmt.Errorf(\"create mountpoint directory error, %v\", err)\n\t\t}\n\t}\n\tfs, err := hookfs.NewHookFs(original, mountpoint, &chaosbladehook.ChaosbladeHook{MountPoint: mountpoint})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create hookfs error, %v\", err)\n\t}\n\terrCh := make(chan error)\n\tgo func() {\n\t\terrCh <- fs.Serve()\n\t}()\n\tfor {\n\t\tselect {\n\t\tcase <-stop.Done():\n\t\t\tlogFields := logrus.WithFields(logrus.Fields{\n\t\t\t\t\"address\":    address,\n\t\t\t\t\"original\":   original,\n\t\t\t\t\"mountpoint\": mountpoint,\n\t\t\t})\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\tdefer cancel()\n\t\t\tcmd := exec.CommandContext(ctx, \"fusermount\", \"-zu\", mountpoint)\n\t\t\tlogFields.Infof(\"Start unmount fuse volume, cmd: %v\", cmd)\n\t\t\tif err := cmd.Run(); err != nil {\n\t\t\t\tlogFields.WithError(err).Errorln(\"Failed to execute fusermount\")\n\t\t\t}\n\t\t\treturn err\n\t\tcase err := <-errCh:\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/manager/main.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/operator-framework/operator-sdk/pkg/k8sutil\"\n\t\"github.com/operator-framework/operator-sdk/pkg/leader\"\n\t\"github.com/operator-framework/operator-sdk/pkg/log/zap\"\n\tsdkVersion \"github.com/operator-framework/operator-sdk/version\"\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/pflag\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntimeutil \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook\"\n\n\tapiruntime \"k8s.io/apimachinery/pkg/runtime\"\n\t// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client/apiutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client/config\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager/signals\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/controller\"\n\toperator \"github.com/chaosblade-io/chaosblade-operator/pkg/runtime\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n\twebhookcfg \"github.com/chaosblade-io/chaosblade-operator/pkg/webhook\"\n\tmutator \"github.com/chaosblade-io/chaosblade-operator/pkg/webhook/pod\"\n\t\"github.com/chaosblade-io/chaosblade-operator/version\"\n)\n\nfunc printVersion() {\n\tlogrus.Infof(\"Go Version: %s\", runtime.Version())\n\tlogrus.Infof(\"Go OS/Arch: %s/%s\", runtime.GOOS, runtime.GOARCH)\n\tlogrus.Infof(\"Version of operator-sdk: %v\", sdkVersion.Version)\n\tlogrus.Infof(\"Operator Version: %v\", version.Version)\n\tlogrus.Infof(\"Operator Product: %v\", version.Product)\n\tlogrus.Infof(\"Build Time: %v\", version.BuildTime)\n\tlogrus.Infof(\"Git Commit: %v\", version.GitCommit)\n\tlogrus.Infof(\"Git Branch: %v\", version.GitBranch)\n\tlogrus.Infof(\"Platform: %v\", version.Platform)\n\tlogrus.Infof(\"Daemonset Enable: %t\", chaosblade.DaemonsetEnable)\n}\n\nfunc main() {\n\tpflag.CommandLine.AddFlagSet(zap.FlagSet())\n\tpflag.CommandLine.AddFlagSet(operator.FlagSet())\n\tpflag.CommandLine.AddFlagSet(webhookcfg.FlagSet())\n\tpflag.CommandLine.AddGoFlagSet(flag.CommandLine)\n\n\t// 添加版本标志\n\tshowVersion := pflag.Bool(\"version\", false, \"显示版本信息\")\n\tpflag.Parse()\n\n\t// 如果只是查看版本，则显示后退出\n\tif *showVersion {\n\t\tprintVersion()\n\t\treturn\n\t}\n\n\tinitLogger()\n\tprintVersion()\n\n\tcfg, err := config.GetConfig()\n\tif err != nil {\n\t\tlogrus.Fatalf(\"Get apiserver config error, %v\", err)\n\t}\n\terr = leader.Become(context.Background(), \"chaosblade-operator-lock\")\n\tif err != nil {\n\t\tlogrus.Fatalf(\"Become leader error, %v\", err)\n\t}\n\tcfg.QPS = operator.QPS\n\tmgr, err := createManager(cfg)\n\tif err != nil {\n\t\tlogrus.Fatalf(\"Create operator manager error, %v\", err)\n\t}\n\taddComponentsToManager(mgr)\n\tlogrus.Infoln(\"Starting the manager.\")\n\tif err := mgr.Start(signals.SetupSignalHandler()); err != nil {\n\t\tlogrus.Fatalf(\"Manager exited non-zero, %v\", err)\n\t}\n}\n\nfunc addComponentsToManager(mgr manager.Manager) {\n\tlogrus.Infof(\"Add all resources to scheme\")\n\t// Setup Scheme for all resources\n\tif err := apis.AddToScheme(mgr.GetScheme()); err != nil {\n\t\tlogrus.Fatalf(\"Add all resources to scheme error, %v\", err)\n\t}\n\tlogrus.Infof(\"Add all controllers to manager\")\n\t// Setup all Controllers\n\tif err := controller.AddToManager(mgr); err != nil {\n\t\tlogrus.Fatalf(\"Add all controllers to manager error, %v\", err)\n\t}\n\tif webhookcfg.Enable {\n\t\tlogrus.Infof(\"Webhook enabled, add it to manager\")\n\t\tif err := addWebhook(mgr); err != nil {\n\t\t\tlogrus.Fatalf(\"Add webhook to manager error, %v\", err)\n\t\t}\n\t}\n}\n\n// Init logrus and controller-runtime log\nfunc initLogger() {\n\tlevel, err := logrus.ParseLevel(operator.LogLevel)\n\tif err != nil {\n\t\tlevel = logrus.InfoLevel\n\t}\n\tlogrus.SetLevel(level)\n\tlog.SetLogger(zap.Logger())\n}\n\nfunc addWebhook(m manager.Manager) error {\n\tserver := webhook.NewServer(webhook.Options{\n\t\tPort: webhookcfg.Port,\n\t})\n\tif err := m.Add(server); err != nil {\n\t\treturn err\n\t}\n\tlogrus.Infof(\"registering %s to the webhook server\", \"mutating-pods\")\n\tserver.Register(\"/mutating-pods\", &webhook.Admission{Handler: &mutator.Mutator{}})\n\treturn nil\n}\n\n// createManager supports multi namespaces configuration\nfunc createManager(cfg *rest.Config) (manager.Manager, error) {\n\tscheme := apiruntime.NewScheme()\n\truntimeutil.Must(metav1.AddMetaToScheme(scheme))\n\truntimeutil.Must(corev1.AddToScheme(scheme))\n\truntimeutil.Must(appsv1.AddToScheme(scheme))\n\truntimeutil.Must(apis.AddToScheme(scheme))\n\twatchNamespace, err := k8sutil.GetWatchNamespace()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogrus.Infof(\"Get watch namespace is %s\", watchNamespace)\n\tif strings.Contains(watchNamespace, \",\") {\n\t\tdefaultNsps := make(map[string]cache.Config)\n\t\tfor _, nsp := range strings.Split(watchNamespace, \",\") {\n\t\t\tdefaultNsps[nsp] = cache.Config{}\n\t\t}\n\t\treturn manager.New(cfg, manager.Options{\n\t\t\tCache: cache.Options{\n\t\t\t\tScheme:            scheme,\n\t\t\t\tDefaultNamespaces: defaultNsps,\n\t\t\t},\n\t\t\tScheme: scheme,\n\t\t\tMapperProvider: func(c *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) {\n\t\t\t\treturn apiutil.NewDynamicRESTMapper(c, httpClient)\n\t\t\t},\n\t\t\tNewClient: channel.NewClientFunc(),\n\t\t})\n\t}\n\treturn manager.New(cfg, manager.Options{\n\t\tCache: cache.Options{\n\t\t\tScheme:            scheme,\n\t\t\tDefaultNamespaces: map[string]cache.Config{watchNamespace: {}},\n\t\t},\n\t\tScheme: scheme,\n\t\tMapperProvider: func(c *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) {\n\t\t\treturn apiutil.NewDynamicRESTMapper(c, httpClient)\n\t\t},\n\t\tNewClient: channel.NewClientFunc(),\n\t})\n}\n"
  },
  {
    "path": "deploy/crds/chaosblade.io_chaosblades_crd.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: chaosblades.chaosblade.io\nspec:\n  group: chaosblade.io\n  names:\n    kind: ChaosBlade\n    listKind: ChaosBladeList\n    plural: chaosblades\n    singular: chaosblade\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      schema:\n        openAPIV3Schema:\n          description: ChaosBlade is the Schema for the chaosblades API\n          properties:\n            apiVersion:\n              description: 'APIVersion defines the versioned schema of this representation\n                    of an object. Servers should convert recognized schemas to the latest\n                    internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n              type: string\n            kind:\n              description: 'Kind is a string value representing the REST resource this\n                    object represents. Servers may infer this from the endpoint the client\n                    submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n              type: string\n            metadata:\n              type: object\n            spec:\n              description: ChaosBladeSpec defines the desired state of ChaosBlade\n              properties:\n                experiments:\n                  description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                        Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                        modifying this file Add custom validation using kubebuilder tags:\n                        https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n                  items:\n                    properties:\n                      action:\n                        description: Action is the experiment scenario of the target,\n                          such as delay, load\n                        type: string\n                      desc:\n                        description: Desc is the experiment description\n                        type: string\n                      matchers:\n                        description: Matchers is the experiment rules\n                        items:\n                          properties:\n                            name:\n                              description: Name is the name of flag\n                              type: string\n                            value:\n                              description: 'TODO: Temporarily defined as an array for\n                                    all flags Value is the value of flag'\n                              items:\n                                type: string\n                              type: array\n                          required:\n                            - name\n                            - value\n                          type: object\n                        type: array\n                      scope:\n                        description: Scope is the area of the experiments, currently support\n                          node, pod and container\n                        type: string\n                      target:\n                        description: Target is the experiment target, such as cpu, network\n                        type: string\n                    required:\n                      - action\n                      - scope\n                      - target\n                    type: object\n                  type: array\n              required:\n                - experiments\n              type: object\n            status:\n              description: ChaosBladeStatus defines the observed state of ChaosBlade\n              properties:\n                expStatuses:\n                  description: 'Important: Run \"operator-sdk generate k8s\" to regenerate\n                        code after modifying this file Add custom validation using kubebuilder\n                        tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n                  items:\n                    properties:\n                      action:\n                        type: string\n                      error:\n                        type: string\n                      resStatuses:\n                        description: ResStatuses is the details of the experiment\n                        items:\n                          properties:\n                            error:\n                              description: experiment error\n                              type: string\n                            id:\n                              description: experiment uid in chaosblade\n                              type: string\n                            identifier:\n                              description: 'Resource identifier, rules as following: container:\n                                    Namespace/NodeName/PodName/ContainerName pod： Namespace/NodeName/PodName'\n                              type: string\n                            kind:\n                              description: Kind\n                              type: string\n                            state:\n                              description: experiment state\n                              type: string\n                            success:\n                              description: success\n                              type: boolean\n                          required:\n                            - kind\n                            - state\n                            - success\n                          type: object\n                        type: array\n                      scope:\n                        description: experiment scope for cache\n                        type: string\n                      state:\n                        description: State is used to describe the experiment result\n                        type: string\n                      success:\n                        description: Success is used to judge the experiment result\n                        type: boolean\n                      target:\n                        type: string\n                    required:\n                      - action\n                      - scope\n                      - state\n                      - success\n                      - target\n                    type: object\n                  type: array\n                phase:\n                  description: Phase indicates the state of the experiment   Initial ->\n                    Running -> Updating -> Destroying -> Destroyed\n                  type: string\n              required:\n                - expStatuses\n              type: object\n          type: object\n      served: true\n      storage: true\n      subresources:\n        status: {}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/Chart.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nappVersion: \"1.8.0\"\ndescription: ChaosBlade Operator\nname: chaosblade-operator\nversion: 1.8.0\nhome: https://github.com/chaosblade-io\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/crds/crd.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: chaosblades.chaosblade.io\nspec:\n  group: chaosblade.io\n  names:\n    kind: ChaosBlade\n    listKind: ChaosBladeList\n    plural: chaosblades\n    singular: chaosblade\n    shortNames: [blade]\n  scope: Cluster\n  versions:\n    - name: v1alpha1\n      schema:\n        openAPIV3Schema:\n          description: ChaosBlade is the Schema for the chaosblades API\n          properties:\n            apiVersion:\n              description: 'APIVersion defines the versioned schema of this representation\n                    of an object. Servers should convert recognized schemas to the latest\n                    internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n              type: string\n            kind:\n              description: 'Kind is a string value representing the REST resource this\n                    object represents. Servers may infer this from the endpoint the client\n                    submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n              type: string\n            metadata:\n              type: object\n            spec:\n              description: ChaosBladeSpec defines the desired state of ChaosBlade\n              properties:\n                experiments:\n                  description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                        Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                        modifying this file Add custom validation using kubebuilder tags:\n                        https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n                  items:\n                    properties:\n                      action:\n                        description: Action is the experiment scenario of the target,\n                          such as delay, load\n                        type: string\n                      desc:\n                        description: Desc is the experiment description\n                        type: string\n                      matchers:\n                        description: Matchers is the experiment rules\n                        items:\n                          properties:\n                            name:\n                              description: Name is the name of flag\n                              type: string\n                            value:\n                              description: 'TODO: Temporarily defined as an array for\n                                    all flags Value is the value of flag'\n                              items:\n                                type: string\n                              type: array\n                          required:\n                            - name\n                            - value\n                          type: object\n                        type: array\n                      scope:\n                        description: Scope is the area of the experiments, currently support\n                          node, pod and container\n                        type: string\n                      target:\n                        description: Target is the experiment target, such as cpu, network\n                        type: string\n                    required:\n                      - action\n                      - scope\n                      - target\n                    type: object\n                  type: array\n              required:\n                - experiments\n              type: object\n            status:\n              description: ChaosBladeStatus defines the observed state of ChaosBlade\n              properties:\n                expStatuses:\n                  description: 'Important: Run \"operator-sdk generate k8s\" to regenerate\n                        code after modifying this file Add custom validation using kubebuilder\n                        tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n                  items:\n                    properties:\n                      action:\n                        type: string\n                      error:\n                        type: string\n                      resStatuses:\n                        description: ResStatuses is the details of the experiment\n                        items:\n                          properties:\n                            error:\n                              description: experiment error\n                              type: string\n                            id:\n                              description: experiment uid in chaosblade\n                              type: string\n                            identifier:\n                              description: 'Resource identifier, rules as following: container:\n                                    Namespace/NodeName/PodName/ContainerName pod： Namespace/NodeName/PodName'\n                              type: string\n                            kind:\n                              description: Kind\n                              type: string\n                            state:\n                              description: experiment state\n                              type: string\n                            success:\n                              description: success\n                              type: boolean\n                          required:\n                            - kind\n                            - state\n                            - success\n                          type: object\n                        type: array\n                      scope:\n                        description: experiment scope for cache\n                        type: string\n                      state:\n                        description: State is used to describe the experiment result\n                        type: string\n                      success:\n                        description: Success is used to judge the experiment result\n                        type: boolean\n                      target:\n                        type: string\n                    required:\n                      - action\n                      - scope\n                      - state\n                      - success\n                      - target\n                    type: object\n                  type: array\n                phase:\n                  description: Phase indicates the state of the experiment   Initial ->\n                    Running -> Updating -> Destroying -> Destroyed\n                  type: string\n              required:\n                - expStatuses\n              type: object\n          type: object\n      served: true\n      storage: true\n      subresources:\n        status: {}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/templates/NOTES.txt",
    "content": "Thank you for using chaosblade."
  },
  {
    "path": "deploy/helm/chaosblade-operator/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/templates/daemonset.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.daemonset.enable }}\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: chaosblade-tool\n  labels:\n    name: chaosblade-tool\n    app: chaosblade-tool\nspec:\n  selector:\n    matchLabels:\n      name: chaosblade-tool\n      app: chaosblade-tool\n  updateStrategy:\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        name: chaosblade-tool\n        app: chaosblade-tool\n    spec:\n      affinity:\n        nodeAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n            nodeSelectorTerms:\n              - matchExpressions:\n                  - key: type\n                    operator: NotIn\n                    values:\n                      - virtual-kubelet\n      containers:\n        - name: chaosblade-tool\n          image: {{ .Values.blade.repository }}:{{ .Values.blade.version }}\n          imagePullPolicy: {{ .Values.blade.pullPolicy }}\n          env:\n            - name: KUBERNETES_NODENAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: spec.nodeName\n            - name: DOCKER_API_VERSION\n              value: \"1.44\"\n            - name: CGROUP_ROOT\n              value: \"/host-sys/fs/cgroup/\"\n          securityContext:\n            privileged: true\n          volumeMounts:\n            - mountPath: /var/run/docker.sock\n              name: docker-socket\n            - mountPath: /opt/chaosblade/chaosblade.dat\n              name: chaosblade-db-volume\n            - mountPath: /etc/hosts\n              name: hosts\n            - mountPath: /var/log/audit\n              name: audit\n            - mountPath: /var/lib/docker\n              name: docker-lib\n            - mountPath: /etc/docker\n              name: docker-etc\n            - mountPath: /run/containerd\n              name: containerd\n            - mountPath: /var/lib/containerd\n              name: containerd-lib\n            - mountPath: /etc/containerd\n              name: containerd-etc\n            - mountPath: /var/run/netns\n              name: netns\n            - mountPath: /host-sys\n              name: sys\n      dnsPolicy: ClusterFirstWithHostNet\n      hostNetwork: true\n      hostPID: true\n      tolerations:\n        - effect: NoSchedule\n          operator: Exists\n      volumes:\n        - hostPath:\n            path: /var/run/docker.sock\n          name: docker-socket\n        - hostPath:\n            path: /var/run/chaosblade.dat\n            type: FileOrCreate\n          name: chaosblade-db-volume\n        - hostPath:\n            path: /etc/hosts\n          name: hosts\n        - hostPath:\n            path: /var/lib/docker\n          name: docker-lib\n        - hostPath:\n            path: /etc/docker\n          name: docker-etc\n        - hostPath:\n            path: /var/log/audit\n          name: audit\n        - hostPath:\n            path: /run/containerd\n          name: containerd\n        - hostPath:\n            path: /var/lib/containerd\n          name: containerd-lib\n        - hostPath:\n            path: /etc/containerd\n          name: containerd-etc\n        - hostPath:\n            path: /var/run/netns\n          name: netns\n        - hostPath:\n            path: /sys\n          name: sys\n      serviceAccountName: chaosblade\n{{- end }}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/templates/deployment.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: chaosblade-operator\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      name: chaosblade-operator\n  template:\n    metadata:\n      labels:\n        name: chaosblade-operator\n        part-of: chaosblade\n    spec:\n      dnsPolicy: {{ .Values.network.dns.policy }}\n      hostNetwork: {{ .Values.network.host }}\n      serviceAccountName: chaosblade\n      initContainers:\n        - name: chaosblade-tool\n          image: {{ .Values.blade.repository }}:{{ .Values.blade.version }}\n          imagePullPolicy: {{ .Values.blade.pullPolicy }}\n          command: [ \"cp\", \"-R\",\"/opt/chaosblade\", \"/home\" ]\n          volumeMounts:\n            - mountPath: /home\n              name: chaosblade\n      containers:\n        - name: chaosblade-operator\n          image: {{ .Values.operator.repository }}:{{ .Values.operator.version }}\n          command: [\"chaosblade-operator\"]\n          args:\n          {{- if .Values.blade.repository }}\n          - '--chaosblade-image-repository={{ .Values.blade.repository }}'\n          {{- end }}\n          {{- if .Values.blade.version }}\n          - '--chaosblade-version={{ .Values.blade.version }}'\n          {{- end }}\n          {{- if .Values.blade.pullPolicy }}\n          - '--chaosblade-image-pull-policy={{ .Values.blade.pullPolicy }}'\n          {{- end }}\n          {{- if .Values.env.zapLevel }}\n          - '--zap-level={{ .Values.env.zapLevel }}'\n          {{- end }}\n          {{- if .Values.env.logLevel }}\n          - '--log-level={{ .Values.env.logLevel }}'\n          {{- end }}\n          {{- if .Values.webhook.enable }}\n          - '--webhook-enable'\n          {{- end }}\n          {{- if .Values.daemonset.enable }}\n          - '--daemonset-enable'\n          {{- end }}\n          {{- if .Values.remove.blade.interval }}\n          - '--remove-blade-interval={{ .Values.remove.blade.interval }}'\n          {{- end }}\n          {{- if .Values.blade.downloadUrl }}\n          - '--chaosblade-download-url={{ .Values.blade.downloadUrl }}'\n          {{- end }}\n          - '--chaosblade-namespace={{ .Release.Namespace }}'\n          imagePullPolicy: {{ .Values.operator.pullPolicy }}\n          env:\n            - name: WATCH_NAMESPACE\n              value: \"\"\n            - name: POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: OPERATOR_NAME\n              value: \"chaosblade-operator\"\n          ports:\n            - containerPort: 9443\n              protocol: TCP\n          volumeMounts:\n            - mountPath: /tmp/k8s-webhook-server/serving-certs\n              name: cert\n              readOnly: true\n            - mountPath: /opt\n              name: chaosblade\n      volumes:\n        - name: cert\n          secret:\n            defaultMode: 420\n            secretName: chaosblade-webhook-server-cert\n        - name: chaosblade\n          emptyDir: {}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/templates/rbac.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\n  namespace: {{ .Release.Namespace }}\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\nrules:\n  - apiGroups:\n      - ''\n    resources:\n      - pods\n      - pods/exec\n      - configmaps\n      - secrets\n      - services\n      - persistentvolumeclaims\n      - persistentvolumes\n    verbs:\n      - \"*\"\n  - apiGroups:\n      - ''\n    resources:\n      - nodes\n    verbs:\n      - get\n      - list\n      - watch\n      - update\n  - apiGroups:\n      - apps\n    resources:\n      - daemonsets\n      - deployments\n      - statefulsets\n    verbs:\n      - \"*\"\n  - apiGroups:\n      - chaosblade.io\n    resources:\n      - chaosblades\n      - chaosblades/status\n    verbs:\n      - \"*\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\nroleRef:\n  kind: ClusterRole\n  name: chaosblade\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n  - kind: ServiceAccount\n    name: chaosblade\n    namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/templates/secret.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- $ca := genCA \"chaosblade-webhook-server-ca\" 3650 }}\n{{- $cn := \"chaosblade-webhook-server\" }}\n{{- $dns1 := printf \"%s.%s\" $cn .Release.Namespace }}\n{{- $dns2 := printf \"%s.%s.svc\" $cn .Release.Namespace }}\n{{- $cert := genSignedCert $cn nil (list $dns1 $dns2) 3650 $ca }}\n\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: chaosblade-operator\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: chaosblade-operator\n    chart: \"{{ .Chart.Name }}-{{ .Chart.Version }}\"\n    release: \"{{ .Release.Name }}\"\n    heritage: \"{{ .Release.Service }}\"\nwebhooks:\n  - clientConfig:\n      caBundle: {{ $ca.Cert | b64enc | quote }}\n      service:\n        name: chaosblade-webhook-server\n        namespace: {{ .Release.Namespace }}\n        path: /mutating-pods\n    name: \"{{ .Chart.Name }}.{{ .Release.Namespace }}.svc\"\n    failurePolicy: Ignore\n    rules:\n      - apiGroups:\n          - \"\"\n        apiVersions:\n          - v1\n        operations:\n          - CREATE\n          - UPDATE\n        resources:\n          - pods\n    sideEffects: None\n    admissionReviewVersions: [\"v1beta1\"]\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: chaosblade-webhook-server-cert\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: chaosblade-operator\n    chart: \"{{ .Chart.Name }}-{{ .Chart.Version }}\"\n    heritage: {{ .Release.Service }}\n    release: {{ .Release.Name }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ $cert.Cert | b64enc | quote }}\n  tls.key: {{ $cert.Key | b64enc | quote }}\n  ca.crt: {{ $ca.Cert | b64enc | quote }}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/templates/service.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: chaosblade-webhook-server\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n    - port: 443\n      targetPort: 9443\n  selector:\n    name: chaosblade-operator\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator/values.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Default values for chaosblade.\n\n# chaosblade-operator\noperator:\n  repository: ghcr.io/chaosblade-io/chaosblade-operator\n  version: 1.8.0\n  # image.pullPolicy: must be Always|IfNotPresent|Never\n  pullPolicy: IfNotPresent\n  # qps of kubernetes client\n  qps: 20\n  reconcileCount: 20\n\nblade:\n  repository: ghcr.io/chaosblade-io/chaosblade-tool\n  version: 1.8.0\n  pullPolicy: IfNotPresent\n  downloadUrl: \"\"\n\nenv:\n  logLevel: info\n\nwebhook:\n  enable: true\n\ndaemonset:\n  enable: true\n\nremove:\n  blade:\n    interval: 72h\n\nnetwork:\n  host: false\n  dns:\n    policy: ClusterFirst\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/Chart.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nappVersion: \"1.8.0\"\ndescription: ChaosBlade Operator\nname: chaosblade-operator-arm64\nversion: 1.8.0\nhome: https://github.com/chaosblade-io\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/crds/crd.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: chaosblades.chaosblade.io\nspec:\n  group: chaosblade.io\n  names:\n    kind: ChaosBlade\n    listKind: ChaosBladeList\n    plural: chaosblades\n    singular: chaosblade\n    shortNames: [blade]\n  scope: Cluster\n  versions:\n    - name: v1alpha1\n      schema:\n        openAPIV3Schema:\n          description: ChaosBlade is the Schema for the chaosblades API\n          properties:\n            apiVersion:\n              description: 'APIVersion defines the versioned schema of this representation\n                    of an object. Servers should convert recognized schemas to the latest\n                    internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n              type: string\n            kind:\n              description: 'Kind is a string value representing the REST resource this\n                    object represents. Servers may infer this from the endpoint the client\n                    submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n              type: string\n            metadata:\n              type: object\n            spec:\n              description: ChaosBladeSpec defines the desired state of ChaosBlade\n              properties:\n                experiments:\n                  description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                        Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                        modifying this file Add custom validation using kubebuilder tags:\n                        https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n                  items:\n                    properties:\n                      action:\n                        description: Action is the experiment scenario of the target,\n                          such as delay, load\n                        type: string\n                      desc:\n                        description: Desc is the experiment description\n                        type: string\n                      matchers:\n                        description: Matchers is the experiment rules\n                        items:\n                          properties:\n                            name:\n                              description: Name is the name of flag\n                              type: string\n                            value:\n                              description: 'TODO: Temporarily defined as an array for\n                                    all flags Value is the value of flag'\n                              items:\n                                type: string\n                              type: array\n                          required:\n                            - name\n                            - value\n                          type: object\n                        type: array\n                      scope:\n                        description: Scope is the area of the experiments, currently support\n                          node, pod and container\n                        type: string\n                      target:\n                        description: Target is the experiment target, such as cpu, network\n                        type: string\n                    required:\n                      - action\n                      - scope\n                      - target\n                    type: object\n                  type: array\n              required:\n                - experiments\n              type: object\n            status:\n              description: ChaosBladeStatus defines the observed state of ChaosBlade\n              properties:\n                expStatuses:\n                  description: 'Important: Run \"operator-sdk generate k8s\" to regenerate\n                        code after modifying this file Add custom validation using kubebuilder\n                        tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n                  items:\n                    properties:\n                      action:\n                        type: string\n                      error:\n                        type: string\n                      resStatuses:\n                        description: ResStatuses is the details of the experiment\n                        items:\n                          properties:\n                            error:\n                              description: experiment error\n                              type: string\n                            id:\n                              description: experiment uid in chaosblade\n                              type: string\n                            identifier:\n                              description: 'Resource identifier, rules as following: container:\n                                    Namespace/NodeName/PodName/ContainerName pod： Namespace/NodeName/PodName'\n                              type: string\n                            kind:\n                              description: Kind\n                              type: string\n                            state:\n                              description: experiment state\n                              type: string\n                            success:\n                              description: success\n                              type: boolean\n                          required:\n                            - kind\n                            - state\n                            - success\n                          type: object\n                        type: array\n                      scope:\n                        description: experiment scope for cache\n                        type: string\n                      state:\n                        description: State is used to describe the experiment result\n                        type: string\n                      success:\n                        description: Success is used to judge the experiment result\n                        type: boolean\n                      target:\n                        type: string\n                    required:\n                      - action\n                      - scope\n                      - state\n                      - success\n                      - target\n                    type: object\n                  type: array\n                phase:\n                  description: Phase indicates the state of the experiment   Initial ->\n                    Running -> Updating -> Destroying -> Destroyed\n                  type: string\n              required:\n                - expStatuses\n              type: object\n          type: object\n      served: true\n      storage: true\n      subresources:\n        status: {}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/templates/NOTES.txt",
    "content": "Thank you for using chaosblade."
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/templates/daemonset.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- if .Values.daemonset.enable }}\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: chaosblade-tool\n  labels:\n    name: chaosblade-tool\n    app: chaosblade-tool\nspec:\n  selector:\n    matchLabels:\n      name: chaosblade-tool\n      app: chaosblade-tool\n  updateStrategy:\n    type: RollingUpdate\n  template:\n    metadata:\n      labels:\n        name: chaosblade-tool\n        app: chaosblade-tool\n    spec:\n      affinity:\n        nodeAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n            nodeSelectorTerms:\n              - matchExpressions:\n                  - key: type\n                    operator: NotIn\n                    values:\n                      - virtual-kubelet\n      containers:\n        - name: chaosblade-tool\n          image: {{ .Values.blade.repository }}:{{ .Values.blade.version }}\n          imagePullPolicy: {{ .Values.blade.pullPolicy }}\n          env:\n            - name: KUBERNETES_NODENAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: spec.nodeName\n            - name: DOCKER_API_VERSION\n              value: \"1.14.0\"\n            - name: CGROUP_ROOT\n              value: \"/host-sys/fs/cgroup/\"\n          securityContext:\n            privileged: true\n          volumeMounts:\n            - mountPath: /var/run/docker.sock\n              name: docker-socket\n            - mountPath: /opt/chaosblade/chaosblade.dat\n              name: chaosblade-db-volume\n            - mountPath: /etc/hosts\n              name: hosts\n            - mountPath: /var/log/audit\n              name: audit\n            - mountPath: /var/lib/docker\n              name: docker-lib\n            - mountPath: /etc/docker\n              name: docker-etc\n            - mountPath: /run/containerd\n              name: containerd\n            - mountPath: /var/lib/containerd\n              name: containerd-lib\n            - mountPath: /etc/containerd\n              name: containerd-etc\n            - mountPath: /var/run/netns\n              name: netns\n            - mountPath: /host-sys\n              name: sys\n      dnsPolicy: ClusterFirstWithHostNet\n      hostNetwork: true\n      hostPID: true\n      tolerations:\n        - effect: NoSchedule\n          operator: Exists\n      volumes:\n        - hostPath:\n            path: /var/run/docker.sock\n          name: docker-socket\n        - hostPath:\n            path: /var/run/chaosblade.dat\n            type: FileOrCreate\n          name: chaosblade-db-volume\n        - hostPath:\n            path: /etc/hosts\n          name: hosts\n        - hostPath:\n            path: /var/lib/docker\n          name: docker-lib\n        - hostPath:\n            path: /etc/docker\n          name: docker-etc\n        - hostPath:\n            path: /var/log/audit\n          name: audit\n        - hostPath:\n            path: /run/containerd\n          name: containerd\n        - hostPath:\n            path: /var/lib/containerd\n          name: containerd-lib\n        - hostPath:\n            path: /etc/containerd\n          name: containerd-etc\n        - hostPath:\n            path: /var/run/netns\n          name: netns\n        - hostPath:\n            path: /sys\n          name: sys\n      serviceAccountName: chaosblade\n{{- end }}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/templates/deployment.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: chaosblade-operator\n  namespace: {{ .Release.Namespace }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      name: chaosblade-operator\n  template:\n    metadata:\n      labels:\n        name: chaosblade-operator\n        part-of: chaosblade\n    spec:\n      dnsPolicy: {{ .Values.network.dns.policy }}\n      hostNetwork: {{ .Values.network.host }}\n      serviceAccountName: chaosblade\n      initContainers:\n        - name: chaosblade-tool\n          image: {{ .Values.blade.repository }}:{{ .Values.blade.version }}\n          imagePullPolicy: {{ .Values.blade.pullPolicy }}\n          command: [ \"cp\", \"-R\",\"/opt/chaosblade\", \"/home\" ]\n          volumeMounts:\n            - mountPath: /home\n              name: chaosblade\n      containers:\n        - name: chaosblade-operator\n          image: {{ .Values.operator.repository }}:{{ .Values.operator.version }}\n          command: [\"chaosblade-operator\"]\n          args:\n          {{- if .Values.blade.repository }}\n          - '--chaosblade-image-repository={{ .Values.blade.repository }}'\n          {{- end }}\n          {{- if .Values.blade.version }}\n          - '--chaosblade-version={{ .Values.blade.version }}'\n          {{- end }}\n          {{- if .Values.blade.pullPolicy }}\n          - '--chaosblade-image-pull-policy={{ .Values.blade.pullPolicy }}'\n          {{- end }}\n          {{- if .Values.env.zapLevel }}\n          - '--zap-level={{ .Values.env.zapLevel }}'\n          {{- end }}\n          {{- if .Values.env.logLevel }}\n          - '--log-level={{ .Values.env.logLevel }}'\n          {{- end }}\n          {{- if .Values.webhook.enable }}\n          - '--webhook-enable'\n          {{- end }}\n          {{- if .Values.daemonset.enable }}\n          - '--daemonset-enable'\n          {{- end }}\n          {{- if .Values.remove.blade.interval }}\n          - '--remove-blade-interval={{ .Values.remove.blade.interval }}'\n          {{- end }}\n          {{- if .Values.blade.downloadUrl }}\n          - '--chaosblade-download-url={{ .Values.blade.downloadUrl }}'\n          {{- end }}\n          - '--chaosblade-namespace={{ .Release.Namespace }}'\n          imagePullPolicy: {{ .Values.operator.pullPolicy }}\n          env:\n            - name: WATCH_NAMESPACE\n              value: \"\"\n            - name: POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: OPERATOR_NAME\n              value: \"chaosblade-operator\"\n          ports:\n            - containerPort: 9443\n              protocol: TCP\n          volumeMounts:\n            - mountPath: /tmp/k8s-webhook-server/serving-certs\n              name: cert\n              readOnly: true\n            - mountPath: /opt\n              name: chaosblade\n      volumes:\n        - name: cert\n          secret:\n            defaultMode: 420\n            secretName: chaosblade-webhook-server-cert\n        - name: chaosblade\n          emptyDir: {}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/templates/rbac.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\n  namespace: {{ .Release.Namespace }}\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\nrules:\n  - apiGroups:\n      - ''\n    resources:\n      - pods\n      - pods/exec\n      - configmaps\n      - secrets\n      - services\n      - persistentvolumeclaims\n      - persistentvolumes\n    verbs:\n      - \"*\"\n  - apiGroups:\n      - ''\n    resources:\n      - nodes\n    verbs:\n      - get\n      - list\n      - watch\n      - update\n  - apiGroups:\n      - apps\n    resources:\n      - daemonsets\n      - deployments\n      - statefulsets\n    verbs:\n      - \"*\"\n  - apiGroups:\n      - chaosblade.io\n    resources:\n      - chaosblades\n      - chaosblades/status\n    verbs:\n      - \"*\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\nroleRef:\n  kind: ClusterRole\n  name: chaosblade\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n  - kind: ServiceAccount\n    name: chaosblade\n    namespace: {{ .Release.Namespace }}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/templates/secret.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n{{- $ca := genCA \"chaosblade-webhook-server-ca\" 3650 }}\n{{- $cn := \"chaosblade-webhook-server\" }}\n{{- $dns1 := printf \"%s.%s\" $cn .Release.Namespace }}\n{{- $dns2 := printf \"%s.%s.svc\" $cn .Release.Namespace }}\n{{- $cert := genSignedCert $cn nil (list $dns1 $dns2) 3650 $ca }}\n\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: chaosblade-operator\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: chaosblade-operator\n    chart: \"{{ .Chart.Name }}-{{ .Chart.Version }}\"\n    release: \"{{ .Release.Name }}\"\n    heritage: \"{{ .Release.Service }}\"\nwebhooks:\n  - clientConfig:\n      caBundle: {{ $ca.Cert | b64enc | quote }}\n      service:\n        name: chaosblade-webhook-server\n        namespace: {{ .Release.Namespace }}\n        path: /mutating-pods\n    name: \"{{ .Chart.Name }}.{{ .Release.Namespace }}.svc\"\n    failurePolicy: Ignore\n    rules:\n      - apiGroups:\n          - \"\"\n        apiVersions:\n          - v1\n        operations:\n          - CREATE\n          - UPDATE\n        resources:\n          - pods\n    sideEffects: None\n    admissionReviewVersions: [\"v1beta1\"]\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: chaosblade-webhook-server-cert\n  namespace: {{ .Release.Namespace }}\n  labels:\n    app: chaosblade-operator\n    chart: \"{{ .Chart.Name }}-{{ .Chart.Version }}\"\n    heritage: {{ .Release.Service }}\n    release: {{ .Release.Name }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ $cert.Cert | b64enc | quote }}\n  tls.key: {{ $cert.Key | b64enc | quote }}\n  ca.crt: {{ $ca.Cert | b64enc | quote }}\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/templates/service.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: chaosblade-webhook-server\n  namespace: {{ .Release.Namespace }}\nspec:\n  ports:\n    - port: 443\n      targetPort: 9443\n  selector:\n    name: chaosblade-operator\n"
  },
  {
    "path": "deploy/helm/chaosblade-operator-arm64/values.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Default values for chaosblade.\n\n# chaosblade-operator\noperator:\n  repository: ghcr.io/chaosblade-io/chaosblade-operator-arm64\n  version: 1.8.0\n  # image.pullPolicy: must be Always|IfNotPresent|Never\n  pullPolicy: IfNotPresent\n  # qps of kubernetes client\n  qps: 20\n  reconcileCount: 20\n\nblade:\n  repository: ghcr.io/chaosblade-io/chaosblade-tool-arm64\n  version: 1.8.0\n  pullPolicy: IfNotPresent\n  downloadUrl: \"\"\n\nenv:\n  logLevel: info\n\nwebhook:\n  enable: true\n\ndaemonset:\n  enable: true\n\nremove:\n  blade:\n    interval: 72h\n\nnetwork:\n  host: false\n  dns:\n    policy: ClusterFirst\n"
  },
  {
    "path": "deploy/olm/Makefile",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n.PHONY: build clean\n\nBLADE_VERSION=0.6.0\n\n# Build [OLM](https://github.com/operator-framework/operator-lifecycle-manager)\nbuild:\n\toperator-sdk olm-catalog gen-csv \\\n\t  --operator-name chaosblade-operator \\\n\t  --csv-version $(BLADE_VERSION) \\\n\t  --update-crds --verbose\n\t# Change `olm` keyword to `chaosblade-operator`\n\tsed 's/olm/chaosblade-operator/g' deploy/olm-catalog/olm/olm.package.yaml \\\n      > deploy/olm-catalog/chaosblade-operator/chaosblade-operator.package.yaml\n\n\trm -rf deploy/olm-catalog/olm\n\nclean:\n\trm -rf deploy/olm-catalog"
  },
  {
    "path": "deploy/olm/deploy/crd.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: chaosblades.chaosblade.io\nspec:\n  group: chaosblade.io\n  names:\n    kind: ChaosBlade\n    listKind: ChaosBladeList\n    plural: chaosblades\n    singular: chaosblade\n    shortNames: [blade]\n  scope: Cluster\n  subresources:\n    status: {}\n  validation:\n    openAPIV3Schema:\n      properties:\n        apiVersion:\n          description: 'APIVersion defines the versioned schema of this representation\n            of an object. Servers should convert recognized schemas to the latest\n            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'\n          type: string\n        kind:\n          description: 'Kind is a string value representing the REST resource this\n            object represents. Servers may infer this from the endpoint the client\n            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'\n          type: string\n        metadata:\n          type: object\n        spec:\n          properties:\n            experiments:\n              description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                modifying this file Add custom validation using kubebuilder tags:\n                https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    description: Action is the experiment scenario of the target,\n                      such as delay, load\n                    type: string\n                  desc:\n                    description: Desc is the experiment description\n                    type: string\n                  matchers:\n                    description: Matchers is the experiment rules\n                    items:\n                      properties:\n                        name:\n                          description: Name is the name of flag\n                          type: string\n                        value:\n                          description: 'TODO: Temporarily defined as an array for\n                            all flags Value is the value of flag'\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - name\n                      - value\n                      type: object\n                    type: array\n                  scope:\n                    description: Scope is the area of the experiments, currently support\n                      node, pod and container\n                    type: string\n                  target:\n                    description: Target is the experiment target, such as cpu, network\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                type: object\n              type: array\n          required:\n          - experiments\n          type: object\n        status:\n          properties:\n            expStatuses:\n              description: 'Important: Run \"operator-sdk generate k8s\" to regenerate\n                code after modifying this file Add custom validation using kubebuilder\n                tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    type: string\n                  error:\n                    type: string\n                  resStatuses:\n                    description: ResStatuses is the details of the experiment\n                    items:\n                      properties:\n                        error:\n                          description: experiment error\n                          type: string\n                        id:\n                          description: experiment uid in chaosblade\n                          type: string\n                        kind:\n                          description: Kind\n                          type: string\n                        name:\n                          description: resource name\n                          type: string\n                        nodeName:\n                          description: NodeName\n                          type: string\n                        state:\n                          description: experiment state\n                          type: string\n                        success:\n                          description: success\n                          type: boolean\n                        uid:\n                          description: resource uid\n                          type: string\n                      required:\n                      - state\n                      - kind\n                      - success\n                      type: object\n                    type: array\n                  scope:\n                    description: experiment scope for cache\n                    type: string\n                  state:\n                    description: State is used to describe the experiment result\n                    type: string\n                  success:\n                    description: Success is used to judge the experiment result\n                    type: boolean\n                  target:\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                - success\n                - state\n                type: object\n              type: array\n            phase:\n              description: Phase indicates the state of the experiment   Initial ->\n                Running -> Updating -> Destroying -> Destroyed\n              type: string\n          required:\n          - expStatuses\n          type: object\n  version: v1alpha1\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n"
  },
  {
    "path": "deploy/olm/deploy/crds/chaosblade_v1alpha1_chaosblade_crd.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: chaosblades.chaosblade.io\nspec:\n  group: chaosblade.io\n  names:\n    kind: ChaosBlade\n    listKind: ChaosBladeList\n    plural: chaosblades\n    singular: chaosblade\n  scope: Namespaced\n  subresources:\n    status: {}\n  validation:\n    openAPIV3Schema:\n      properties:\n        apiVersion:\n          description: 'APIVersion defines the versioned schema of this representation\n            of an object. Servers should convert recognized schemas to the latest\n            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'\n          type: string\n        kind:\n          description: 'Kind is a string value representing the REST resource this\n            object represents. Servers may infer this from the endpoint the client\n            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'\n          type: string\n        metadata:\n          type: object\n        spec:\n          properties:\n            experiments:\n              description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                modifying this file Add custom validation using kubebuilder tags:\n                https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    description: Action is the experiment scenario of the target,\n                      such as delay, load\n                    type: string\n                  desc:\n                    description: Desc is the experiment description\n                    type: string\n                  matchers:\n                    description: Matchers is the experiment rules\n                    items:\n                      properties:\n                        name:\n                          description: Name is the name of flag\n                          type: string\n                        value:\n                          description: 'TODO: Temporarily defined as an array for\n                            all flags Value is the value of flag'\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - name\n                      - value\n                      type: object\n                    type: array\n                  scope:\n                    description: Scope is the area of the experiments, currently support\n                      node, pod and container\n                    type: string\n                  target:\n                    description: Target is the experiment target, such as cpu, network\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                type: object\n              type: array\n          required:\n          - experiments\n          type: object\n        status:\n          properties:\n            expStatuses:\n              description: 'Important: Run \"operator-sdk generate k8s\" to regenerate\n                code after modifying this file Add custom validation using kubebuilder\n                tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    type: string\n                  error:\n                    type: string\n                  resStatuses:\n                    description: ResStatuses is the details of the experiment\n                    items:\n                      properties:\n                        error:\n                          description: experiment error\n                          type: string\n                        id:\n                          description: experiment uid in chaosblade\n                          type: string\n                        kind:\n                          description: Kind\n                          type: string\n                        name:\n                          description: resource name\n                          type: string\n                        nodeName:\n                          description: NodeName\n                          type: string\n                        state:\n                          description: experiment state\n                          type: string\n                        success:\n                          description: success\n                          type: boolean\n                        uid:\n                          description: resource uid\n                          type: string\n                      required:\n                      - state\n                      - kind\n                      - success\n                      type: object\n                    type: array\n                  scope:\n                    description: experiment scope for cache\n                    type: string\n                  state:\n                    description: State is used to describe the experiment result\n                    type: string\n                  success:\n                    description: Success is used to judge the experiment result\n                    type: boolean\n                  target:\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                - success\n                - state\n                type: object\n              type: array\n            phase:\n              description: Phase indicates the state of the experiment   Initial ->\n                Running -> Updating -> Destroying -> Destroyed\n              type: string\n          required:\n          - expStatuses\n          type: object\n  version: v1alpha1\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n"
  },
  {
    "path": "deploy/olm/deploy/olm-catalog/chaosblade-operator/0.5.1/chaosblade-operator.v0.5.1.clusterserviceversion.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: operators.coreos.com/v1alpha1\nkind: ClusterServiceVersion\nmetadata:\n  annotations:\n    capabilities: Basic Install\n    categories: Chaos Engineering\n    containerImage: chaosbladeio/chaosblade-operator:0.5.1\n    createdAt: 2020-02-11T15:40:00Z\n    certified: \"false\"\n    support: chaosblade.io\n    repository: https://github.com/chaosblade-io/chaosblade-operator\n    description: A chaos engineering operator for cloud-native on Kubernetes environments.\n    alm-examples: |-\n      [\n        {\n          \"apiVersion\": \"chaosblade.io/v1alpha1\",\n          \"kind\": \"ChaosBlade\",\n          \"metadata\": {\n            \"name\": \"delay-pod-network-by-names\"\n          },\n          \"spec\": {\n            \"experiments\": [\n              {\n                \"scope\": \"pod\",\n                \"target\": \"network\",\n                \"action\": \"delay\",\n                \"desc\": \"delay pod network by names\",\n                \"matchers\": [\n                  {\n                    \"name\": \"names\",\n                    \"value\": [\n                      \"redis-slave-674d68586-jnf7f\"\n                    ]\n                  },\n                  {\n                    \"name\": \"namespace\",\n                    \"value\": [\n                      \"default\"\n                    ]\n                  },\n                  {\n                    \"name\": \"local-port\",\n                    \"value\": [\n                      \"6379\"\n                    ]\n                  },\n                  {\n                    \"name\": \"interface\",\n                    \"value\": [\n                      \"eth0\"\n                    ]\n                  },\n                  {\n                    \"name\": \"time\",\n                    \"value\": [\n                      \"3000\"\n                    ]\n                  },\n                  {\n                    \"name\": \"offset\",\n                    \"value\": [\n                      \"1000\"\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        }\n      ]\n  name: chaosblade-operator.v0.5.1\n  namespace: kube-system\nspec:\n  apiservicedefinitions: {}\n  customresourcedefinitions:\n    owned:\n      - description: Chaos engineering experiment definition\n        displayName: ChaosBlade\n        kind: ChaosBlade\n        name: chaosblades.chaosblade.io\n        version: v1alpha1\n  description: >\n    ## Introduction\n    Chaosblade Operator is a chaos experiments injection tool for cloud-native on kubernetes platform. By defining Kubernetes CRD to manage chaos experiments, each experiment has a very clear execution status. The tool has the characteristics of simple deployment, convenient execution, standardized implementation, and rich experiments. The chaos experimental model in chaosblade is well integrated with Kubernetes, which can realize the reuse of experiments such as basic resources, application services, and containers on the Kubernetes platform, which facilitates the expansion of resource experiments under Kubernetes, and can be executed uniformly through chaosblade cli tool.\n\n    ## Supported experiments (continuously adding ...)\n    The current experimental scenarios involve resources including Node, Pod, and Container. The specific supported experimental scenarios are as follows:\n    * Node:\n        * CPU: specify CPU usage\n        * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n        * Process: specify process Hang, kill process, etc.\n        * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n        * Memory: specify memory usage\n    * Pod:\n        * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n        * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n        * Memory: specify memory usage\n        * Pod: kill pod\n    * Container:\n        * CPU: specify CPU usage\n        * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n        * Process: specify process Hang, kill process, etc.\n        * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n        * Memory: specify memory usage\n        * Container: remove container\n\n    ## Install and uninstall\n    Chaosblade operator can be installed through kubectl or helm, the installation method is as follows:\n\n    Note: For the following `VERSION`, please use the latest version number instead\n\n    ### Helm v2\n    * Download the latest `chaosblade-operator-VERSION-v2.tgz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n    * Install using `helm install --namespace kube-system --name chaosblade-operator chaosblade-operator-VERSION-v2.tgz`\n    * Use `kubectl get pod -l part-of=chaosblade -n kube-system` to check the installation status of the Pod. If both are running, the installation was successful\n    * Use the following command to uninstall, pay attention to the execution order:\n    ```shell script\n    kubectl delete crd chaosblades.chaosblade.io\n    helm del --purge chaosblade-operator\n    ```\n    ### Helm v3\n    * Download the latest `chaosblade-operator-VERSION-v3.tgz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n    * Use `helm install chaosblade-operator chaosblade-operator-VERSION-v3.tgz --namespace kube-system` command to install\n    * Use `kubectl get pod -l part-of=chaosblade -n kube-system` to check the installation status of the Pod. If both are running, the installation was successful\n    * Use the following command to uninstall, pay attention to the execution order:\n    ```shell script\n    kubectl delete crd chaosblades.chaosblade.io\n    helm uninstall chaosblade-operator -n kube-system\n    ```\n    ### Kubectl\n    * Download the latest `chaosblade-operator-yaml-VERSION.tar.gz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n    * After decompression, execute `kubectl apply -f chaosblade-operator-yaml-VERSION/` installation\n    * Use `kubectl get pod -l part-of=chaosblade -n kube-system` to check the installation status of the Pod. If both are running, the installation was successful\n    * Use the following command to uninstall, pay attention to the execution order:\n    ```shell script\n    kubectl delete crd chaosblades.chaosblade.io\n    kubectl delete -f chaosblade-operator-yaml-VERSION/\n    ```\n\n    ## How to use\n    You can run chaos experiments after installing the chaosblade operator. There are three ways to execute chaos experiments:\n    * By configuring yaml file, use kubectl to execute\n    * Executed using chaosblade cli tool\n    * Use Kubernetes API to execute by writing code\n\n    The following uses a specific case to illustrate the use of chaosblade-operator: simulate cn-hangzhou.192.168.0.205 node local port 40690 60% network packet loss.\n\n    ### By configuring the yaml file, use kubectl to execute\n    ```\n    apiVersion: chaosblade.io/v1alpha1\n    kind: ChaosBlade\n    metadata:\n      name: loss-node-network-by-names\n    spec:\n      experiments:\n        - scope: node\n          target: network\n          action: loss\n          desc: \"node network loss\"\n          matchers:\n            - name: names\n              value: [\"cn-hangzhou.192.168.0.205\"]\n            - name: percent\n              value: [\"60\"]\n            - name: interface\n              value: [\"eth0\"]\n            - name: local-port\n              value: [\"40690\"]\n    ```\n    Execute experiment：\n    ```\n    kubectl apply -f loss-node-network-by-names.yaml\n    ```\n    Query the experimental status, the returned information is as follows (spec and other contents are omitted):\n    ```\n    ~ » kubectl get blade loss-node-network-by-names -o json\n    {\n      \"apiVersion\": \"chaosblade.io/v1alpha1\",\n      \"kind\": \"ChaosBlade\",\n      \"metadata\": {\n        \"creationTimestamp\": \"2019-11-04T09:56:36Z\",\n        \"finalizers\": [\n          \"finalizer.chaosblade.io\"\n        ],\n        \"generation\": 1,\n        \"name\": \"loss-node-network-by-names\",\n        \"resourceVersion\": \"9262302\",\n        \"selfLink\": \"/apis/chaosblade.io/v1alpha1/chaosblades/loss-node-network-by-names\",\n        \"uid\": \"63a926dd-fee9-11e9-b3be-00163e136d88\"\n      },\n      \"status\": {\n        \"expStatuses\": [\n        {\n          \"action\": \"loss\",\n          \"resStatuses\": [\n          {\n            \"id\": \"057acaa47ae69363\",\n            \"kind\": \"node\",\n            \"name\": \"cn-hangzhou.192.168.0.205\",\n            \"nodeName\": \"cn-hangzhou.192.168.0.205\",\n            \"state\": \"Success\",\n            \"success\": true,\n            \"uid\": \"e179b30d-df77-11e9-b3be-00163e136d88\"\n          }\n          ],\n          \"scope\": \"node\",\n          \"state\": \"Success\",\n          \"success\": true,\n          \"target\": \"network\"\n        }\n        ],\n        \"phase\": \"Running\"\n      }\n    }\n    ```\n    From the above, you can clearly see the running status of the chaos experiment. Run the following command to stop the experiment:\n    ```\n    kubectl delete -f loss-node-network-by-names.yaml\n    ```\n    Or delete this blade resource directly:\n    ```\n    kubectl delete blade loss-node-network-by-names\n    ```\n    You can also edit the yaml file to update the content of the experiment and the chaosblade operator will complete the update of the experiment. See more examples: [Examples](https://github.com/chaosblade-io/chaosblade-operator/tree/master/examples)\n\n    ### Execute with chaosblade cli tool\n    ```\n    blade create k8s node-network loss --percent 60 --interface eth0 --local-port 40690 --names cn-hangzhou.192.168.0.205 --kubeconfig config\n    ```\n    If the execution fails, a detailed error message is returned; if the execution is successful, the experiment UID is returned:\n    ```\n    {\"code\":200,\"success\":true,\"result\":\"e647064f5f20953c\"}\n    ```\n    You can query the status of the experiment with the following command:\n    ```\n    blade query k8s create e647064f5f20953c --kubeconfig config\n\n    {\n      \"code\": 200,\n      \"success\": true,\n      \"result\": {\n        \"uid\": \"e647064f5f20953c\",\n        \"success\": true,\n        \"error\": \"\",\n        \"statuses\": [\n        {\n          \"id\": \"fa471a6285ec45f5\",\n          \"uid\": \"e179b30d-df77-11e9-b3be-00163e136d88\",\n          \"name\": \"cn-hangzhou.192.168.0.205\",\n          \"state\": \"Success\",\n          \"kind\": \"node\",\n          \"success\": true,\n          \"nodeName\": \"cn-hangzhou.192.168.0.205\"\n        }\n        ]\n      }\n    }\n    ```\n    Destroy experiment:\n    ```\n    blade destroy e647064f5f20953c\n    ```\n    In addition to the above two methods, you can also use the kubernetes client-go api for execution. For details, please refer to: [executor.go](https://github.com/chaosblade-io/chaosblade/blob/master/exec/kubernetes/executor.go) code implementation.\n\n    [Chinese documentation](https://chaosblade-io.gitbook.io/chaosblade-help-zh-cn/blade-create-k8s)\n\n    ## Questions & Suggestions\n    If you encounter problems during installation and use, or suggestions and new features, all projects (including other projects) can be submitted to [Github Issues](https://github.com/chaosblade-io/chaosblade/issues)\n\n    You can also contact us via:\n    * Dingding group: 23177705\n    * Gitter room: [chaosblade community](https://gitter.im/chaosblade-io/community)\n    * Email: chaosblade.io.01@gmail.com\n    * Twitter: [chaosblade.io](https://twitter.com/ChaosbladeI)\n\n    ## Contributions\n    We welcome every issue and PR. Even a punctuation mark, how to participate in the contribution please read the project contributing document, or contact us through the above method.\n\n    ## Open source license\n    Chaosblade-operator is licensed under the Apache 2.0 license.\n\n  displayName: Chaosblade Operator\n  icon:\n    - base64data: iVBORw0KGgoAAAANSUhEUgAABugAAAESCAYAAAAfYlPwAAAACXBIWXMAAC4jAAAuIwF4pT92AAAgAElEQVR4nOzdC3Bk2V3n+f9VS1ULPW3JZmkY22HJhsHA0CE1PZ4gGNjKjhnAD3Kk9gvb0JYKKFUCs5TaBMvEzrKlWpjZmSWgVDMspFRASYMBg8EtoQHMw5TEGg+LgZYw2BjW3ZLx+ynZbuyqkvJsHOU/q7NVKpUeec/533O/n4iMbhzYefNe3cxzz+/8/ydzzgkAAAAAAAAAAACAMLo4zwAAAAAAAAAAAEA4BHQAAAAAAAAAAABAQAR0AAAAAAAAAAAAQEAEdAAAAAAAAAAAAEBABHQAAAAAAAAAAABAQAR0AAAAAAAAAAAAQEAEdAAAAAAAAAAAAEBABHQAAAAAAAAAAABAQAR0AAAAAAAAAAAAQEAEdAAAAAAAAAAAAEBABHQAAAAAAAAAAABAQAR0AAAAAAAAAAAAQEAEdAAAAAAAAAAAAEBABHQAAAAAAAAAAABAQAR0AAAAAAAAAAAAQEAEdAAAAAAAAAAAAEBABHQAAAAAAAAAAABAQAR0AAAAAAAAAAAAQEDdnGx410+eGRKRPhHZ/U/vFCcJCZo7ce3yWKiPdf3kGf9eVwyfxgURGTtx7fKGgWMBAAAAAAAAgKQR0JXMtZNnWiFcRf85ICKDZT8vKJ25kwHDuWu2w7lNH8ydvHZ53sCxAAAAAAAAAEApZM45rnTCNJAbaQvkCONQdssnr12uhDoHxsO5BQ3nqJoDAAAAAAAAgICooEvQF06eqbSFcgRywFNW9d4I4gvN1rFTRs//I//DtctWjw0AAAAAAAAAkkZAl4jPnzwzosGDf/WW/XwAe9hp5fhFgarFPt8M55YM3o/+PFS+6NrlFQPHAgAAAAAAAAClREBXYBoAjOmLUA7Y31ioUOrzzdayswbvy50Kwi+6dnnNwLEAAAAAAAAAQGkR0BXMPzy1p9wE7SuBA7vwxdcuz4c6XU5k3uD9ubPf3Bez3xwAAAAAAAAARJc557gKBfDkyTMDIjJJC0vg0Jbvvna5Euq0PXnyjN/X7ZyxyzR397XLYwaOAwAAAAAAAABKT6igs+9zJ89UtFpuuOznAjiCTW0BG8TnmntBmgvn/hHhHAAAAAAAAACYQkBn1GebwZyvmDtV9nMBHMPYPYH2W/tss8p11tjFmruHcA4AAAAAAAAAzCGgM+azJ88MicgUwRxwbAv3BNx3Tpr7zllqP0s4BwAAAAAAAABGEdAZ8Zmn9pgbLfu5ADogaGvLz5w84+/dQUMXbu4ZhHMAAAAAAAAAYFbmnOPqRLR58kyf7jE3Yaz6Biiy073XLgdpN7nZrHp9zNC5muslnAMAAAAAAAAA06igi2izuc+cDxH6S3sSgM5bDhXOKUv7zi0TzgEAAAAAAACAfQR0EWw0q+b8pP5w6T48kL9gAdWGrdaWqyIyYuA4AAAAAAAAAAB3QIvLwD598syIhnO0swQ679Izr12eCHFeP93cN/IJI9fQ77lXeea1yysGjgUAAAAAAAAAcAdU0AXyKarmgLz5kGoy1Fl2tlpbjjyLcA4AAAAAAAAACqOLS5W/TzX3mlshnANyNfmsa5c3QpziTzUrYU8ZuZwXnnXt8pKB4wAAAAAAAAAAHBAtLnP2yeYeVeeT/pBAfOtfcu3yQKij+OTJM2si0m/gcy9/ybXLFQPHAQAAAAAAAAA4BFpc5uQTtLQEQgrW2vITzdDdQjjnW3qOGDgOAAAAAAAAAMAhUUGXg4+fODOk4dxgch8OsGf9S6+HqZ77+Imd4N1Xz/UaOAsPfen1y/MGjgMAAAAAAAAAcEhU0HXYx07s7Dc3b2QCHyiDYNVzTmTCyL29cC/hHAAAAAAAAAAUFhV0HfSxE2fGRORKMh8IsG/93kDVcx+zUz3nW1sO3Hv98kbk4wAAAAAAAAAAHBEVdB3y0RM7+1KdT+LDAMUxG+pIDVXPTXwZ4RwAAAAAAAAAFBoVdB3wkRNnfEgwWvgPAhTLTiXZlwcIqz5ip3pu+cuvX65EPgYAAAAAAAAAwDF1cQKPh3AOiGY+RDinrFTPBdtvDwAAAAAAAACQH1pcHsOHCeeAmIKFVU5kzMCVnvvH1y8vGTgOAAAAAAAAAMAxEdAd0YcI54CYlp99/fJaiPf/0IkzPpzrN3C1qZ4DAAAAAAAAgETQ4vIICOeA6GYDHoCJ6rlQgSQAAAAAAAAAIH9U0B3SBwnngNg2/f5zIY7hgyfODIjIKQOfmeo5AAAAAAAAAEgIAd0hfODEmQnCOSC6+edev7wR4iCcyISBzzv3XKrnAAAAAAAAACAptLg8oA8096G6WIiDBdIWpHpOjRg4k1TPAQAAAAAAAEBiMucc1/QO3n/izJCIPGb6IPPhWwmuiMiG/lN2/TvKxVeTDcf+m3ze9ct9Id7o/SfO+HDu0RDvtY/l512/XIl8DAAAAAAAAACADqPF5R28v7kH1ZLpg+yMdf2cK63X8wK1EUQxvP/EmSkDB1q26jkL5xwAAAAAAAAA0GEEdPtYP3GmTwOBXrMHeXSb+tl8KLfUzx5X2IfeC4MGzlGwsNzFD+jW+69fDhlIAgAAAAAAAAACIaDbh2tWr1gIJTqlFcrNDzDxj0NwIlbaLAb5u11rtrWNHcxzjwIAAAAAAABAogjobmPtxJkxERk1eXCHtywiswPXL88W7cBhxpCBA1kdCNd2dSzQ++yH9pYAAAAAAAAAkCgCuj080ayeSWFyfM5/judfv7xi4FhQYEYq6IJVlBn4vKvPp+0sAAAAAAAAACSLgG4PTmS24PvO+WBu8gVM8KNDnI0KuiD7zz1uY789ql0BAAAAAAAAIGEEdLs8fuLMZIH3nSOYQ8c9fuLMgIXA+gXXLwcJ6MRGtWCozwoAAAAAAAAAiICArs37ToxXRLLzZg7o4Pwec5NfcX2GSX10nJPMxP5zod7ISRY7oFv/iusztKUFAAAAAAAAgIQR0LVxxWsrt+mDua+8PpPCfnkwqkztLcXG/nPB9toDYF81q/kq5gE90Pbvp/b//CA2RKQ9/F/R/0wWXZ0FPgAAAAAAAIER0Km/OzHuW1v2mziYg/FVc2P/5PoM7SyRKyMBXbCKMhe/xS0T5UDJVLOa3/tySF8Dbf/s9LhkeK//sJrVWv+63Bbk+fHFyqKrU9ELAAAAAACQAwK6ZjjnJ8GK0tpyp2run1A1h3D6DJzrIEH03+20uY2OgA5IXDWrVTSEa/3TygKhU/rPm0GehnerGtotEdoBAAAAAAB0BgFdsVpbrovIyFexPxUCck9N2EbzVYH2VzRQLbj6VddnNiIfA4AOq2a1VhhXuV0Vm3GD+hqV5ufZ1LDOv+YXXZ1qfgAAAAAAgEMqfUD33hPjI2IggDiABd/S8oVM3iOg954Yt1A9tx7qjdzh9nPKA+E7kAgN5cb8wpqCtdA+iF4NGv3rYjWrrev+mbNU1wEAAAAAABxM6QM6J1KEVpFzX319ZszAcaBkjOw/F6wyw8DnZWIbKLDEQ7n9+M96zr+0um6WsA4AAAAAAGB/pQ7o3nNifLIAE2inv+b6TFFacCIxzsbHCTbBSwUdgMOqZrU+DeQmtA1k2fW2hXW+sm5Kwzo6AAAAAAAAALQpbUD3nhPjAzqZZhnhHGKrGDiGkJO6UQP7rwm01x6A46tmNT+OmNRwrpdTuif/nXpR22DOaVDH9xwAAAAAACg9KXNA55qTapYn1E5/LeEcIitTBd27m6F9TMH22gNwdNWsVtE2lqOcxkPx52u0mtWWfbBJUHcw+vc2ogtmNhZd3cLCGQAAclHNakYeQQtrVRe4runLP0uv0XY8H9pJo6KvEV2MNpniZwVSx3MXYiplQPfXJ8YrxifWTv9TwjkYYKDlo4SqoDPwWYPttQfg8HR/Od+u8RSn71j8+btKULc3rcwcaZvsaV9MtmzpWAEAgDmtdutPG69Ws5roOMKPu1YWXX2eS3c0+kzQmsjnuQAoqF3PXcO7PgXPXQiqrBV0lle0EM7BEgsBXVkQ0AEGtbWypGKuswjqVDWrjbRN9FjfGxkAABTTqVagpIHdgoj4oG6evYJvr22/6dbiKcZqQAHtUfHKvQwzShfQ/dWJ8THDq1we+TrCORhiob/I1wXal82JDIV4n30Q0AGG6ADe71V7nuuSq1ZQ5/eomyjDBBErrwEAgAHD+rpSzWoLGtQxH/XUWG1EX4MH+K8AMIjnLhRFqQK6d50Y7zNcPTd33/WZKQPHAdxUpg0AnEhf5ENg1SJghFY0TbGqLihfoThSzWq+mi6p8VDbas0RVl4DAACDdsI6Pw7z+6j5cXCZquq0Y0b7WK33AP81AMbsqngd4V5GUZStgm7C6KTI6n3XZ8YMHAewW+zQqkzYuBuITAf0s3v0oEcY/gHqogakY4uuXtjKYlZeAwCAAurX7hET1aw2lXJQ19ZivMJYDSiualZrb1vJvYxCKk1A95cnxgeMtqna1C8SwBxXoh+3MlULAriVPqTPssrOBN9+ZKVI1XRtK69ZrQkAAIquN7WgTsdqrVCOxXhAQVHxihSVJqBzzVZVFo0MXp+htR1MKlmLSwAlpFVzU9piEXa0qukqWk1nbqykxzbCymsAAJCoVlA3Vs1qfq/g+aJ8TFqMA+mg4hWpK0VAt3Ji3OoKmUtD12eWDBwHsCcCunD4LgDC0zaEswzyTfPjt6VqVvMhnbVWwFcNHAMAAEDefLj1aDWrLReoDbkf5z9q4DgAHB/3MpLWVYbL66vnnE7AG3qtDl2fmTBweoDbsnC/hFKWzwmgSVfhLRHOFcKghnQjZT8RAAAAEbXakDMmAwCgQ5IP6B7rGR8TJ4PW0jlxQjgH+0joSOiABPm9zXQVHv3qi6NXV26Plf1EAAAARNQak81yEQAAOL6kA7q/6BnvM1o9d+n+G7Szg33kc+RzQGp0MuE8F7awrhDSAQAARDdazWorutcbAAA4oqQDOicy6UR6jYVzm/64DJwe4I4I6AjogFT4yYNqVvOLY0a5qIVHSAcAABCfb0O+pvs6AwCAI0g2oPvznvEBETln4FB2m3jgxsyGrUMCbmuVUwOg6HRl75Lum4E0ENIBAADE16t7BRPSAQBwBMkGdE5k1ljlnH+tPnBjhj7dKAwnskEFXZjXnzUXFQDosLZwbpBzmxxCOgAAgPgI6QAAOKIkA7p39oxXnMgpgwHdhIHTAxyYhfsmFAOflYAO6DDCuVLwId1I2U8CAABAZIR0AAAcQaoVdBar1BZedGNmycBxAIXiA/dAx7vCXwaQDsK5UpllMggAACA6QjoAAA4puYDuT3vGJ5xIP9VzwPFZuX9CMNDOM1QQCZTFPOFcafRqSNdX9hMBAAAQGeMyAAAOIamA7v/tGe9zIpMGw7kL//zGzJqBUwQcihNZKlFAF/vFAwzQIdWs5ivpT3E+S2XQaAcFAACAsmFcBgDAAXWndKKcyJSu1rFkXZrHBRROqHDsDirapi5XLn6LS9qAAB1QzWqTIjJagnO5eYTWvKmHlsN+P7pFV583cCwAAABl5sdlE4uuznwYAAD7SCag+5Oe8SGjE3KT33BjZsPAcQDYh79P/6RnPOYpotoHOCYfzojI+YTOo1/ks6aLFNb0tbLo6scaV2jLIT9uav3TvwYSaQnqWyoNHPccAQAA4NguVrPa0qKrs987AAC3kUxA17BZpbb8jTdmKOtHYTWak8KxJ7uDVZY1mpPh/aHeb7d39IwPfeONGR5egCPQzeiL/pu7rnvnLWkQl0t7bA2vWpXJN6vN2oI7X7k8UtDArlf/DkYMHAsAAEDZzdItBgCA20sioHtHz/iI0eqTSQPHABRdyL3Z1mIGdDopTkAHHJIGS7MG21wfxKoe+3xegdxBtQV3/jWp53VEX8Mxj+2QfEulyqKr594eGQAAAPsa9C3oF12d+TEAAPZQ+IDuj3vG+4zu8Tb3L27MMDGEQjOwL5uEDN9dM6CLGfZX2LMSOJKpglV7bWooN2u55Y8GdrOttpEiMiYiEwUJQme1bScAAADimqhmtSlakAMAcKvCB3SuOVEUs+JlL5s6gQUU2r+4MbPx9rj7su14e8943zcF2MtRA7qYhkN91pDe3twjtGPB4zfdmKkU4oMjCN13zuIetHtZ13thtmgTFFrd56vqpnSMYz2o669mtbFFV6fVOAAAuJ3lRVcv5LOF7xag/zrU9rK6YK1Xx8BjBo4FAABTCh3Qvb1nfMBoEDaV2gQ7Sm3VwEB/qG2/pDxZ2HOv0r4nVCL6jLYhRsG1tba0zi/c8a19Cl8hq8Fie1AX+ztzP5MJ7EsIAABwi7ZW3jefk423Jx/VVpexF8UCAGBKV5EvhxOZdCK9rvnvVl7r33Rjht7aSIYT2TBwbwXZVNpX0Bn4rMmtKuz0OQLaFGHfuUu+1WIK4Vw7H9TpXiL360IOi3aq6IweGwAAQEfp+Mx3avAB3TNF5IIuFLOCuTIAAHYpbED3Rz3jFScyaiyca7XcBJLhRJbKEtB9840ZCwHd8B8199ZM6W+IgA4dp60tra0MbudDq/sXXX0i5f02/B56i64+pEGkRYzLAABA6bQtphowFNSNapUfAABQRa6gs7jyZvl/ujGTWms6wEILiiABnVoO+F63Q8UHsA99sLdckXbBh1Y+vDJwLEH4IFJEThtbpe0NVrNayN8QAAAAM9qCuiEjXQ941gUAoE0hA7rlnvExJ3KK6jkgf0baPgbbA8+JrPBd0llU0CEH/h7pN3hifTj1kE6ClI5vqaT7aFoL6RifAQCAUvN7v2nXg7nI54FxGQAAbQoX0C31jPc5kSmD4dylUzdmSrNSHuVx6saMhRaX/t6vhDjpRlp69i/1jCezspCADp1UzWq+Tc95gyfVr0iuLLp6qSvptWrQWkg3YuAYAAAAolt09bHIIV0/3Q0AAHhKESvo/GqbXgPH0W6TzW6ROAutMIIEdCJiJWhnZSGwN4utLVvhHAt1bIZ0vbpnIQAAQOkZCOlocwkAgCpUQHe1Z3zAiZw3WD03Wbkxs2HgFAG5MNL2MUhAV7kx41t6rhv4vINXE6mio4IOnVLNav57YNjYCW2Fc4wD2mhIZ2mhAQEdAACA0pAu1kLcUItvAQAwr1ABndHWlusP3pixuJof6BgjAd2pUFfUSJtL/0qiMpeADh1k7Z4gnNuH7kl3ycjhMBEEAADwdLEWhA5Ws1of1wIAgAIFdG/rGa84kWGDAR2l+UielcDqbeXah86/+t/WM174VpcEdOgErZ4LFtQfAOHcASy6+oSRNsn9un8hAAAAnup4EKvVJYunAAClJ0UK6IxWzy3/yxszSwZOD5Crf3ljxkIFnX8FaVHmROYNfc9M/kHPeKEnlQno0CGWquf83mpjhHMHZmUxExNBAAAATxdrjD3EdQAAoCAB3R80K0gGDRzKblTPoUyWDXzWIAHdv2ruKWmh4sPrFRHa6KLUDFbPjeiKYxyAnqsLBs4VE0EAAABtFl19TUQWIpwTFk4BAErP67Z+Fn6/Z7zP4J4z3oVvuTGzZuA4gCB820cDE+T9v98zPhDi3nMifu+ki3m/zwEN/37P+Ni33JiZNXI8h0LVGzrA0oKYC4uuTvX84VlYaMC4DQAA4Fbz/pkz8Hmh9TgAoPSkCAGda4ZzvQYOpd0mFS0oG9cctJ838LFHQtx/+nmtBHTe1O/1jC99awEXBhDQ4Th037BRIydxddHVLS4aMk/bgXLuAGAX/Z0b0CrfPn0dtuJ3rW0Rws4iEhaTADgE/+x7JfAJ6+cCAQBgPKD7vea+S+cMHMpuE9/abIEHlMa33phZ+b2e8U0DgflYiIDOB2G/1zO+aqi9rj/v87/XM17h+wclY6l6LkibXQBAmqpZbUjbug3pq1PjzPYuFzsL6qpZzf9jXURW9OUDuxX2TwWwm/9eqGa14M++1azWx3cSAKDsTAd02mLOmtVvK2ibOeC4tM1l6NYXuw3+bs/4wLeVr82l6APTVNH2v6SCDsdk5e/9gu7RAcQyVM1qoSti2t/PT/Cvsf/i7VWz2pTBvQ439Nq1+Gu6wXUMQ6vjRjSUq0RY6Navr+G24G5V/w7mqbI7Ht0jd3fFYxH31ZpddHXmOBAjKBvaNdZAAIxXAByGLjDr0//KXuOc1lho5Ta/Je2dHlgstgezAd1be8YrBva72suEvUMCwnBxetPvZSxEqzSDbS690bf2jK+9+MZMYVrFEdDhqKpZbcRI+5t1WlvCgN4IY+Nb3k+rcloT/DsvHrJuGjL6/NI+dmuvrlptq6xaYhFCZ7SFcmOGOjG0G9TXuWpW29TWdj6sm7dziLb4Kh+9vyttFZDWtuE4DgISiNjYcx5hMF4BcAtdeDTU1nr9sOOdA32v6H293BbMr2hwV9p723IFncUVXAsvvjHD4BVlFqM3/V6CBHQvvjGz9tae8WWDg9fzGtKx0hWps1I9x+Ic4OluTvBL8yFrQSf4+V0qltZ13Nnns62yaorJr8OrZrUx/d0q0gR3r17/0WpWW9dn8Fmu/9OC1hFCCwCIivEK0GG6+KjS9gq9qKw1troZzOvCsaW2Tg+lub9NBnS/0zM+aXDD2E0m6FB2L74xs/E7NgKr/t/pGa+8JEBgrm0uLT6UX/mdnnF5SQFCOirocBQ6YLRQsbtMVQFwR/5eHdaWSVNM8BdWe2XVqk58EbruQ3+rJjSYs/b8elj9WrFwvprV5vxiuLLdx3o9R/SaWqx+BAAwXgGOpACLj3pbz5W+m5kuHpvXZ8uk292aC+h+u2e8z2gQNvXSAHteAdZp20cLX+RjIdqx+ADst3vGp4y2sbny2z3j8lLjIR0BHY5oxMiJo7UlcHC9bRP8l3SCn/aXxeQnvq60ha5TXMuntAVzE4m1OmxpVdWVIqjTCatW0Jri9QSAVDFeAfbRtvioaF0eRBePndMwvhXWJVk5ay6gc80vVGuD4nX9ogdKz9C+bKO/1TM++bIAwbl+L53P+32O6Mpv9YwPGN87wtoG1CgGCwHd6qKr09oaOBr/MDXmJ0zYw7HQWqHrBNeySVtZWuz4kodWUJdk4K6TVpOtdr0AgMJivAK0SXDxUXtYt6zj0mTmakwFdP+tZ3yo1VPYmMlvvzHDCgxARHwg9t/s7MsWZC86bXNpNaCTVrWCgeMAOsJQe0sW5wDH06vVdDurNlNvTZK41rUc02tZusUL1aw2pL8LZdyPrBW4T6TSRsx/Fn2OoGIOaKpwHpCA0o9XUG4azE0azVc6xY/Fr2pV3WQKY9MuA8dwk69ScdoOzdBr+dsLsMcTEJIPrIzcoxOLzb**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\n      mediatype: image/png\n  install:\n    spec:\n      clusterPermissions:\n        - rules:\n            - apiGroups:\n                - \"\"\n              resources:\n                - pods\n                - pods/exec\n                - services\n                - endpoints\n                - persistentvolumeclaims\n                - persistentvolumes\n                - events\n                - configmaps\n                - secrets\n                - namespaces\n                - nodes\n              verbs:\n                - '*'\n            - apiGroups:\n                - extensions\n              resources:\n                - deployments\n                - daemonsets\n                - replicasets\n                - ingresses\n              verbs:\n                - '*'\n            - apiGroups:\n                - apps\n              resources:\n                - deployments\n                - daemonsets\n                - replicasets\n                - statefulsets\n              verbs:\n                - '*'\n            - apiGroups:\n                - chaosblade.io\n              resources:\n                - chaosblades\n                - chaosblades/status\n              verbs:\n                - '*'\n          serviceAccountName: chaosblade\n      deployments:\n        - name: chaosblade-operator\n          spec:\n            replicas: 1\n            selector:\n              matchLabels:\n                name: chaosblade-operator\n            strategy: {}\n            template:\n              metadata:\n                labels:\n                  name: chaosblade-operator\n              spec:\n                containers:\n                  - args:\n                      - --blade-version=0.5.0\n                      - --image-repo=chaosbladeio/chaosblade-tool\n                      - --pull-policy=IfNotPresent\n                      - --namespace=kube-system\n                    command:\n                      - chaosblade-operator\n                    env:\n                      - name: WATCH_NAMESPACE\n                        valueFrom:\n                          fieldRef:\n                            fieldPath: metadata.annotations['olm.targetNamespaces']\n                      - name: POD_NAME\n                        valueFrom:\n                          fieldRef:\n                            fieldPath: metadata.name\n                      - name: OPERATOR_NAME\n                        value: chaosblade-operator\n                    image: chaosbladeio/chaosblade-operator:0.5.1\n                    imagePullPolicy: IfNotPresent\n                    name: chaosblade-operator\n                    resources: {}\n                serviceAccountName: chaosblade\n    strategy: deployment\n  installModes:\n    - supported: true\n      type: OwnNamespace\n    - supported: true\n      type: SingleNamespace\n    - supported: false\n      type: MultiNamespace\n    - supported: true\n      type: AllNamespaces\n  keywords:\n    - chaosblade\n    - cloud native\n    - kubernetes\n    - open source\n    - chaos engineering\n  maturity: alpha\n  labels:\n    alm-owner-etcd: chaosblade-operator\n    operated-by: chaosblade-operator\n  selector:\n    matchLabels:\n      alm-owner-etcd: chaosblade-operator\n      operated-by: chaosblade-operator\n  links:\n    - name: ChaosBlade\n      url: https://github.com/chaosblade-io\n    - name: Chaosblade CLI\n      url: https://github.com/chaosblade-io/chaosblade\n    - name: Chaosblade for Basic Resource\n      url: https://github.com/chaosblade-io/chaosblade-exec-os\n    - name: Chaosblade for Docker\n      url: https://github.com/chaosblade-io/chaosblade-exec-docker\n    - name: Chaosblade for Java\n      url: https://github.com/chaosblade-io/chaosblade-exec-jvm\n    - name: Chaosblade for C++\n      url: https://github.com/chaosblade-io/chaosblade-exec-cplus\n    - name: Chaosblade for Kubernetes\n      url: https://github.com/chaosblade-io/chaosblade-operator\n    - name: Documentation(Chinese)\n      url: https://chaosblade-io.gitbook.io/chaosblade-help-zh-cn\n  maintainers:\n    - email: chaosblade.io.01@gmail.com\n      name: ChaosBlade Community\n  minKubeVersion: 1.12.0\n  provider:\n    name: Alibaba Cloud\n  version: 0.5.1\n"
  },
  {
    "path": "deploy/olm/deploy/olm-catalog/chaosblade-operator/0.5.1/chaosblade_v1alpha1_chaosblade_crd.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: chaosblades.chaosblade.io\nspec:\n  group: chaosblade.io\n  names:\n    kind: ChaosBlade\n    listKind: ChaosBladeList\n    plural: chaosblades\n    singular: chaosblade\n  scope: Namespaced\n  subresources:\n    status: {}\n  validation:\n    openAPIV3Schema:\n      properties:\n        apiVersion:\n          description: 'APIVersion defines the versioned schema of this representation\n            of an object. Servers should convert recognized schemas to the latest\n            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'\n          type: string\n        kind:\n          description: 'Kind is a string value representing the REST resource this\n            object represents. Servers may infer this from the endpoint the client\n            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'\n          type: string\n        metadata:\n          type: object\n        spec:\n          properties:\n            experiments:\n              description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                modifying this file Add custom validation using kubebuilder tags:\n                https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    description: Action is the experiment scenario of the target,\n                      such as delay, load\n                    type: string\n                  desc:\n                    description: Desc is the experiment description\n                    type: string\n                  matchers:\n                    description: Matchers is the experiment rules\n                    items:\n                      properties:\n                        name:\n                          description: Name is the name of flag\n                          type: string\n                        value:\n                          description: 'TODO: Temporarily defined as an array for\n                            all flags Value is the value of flag'\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - name\n                      - value\n                      type: object\n                    type: array\n                  scope:\n                    description: Scope is the area of the experiments, currently support\n                      node, pod and container\n                    type: string\n                  target:\n                    description: Target is the experiment target, such as cpu, network\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                type: object\n              type: array\n          required:\n          - experiments\n          type: object\n        status:\n          properties:\n            expStatuses:\n              description: 'Important: Run \"operator-sdk generate k8s\" to regenerate\n                code after modifying this file Add custom validation using kubebuilder\n                tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    type: string\n                  error:\n                    type: string\n                  resStatuses:\n                    description: ResStatuses is the details of the experiment\n                    items:\n                      properties:\n                        error:\n                          description: experiment error\n                          type: string\n                        id:\n                          description: experiment uid in chaosblade\n                          type: string\n                        kind:\n                          description: Kind\n                          type: string\n                        name:\n                          description: resource name\n                          type: string\n                        nodeName:\n                          description: NodeName\n                          type: string\n                        state:\n                          description: experiment state\n                          type: string\n                        success:\n                          description: success\n                          type: boolean\n                        uid:\n                          description: resource uid\n                          type: string\n                      required:\n                      - state\n                      - kind\n                      - success\n                      type: object\n                    type: array\n                  scope:\n                    description: experiment scope for cache\n                    type: string\n                  state:\n                    description: State is used to describe the experiment result\n                    type: string\n                  success:\n                    description: Success is used to judge the experiment result\n                    type: boolean\n                  target:\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                - success\n                - state\n                type: object\n              type: array\n            phase:\n              description: Phase indicates the state of the experiment   Initial ->\n                Running -> Updating -> Destroying -> Destroyed\n              type: string\n          required:\n          - expStatuses\n          type: object\n  version: v1alpha1\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n"
  },
  {
    "path": "deploy/olm/deploy/olm-catalog/chaosblade-operator/0.6.0/chaosblade-operator.v0.6.0.clusterserviceversion.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: operators.coreos.com/v1alpha1\nkind: ClusterServiceVersion\nmetadata:\n  annotations:\n    capabilities: Basic Install\n    categories: Chaos Engineering\n    containerImage: chaosbladeio/chaosblade-operator:0.6.0\n    createdAt: 2020-02-11T15:40:00Z\n    certified: \"false\"\n    support: chaosblade.io\n    repository: https://github.com/chaosblade-io/chaosblade-operator\n    description: A chaos engineering operator for cloud-native on Kubernetes environments.\n    alm-examples: |-\n      [\n        {\n          \"apiVersion\": \"chaosblade.io/v1alpha1\",\n          \"kind\": \"ChaosBlade\",\n          \"metadata\": {\n            \"name\": \"delay-pod-network-by-names\"\n          },\n          \"spec\": {\n            \"experiments\": [\n              {\n                \"scope\": \"pod\",\n                \"target\": \"network\",\n                \"action\": \"delay\",\n                \"desc\": \"delay pod network by names\",\n                \"matchers\": [\n                  {\n                    \"name\": \"names\",\n                    \"value\": [\n                      \"redis-slave-674d68586-jnf7f\"\n                    ]\n                  },\n                  {\n                    \"name\": \"namespace\",\n                    \"value\": [\n                      \"default\"\n                    ]\n                  },\n                  {\n                    \"name\": \"local-port\",\n                    \"value\": [\n                      \"6379\"\n                    ]\n                  },\n                  {\n                    \"name\": \"interface\",\n                    \"value\": [\n                      \"eth0\"\n                    ]\n                  },\n                  {\n                    \"name\": \"time\",\n                    \"value\": [\n                      \"3000\"\n                    ]\n                  },\n                  {\n                    \"name\": \"offset\",\n                    \"value\": [\n                      \"1000\"\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        }\n      ]\n  name: chaosblade-operator.v0.6.0\n  namespace: kube-system\nspec:\n  apiservicedefinitions: {}\n  customresourcedefinitions:\n    owned:\n      - description: Chaos engineering experiment definition\n        displayName: ChaosBlade\n        kind: ChaosBlade\n        name: chaosblades.chaosblade.io\n        version: v1alpha1\n  description: >\n    ## Introduction\n    Chaosblade Operator is a chaos experiments injection tool for cloud-native on kubernetes platform. By defining Kubernetes CRD to manage chaos experiments, each experiment has a very clear execution status. The tool has the characteristics of simple deployment, convenient execution, standardized implementation, and rich experiments. The chaos experimental model in chaosblade is well integrated with Kubernetes, which can realize the reuse of experiments such as basic resources, application services, and containers on the Kubernetes platform, which facilitates the expansion of resource experiments under Kubernetes, and can be executed uniformly through chaosblade cli tool.\n\n    ## Supported experiments (continuously adding ...)\n    The current experimental scenarios involve resources including Node, Pod, and Container. The specific supported experimental scenarios are as follows:\n    * Node:\n        * CPU: specify CPU usage\n        * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n        * Process: specify process Hang, kill process, etc.\n        * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n        * Memory: specify memory usage\n    * Pod:\n        * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n        * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n        * Memory: specify memory usage\n        * Pod: kill pod\n    * Container:\n        * CPU: specify CPU usage\n        * Network: specify network card, port, IP, etc. packet delay, packet loss, packet blocking, packet duplication, packet re-ordering, packet corruption, etc.\n        * Process: specify process Hang, kill process, etc.\n        * Disk: specify the directory disk occupation, disk IO read and write load, etc.\n        * Memory: specify memory usage\n        * Container: remove container\n\n    ## Install and uninstall\n    Chaosblade operator can be installed through kubectl or helm, the installation method is as follows:\n\n    Note: For the following `VERSION`, please use the latest version number instead\n\n    ### Helm v2\n    * Download the latest `chaosblade-operator-VERSION-v2.tgz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n    * Install using `helm install --namespace kube-system --name chaosblade-operator chaosblade-operator-VERSION-v2.tgz`\n    * Use `kubectl get pod -l part-of=chaosblade -n kube-system` to check the installation status of the Pod. If both are running, the installation was successful\n    * Use the following command to uninstall, pay attention to the execution order:\n    ```shell script\n    kubectl delete crd chaosblades.chaosblade.io\n    helm del --purge chaosblade-operator\n    ```\n    ### Helm v3\n    * Download the latest `chaosblade-operator-VERSION-v3.tgz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n    * Use `helm install chaosblade-operator chaosblade-operator-VERSION-v3.tgz --namespace kube-system` command to install\n    * Use `kubectl get pod -l part-of=chaosblade -n kube-system` to check the installation status of the Pod. If both are running, the installation was successful\n    * Use the following command to uninstall, pay attention to the execution order:\n    ```shell script\n    kubectl delete crd chaosblades.chaosblade.io\n    helm uninstall chaosblade-operator -n kube-system\n    ```\n    ### Kubectl\n    * Download the latest `chaosblade-operator-yaml-VERSION.tar.gz` package at [Release](https://github.com/chaosblade-io/chaosblade-operator/releases)\n    * After decompression, execute `kubectl apply -f chaosblade-operator-yaml-VERSION/` installation\n    * Use `kubectl get pod -l part-of=chaosblade -n kube-system` to check the installation status of the Pod. If both are running, the installation was successful\n    * Use the following command to uninstall, pay attention to the execution order:\n    ```shell script\n    kubectl delete crd chaosblades.chaosblade.io\n    kubectl delete -f chaosblade-operator-yaml-VERSION/\n    ```\n\n    ## How to use\n    You can run chaos experiments after installing the chaosblade operator. There are three ways to execute chaos experiments:\n    * By configuring yaml file, use kubectl to execute\n    * Executed using chaosblade cli tool\n    * Use Kubernetes API to execute by writing code\n\n    The following uses a specific case to illustrate the use of chaosblade-operator: simulate cn-hangzhou.192.168.0.205 node local port 40690 60% network packet loss.\n\n    ### By configuring the yaml file, use kubectl to execute\n    ```\n    apiVersion: chaosblade.io/v1alpha1\n    kind: ChaosBlade\n    metadata:\n      name: loss-node-network-by-names\n    spec:\n      experiments:\n        - scope: node\n          target: network\n          action: loss\n          desc: \"node network loss\"\n          matchers:\n            - name: names\n              value: [\"cn-hangzhou.192.168.0.205\"]\n            - name: percent\n              value: [\"60\"]\n            - name: interface\n              value: [\"eth0\"]\n            - name: local-port\n              value: [\"40690\"]\n    ```\n    Execute experiment：\n    ```\n    kubectl apply -f loss-node-network-by-names.yaml\n    ```\n    Query the experimental status, the returned information is as follows (spec and other contents are omitted):\n    ```\n    ~ » kubectl get blade loss-node-network-by-names -o json\n    {\n      \"apiVersion\": \"chaosblade.io/v1alpha1\",\n      \"kind\": \"ChaosBlade\",\n      \"metadata\": {\n        \"creationTimestamp\": \"2019-11-04T09:56:36Z\",\n        \"finalizers\": [\n          \"finalizer.chaosblade.io\"\n        ],\n        \"generation\": 1,\n        \"name\": \"loss-node-network-by-names\",\n        \"resourceVersion\": \"9262302\",\n        \"selfLink\": \"/apis/chaosblade.io/v1alpha1/chaosblades/loss-node-network-by-names\",\n        \"uid\": \"63a926dd-fee9-11e9-b3be-00163e136d88\"\n      },\n      \"status\": {\n        \"expStatuses\": [\n        {\n          \"action\": \"loss\",\n          \"resStatuses\": [\n          {\n            \"id\": \"057acaa47ae69363\",\n            \"kind\": \"node\",\n            \"name\": \"cn-hangzhou.192.168.0.205\",\n            \"nodeName\": \"cn-hangzhou.192.168.0.205\",\n            \"state\": \"Success\",\n            \"success\": true,\n            \"uid\": \"e179b30d-df77-11e9-b3be-00163e136d88\"\n          }\n          ],\n          \"scope\": \"node\",\n          \"state\": \"Success\",\n          \"success\": true,\n          \"target\": \"network\"\n        }\n        ],\n        \"phase\": \"Running\"\n      }\n    }\n    ```\n    From the above, you can clearly see the running status of the chaos experiment. Run the following command to stop the experiment:\n    ```\n    kubectl delete -f loss-node-network-by-names.yaml\n    ```\n    Or delete this blade resource directly:\n    ```\n    kubectl delete blade loss-node-network-by-names\n    ```\n    You can also edit the yaml file to update the content of the experiment and the chaosblade operator will complete the update of the experiment. See more examples: [Examples](https://github.com/chaosblade-io/chaosblade-operator/tree/master/examples)\n\n    ### Execute with chaosblade cli tool\n    ```\n    blade create k8s node-network loss --percent 60 --interface eth0 --local-port 40690 --names cn-hangzhou.192.168.0.205 --kubeconfig config\n    ```\n    If the execution fails, a detailed error message is returned; if the execution is successful, the experiment UID is returned:\n    ```\n    {\"code\":200,\"success\":true,\"result\":\"e647064f5f20953c\"}\n    ```\n    You can query the status of the experiment with the following command:\n    ```\n    blade query k8s create e647064f5f20953c --kubeconfig config\n\n    {\n      \"code\": 200,\n      \"success\": true,\n      \"result\": {\n        \"uid\": \"e647064f5f20953c\",\n        \"success\": true,\n        \"error\": \"\",\n        \"statuses\": [\n        {\n          \"id\": \"fa471a6285ec45f5\",\n          \"uid\": \"e179b30d-df77-11e9-b3be-00163e136d88\",\n          \"name\": \"cn-hangzhou.192.168.0.205\",\n          \"state\": \"Success\",\n          \"kind\": \"node\",\n          \"success\": true,\n          \"nodeName\": \"cn-hangzhou.192.168.0.205\"\n        }\n        ]\n      }\n    }\n    ```\n    Destroy experiment:\n    ```\n    blade destroy e647064f5f20953c\n    ```\n    In addition to the above two methods, you can also use the kubernetes client-go api for execution. For details, please refer to: [executor.go](https://github.com/chaosblade-io/chaosblade/blob/master/exec/kubernetes/executor.go) code implementation.\n\n    [Chinese documentation](https://chaosblade-io.gitbook.io/chaosblade-help-zh-cn/blade-create-k8s)\n\n    ## Questions & Suggestions\n    If you encounter problems during installation and use, or suggestions and new features, all projects (including other projects) can be submitted to [Github Issues](https://github.com/chaosblade-io/chaosblade/issues)\n\n    You can also contact us via:\n    * Dingding group: 23177705\n    * Gitter room: [chaosblade community](https://gitter.im/chaosblade-io/community)\n    * Email: chaosblade.io.01@gmail.com\n    * Twitter: [chaosblade.io](https://twitter.com/ChaosbladeI)\n\n    ## Contributions\n    We welcome every issue and PR. Even a punctuation mark, how to participate in the contribution please read the project contributing document, or contact us through the above method.\n\n    ## Open source license\n    Chaosblade-operator is licensed under the Apache 2.0 license.\n\n  displayName: Chaosblade Operator\n  icon:\n    - base64data: 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**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\n      mediatype: image/png\n  install:\n    spec:\n      clusterPermissions:\n        - rules:\n            - apiGroups:\n                - \"\"\n              resources:\n                - pods\n                - pods/exec\n                - services\n                - endpoints\n                - persistentvolumeclaims\n                - persistentvolumes\n                - events\n                - configmaps\n                - secrets\n                - namespaces\n                - nodes\n              verbs:\n                - '*'\n            - apiGroups:\n                - extensions\n              resources:\n                - deployments\n                - daemonsets\n                - replicasets\n                - ingresses\n              verbs:\n                - '*'\n            - apiGroups:\n                - apps\n              resources:\n                - deployments\n                - daemonsets\n                - replicasets\n                - statefulsets\n              verbs:\n                - '*'\n            - apiGroups:\n                - chaosblade.io\n              resources:\n                - chaosblades\n                - chaosblades/status\n              verbs:\n                - '*'\n          serviceAccountName: chaosblade\n      deployments:\n        - name: chaosblade-operator\n          spec:\n            replicas: 1\n            selector:\n              matchLabels:\n                name: chaosblade-operator\n            strategy: {}\n            template:\n              metadata:\n                labels:\n                  name: chaosblade-operator\n              spec:\n                containers:\n                  - args:\n                      - --blade-version=0.6.0\n                      - --image-repo=chaosbladeio/chaosblade-tool\n                      - --pull-policy=IfNotPresent\n                      - --namespace=kube-system\n                    command:\n                      - chaosblade-operator\n                    env:\n                      - name: WATCH_NAMESPACE\n                        valueFrom:\n                          fieldRef:\n                            fieldPath: metadata.annotations['olm.targetNamespaces']\n                      - name: POD_NAME\n                        valueFrom:\n                          fieldRef:\n                            fieldPath: metadata.name\n                      - name: OPERATOR_NAME\n                        value: chaosblade-operator\n                    image: chaosbladeio/chaosblade-operator:0.6.0\n                    imagePullPolicy: IfNotPresent\n                    name: chaosblade-operator\n                    resources: {}\n                serviceAccountName: chaosblade\n    strategy: deployment\n  installModes:\n    - supported: true\n      type: OwnNamespace\n    - supported: true\n      type: SingleNamespace\n    - supported: false\n      type: MultiNamespace\n    - supported: true\n      type: AllNamespaces\n  keywords:\n    - chaosblade\n    - cloud native\n    - kubernetes\n    - open source\n    - chaos engineering\n  maturity: alpha\n  labels:\n    alm-owner-etcd: chaosblade-operator\n    operated-by: chaosblade-operator\n  selector:\n    matchLabels:\n      alm-owner-etcd: chaosblade-operator\n      operated-by: chaosblade-operator\n  links:\n    - name: ChaosBlade\n      url: https://github.com/chaosblade-io\n    - name: Chaosblade CLI\n      url: https://github.com/chaosblade-io/chaosblade\n    - name: Chaosblade for Basic Resource\n      url: https://github.com/chaosblade-io/chaosblade-exec-os\n    - name: Chaosblade for Docker\n      url: https://github.com/chaosblade-io/chaosblade-exec-docker\n    - name: Chaosblade for Java\n      url: https://github.com/chaosblade-io/chaosblade-exec-jvm\n    - name: Chaosblade for C++\n      url: https://github.com/chaosblade-io/chaosblade-exec-cplus\n    - name: Chaosblade for Kubernetes\n      url: https://github.com/chaosblade-io/chaosblade-operator\n    - name: Documentation(Chinese)\n      url: https://chaosblade-io.gitbook.io/chaosblade-help-zh-cn\n  maintainers:\n    - email: chaosblade.io.01@gmail.com\n      name: ChaosBlade Community\n  minKubeVersion: 1.12.0\n  provider:\n    name: Alibaba Cloud\n  version: 0.6.0\n"
  },
  {
    "path": "deploy/olm/deploy/olm-catalog/chaosblade-operator/0.6.0/chaosblade_v1alpha1_chaosblade_crd.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: chaosblades.chaosblade.io\nspec:\n  group: chaosblade.io\n  names:\n    kind: ChaosBlade\n    listKind: ChaosBladeList\n    plural: chaosblades\n    singular: chaosblade\n  scope: Namespaced\n  subresources:\n    status: {}\n  validation:\n    openAPIV3Schema:\n      properties:\n        apiVersion:\n          description: 'APIVersion defines the versioned schema of this representation\n            of an object. Servers should convert recognized schemas to the latest\n            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'\n          type: string\n        kind:\n          description: 'Kind is a string value representing the REST resource this\n            object represents. Servers may infer this from the endpoint the client\n            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'\n          type: string\n        metadata:\n          type: object\n        spec:\n          properties:\n            experiments:\n              description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                modifying this file Add custom validation using kubebuilder tags:\n                https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    description: Action is the experiment scenario of the target,\n                      such as delay, load\n                    type: string\n                  desc:\n                    description: Desc is the experiment description\n                    type: string\n                  matchers:\n                    description: Matchers is the experiment rules\n                    items:\n                      properties:\n                        name:\n                          description: Name is the name of flag\n                          type: string\n                        value:\n                          description: 'TODO: Temporarily defined as an array for\n                            all flags Value is the value of flag'\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - name\n                      - value\n                      type: object\n                    type: array\n                  scope:\n                    description: Scope is the area of the experiments, currently support\n                      node, pod and container\n                    type: string\n                  target:\n                    description: Target is the experiment target, such as cpu, network\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                type: object\n              type: array\n          required:\n          - experiments\n          type: object\n        status:\n          properties:\n            expStatuses:\n              description: 'Important: Run \"operator-sdk generate k8s\" to regenerate\n                code after modifying this file Add custom validation using kubebuilder\n                tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    type: string\n                  error:\n                    type: string\n                  resStatuses:\n                    description: ResStatuses is the details of the experiment\n                    items:\n                      properties:\n                        error:\n                          description: experiment error\n                          type: string\n                        id:\n                          description: experiment uid in chaosblade\n                          type: string\n                        kind:\n                          description: Kind\n                          type: string\n                        name:\n                          description: resource name\n                          type: string\n                        nodeName:\n                          description: NodeName\n                          type: string\n                        state:\n                          description: experiment state\n                          type: string\n                        success:\n                          description: success\n                          type: boolean\n                        uid:\n                          description: resource uid\n                          type: string\n                      required:\n                      - state\n                      - kind\n                      - success\n                      type: object\n                    type: array\n                  scope:\n                    description: experiment scope for cache\n                    type: string\n                  state:\n                    description: State is used to describe the experiment result\n                    type: string\n                  success:\n                    description: Success is used to judge the experiment result\n                    type: boolean\n                  target:\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                - success\n                - state\n                type: object\n              type: array\n            phase:\n              description: Phase indicates the state of the experiment   Initial ->\n                Running -> Updating -> Destroying -> Destroyed\n              type: string\n          required:\n          - expStatuses\n          type: object\n  version: v1alpha1\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n"
  },
  {
    "path": "deploy/olm/deploy/olm-catalog/chaosblade-operator/chaosblade-operator.package.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nchannels:\n- currentCSV: chaosblade-operator.v0.6.0\n  name: alpha\ndefaultChannel: alpha\npackageName: chaosblade-operator\n"
  },
  {
    "path": "deploy/olm/deploy/operator.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: chaosblade-operator\n  namespace: kube-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      name: chaosblade-operator\n  template:\n    metadata:\n      labels:\n        name: chaosblade-operator\n    spec:\n      serviceAccountName: chaosblade\n      containers:\n        - name: chaosblade-operator\n          # Replace this with the built image name\n          image: chaosbladeio/chaosblade-operator:0.6.0\n          command: [\"chaosblade-operator\"]\n          args:\n            - --blade-version=0.6.0\n            - --image-repo=chaosbladeio/chaosblade-tool\n            - --pull-policy=IfNotPresent\n            - --namespace=kube-system\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: WATCH_NAMESPACE\n              value: \"\"\n            - name: POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: OPERATOR_NAME\n              value: \"chaosblade-operator\"\n"
  },
  {
    "path": "deploy/olm/deploy/role.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRole\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\nrules:\n  - apiGroups:\n    - ''\n    resources:\n    - pods\n    - pods/exec\n    - services\n    - endpoints\n    - persistentvolumeclaims\n    - persistentvolumes\n    - events\n    - configmaps\n    - secrets\n    - namespaces\n    - nodes\n    verbs:\n    - \"*\"\n  - apiGroups:\n    - extensions\n    resources:\n    - deployments\n    - daemonsets\n    - replicasets\n    - ingresses\n    verbs:\n    - \"*\"\n  - apiGroups:\n    - apps\n    resources:\n    - deployments\n    - daemonsets\n    - replicasets\n    - statefulsets\n    verbs:\n    - \"*\"\n  - apiGroups:\n    - chaosblade.io\n    resources:\n    - chaosblades\n    - chaosblades/status\n    verbs:\n    - \"*\"\n"
  },
  {
    "path": "deploy/olm/deploy/role_binding.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRoleBinding\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\nroleRef:\n  kind: ClusterRole\n  name: chaosblade\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n  - kind: ServiceAccount\n    name: chaosblade\n    namespace: kube-system\n"
  },
  {
    "path": "deploy/olm/deploy/service_account.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\n  namespace: kube-system\n"
  },
  {
    "path": "deploy/oss/crd.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: chaosblades.chaosblade.io\nspec:\n  group: chaosblade.io\n  names:\n    kind: ChaosBlade\n    listKind: ChaosBladeList\n    plural: chaosblades\n    singular: chaosblade\n    shortNames: [blade]\n  scope: Cluster\n  subresources:\n    status: {}\n  validation:\n    openAPIV3Schema:\n      properties:\n        apiVersion:\n          description: 'APIVersion defines the versioned schema of this representation\n            of an object. Servers should convert recognized schemas to the latest\n            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'\n          type: string\n        kind:\n          description: 'Kind is a string value representing the REST resource this\n            object represents. Servers may infer this from the endpoint the client\n            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'\n          type: string\n        metadata:\n          type: object\n        spec:\n          properties:\n            experiments:\n              description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                modifying this file Add custom validation using kubebuilder tags:\n                https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    description: Action is the experiment scenario of the target,\n                      such as delay, load\n                    type: string\n                  desc:\n                    description: Desc is the experiment description\n                    type: string\n                  matchers:\n                    description: Matchers is the experiment rules\n                    items:\n                      properties:\n                        name:\n                          description: Name is the name of flag\n                          type: string\n                        value:\n                          description: 'TODO: Temporarily defined as an array for\n                            all flags Value is the value of flag'\n                          items:\n                            type: string\n                          type: array\n                      required:\n                      - name\n                      - value\n                      type: object\n                    type: array\n                  scope:\n                    description: Scope is the area of the experiments, currently support\n                      node, pod and container\n                    type: string\n                  target:\n                    description: Target is the experiment target, such as cpu, network\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                type: object\n              type: array\n          required:\n          - experiments\n          type: object\n        status:\n          properties:\n            expStatuses:\n              description: 'Important: Run \"operator-sdk generate k8s\" to regenerate\n                code after modifying this file Add custom validation using kubebuilder\n                tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n              items:\n                properties:\n                  action:\n                    type: string\n                  error:\n                    type: string\n                  resStatuses:\n                    description: ResStatuses is the details of the experiment\n                    items:\n                      properties:\n                        error:\n                          description: experiment error\n                          type: string\n                        id:\n                          description: experiment uid in chaosblade\n                          type: string\n                        kind:\n                          description: Kind\n                          type: string\n                        name:\n                          description: resource name\n                          type: string\n                        nodeName:\n                          description: NodeName\n                          type: string\n                        state:\n                          description: experiment state\n                          type: string\n                        success:\n                          description: success\n                          type: boolean\n                        uid:\n                          description: resource uid\n                          type: string\n                      required:\n                      - state\n                      - kind\n                      - success\n                      type: object\n                    type: array\n                  scope:\n                    description: experiment scope for cache\n                    type: string\n                  state:\n                    description: State is used to describe the experiment result\n                    type: string\n                  success:\n                    description: Success is used to judge the experiment result\n                    type: boolean\n                  target:\n                    type: string\n                required:\n                - scope\n                - target\n                - action\n                - success\n                - state\n                type: object\n              type: array\n            phase:\n              description: Phase indicates the state of the experiment   Initial ->\n                Running -> Updating -> Destroying -> Destroyed\n              type: string\n          required:\n          - expStatuses\n          type: object\n  version: v1alpha1\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\n"
  },
  {
    "path": "deploy/oss/operator.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: chaosblade-operator\n  namespace: kube-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      name: chaosblade-operator\n  template:\n    metadata:\n      labels:\n        name: chaosblade-operator\n    spec:\n      serviceAccountName: chaosblade\n      containers:\n        - name: chaosblade-operator\n          # Replace this with the built image name\n          image: chaosbladeio/chaosblade-operator:0.6.0\n          command: [\"chaosblade-operator\"]\n          args:\n            - --chaosblade-version=0.6.0\n            - --chaosblade-image-repository=chaosbladeio/chaosblade-tool\n            - --chaosblade-image-pull-policy=IfNotPresent\n            - --chaosblade-namespace=kube-system\n            - --webhook-enable\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: WATCH_NAMESPACE\n              value: \"\"\n            - name: POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: OPERATOR_NAME\n              value: \"chaosblade-operator\"\n          ports:\n            - containerPort: 9443\n              protocol: TCP\n          volumeMounts:\n            - mountPath: /tmp/k8s-webhook-server/serving-certs\n              name: cert\n              readOnly: true\n      volumes:\n        - name: cert\n          secret:\n            defaultMode: 420\n            secretName: chaosblade-webhook-server-cert"
  },
  {
    "path": "deploy/oss/rbac.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\n  namespace: kube-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\nrules:\n  - apiGroups:\n      - ''\n    resources:\n      - pods\n      - pods/exec\n      - services\n      - endpoints\n      - persistentvolumeclaims\n      - persistentvolumes\n      - events\n      - configmaps\n      - secrets\n      - namespaces\n      - nodes\n    verbs:\n      - \"*\"\n  - apiGroups:\n      - extensions\n    resources:\n      - deployments\n      - daemonsets\n      - replicasets\n      - ingresses\n    verbs:\n      - \"*\"\n  - apiGroups:\n      - apps\n    resources:\n      - deployments\n      - daemonsets\n      - replicasets\n      - statefulsets\n    verbs:\n      - \"*\"\n  - apiGroups:\n      - chaosblade.io\n    resources:\n      - chaosblades\n      - chaosblades/status\n    verbs:\n      - \"*\"\n  - apiGroups:\n      - admissionregistration.k8s.io\n    resources:\n      - mutatingwebhookconfigurations\n      - validatingwebhookconfigurations\n    verbs: [\"get\",\"create\",\"update\",\"list\",\"watch\",\"patch\"]\n  - apiGroups:\n      - certificates.k8s.io\n    resources:\n      - certificatesigningrequests\n      - certificatesigningrequests/approval\n    verbs: [\"get\",\"create\",\"update\",\"list\",\"watch\",\"patch\"]\n\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: chaosblade\n  labels:\n    name: chaosblade\nroleRef:\n  kind: ClusterRole\n  name: chaosblade\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n  - kind: ServiceAccount\n    name: chaosblade\n    namespace: kube-system\n"
  },
  {
    "path": "deploy/oss/service.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: v1\nkind: Service\nmetadata:\n  name: chaosblade-webhook-server\n  namespace: kube-system\nspec:\n  ports:\n    - port: 443\n      targetPort: 9443\n  selector:\n    name: chaosblade-operator\n"
  },
  {
    "path": "deploy/oss/webhook-cert-job.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: webhook-certs-job\n  namespace: kube-system\n  labels:\n    app.kubernetes.io/component: webhook-certs-job\nspec:\n  template:\n    metadata:\n      name: webhook-certs-job\n    spec:\n      restartPolicy: Never\n      serviceAccountName: chaosblade\n      containers:\n        - name: webhook-job-certs\n          image: bitnami/kubectl:latest\n          imagePullPolicy: IfNotPresent\n          command:\n            - \"sh\"\n            - \"-c\"\n            - |\n              set -e\n              set -x\n              K8S_SERVICE=chaosblade-webhook-server\n              K8S_SECRET=chaosblade-webhook-server-cert\n              K8S_NAMESPACE=kube-system\n\n              # test if secret already exists\n              certs=$(kubectl get secret ${K8S_SECRET} --ignore-not-found -n ${K8S_NAMESPACE} -o name)\n              if [ \"${certs}\" = \"secret/${K8S_SECRET}\" ];then\n                echo \"Secret already exists\"\n                exit 0\n              fi\n\n              csrName=${K8S_SERVICE}.${K8S_NAMESPACE}\n              tmpdir=$(mktemp -d)\n              echo \"Creating certs in tmpdir ${tmpdir} \"\n\n              cat <<EOF >> ${tmpdir}/csr.conf\n              [req]\n              req_extensions = v3_req\n              distinguished_name = req_distinguished_name\n              [req_distinguished_name]\n              [ v3_req ]\n              basicConstraints = CA:FALSE\n              keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n              extendedKeyUsage = serverAuth\n              subjectAltName = @alt_names\n              [alt_names]\n              DNS.1 = ${K8S_SERVICE}\n              DNS.2 = ${K8S_SERVICE}.${K8S_NAMESPACE}\n              DNS.3 = ${K8S_SERVICE}.${K8S_NAMESPACE}.svc\n              EOF\n\n              openssl genrsa -out ${tmpdir}/server-key.pem 2048\n              openssl req -new -key ${tmpdir}/server-key.pem -subj \"/CN=${K8S_SERVICE}.${K8S_NAMESPACE}.svc\" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf\n\n              # clean-up any previously created CSR for our service. Ignore errors if not present.\n              kubectl delete csr ${csrName} 2>/dev/null || true\n\n              # create server cert/key CSR and send to k8s API\n              cat <<EOF | kubectl create -f -\n              apiVersion: certificates.k8s.io/v1beta1\n              kind: CertificateSigningRequest\n              metadata:\n                name: ${csrName}\n              spec:\n                groups:\n                - system:authenticated\n                request: $(cat ${tmpdir}/server.csr | base64 | tr -d '\\n')\n                usages:\n                - digital signature\n                - key encipherment\n                - server auth\n              EOF\n\n              # verify CSR has been created\n              while true; do\n                  kubectl get csr ${csrName}\n                  if [ \"$?\" -eq 0 ]; then\n                      break\n                  fi\n              done\n\n              # approve and fetch the signed certificate\n              kubectl certificate approve ${csrName}\n\n              # verify certificate has been signed\n              for x in $(seq 10); do\n                  serverCert=$(kubectl get csr ${csrName} -o jsonpath='{.status.certificate}')\n                  if [ -n \"${serverCert}\" ]; then\n                      break\n                  fi\n                  sleep 1\n              done\n\n              if [ -z \"${serverCert}\" ]; then\n                  echo \"ERROR: After approving csr ${csrName}, the signed certificate did not appear on the resource. Giving up after 10 attempts.\" >&2\n                  exit 1\n              fi\n\n              echo ${serverCert} | openssl base64 -d -A -out ${tmpdir}/server-cert.pem\n\n              # create the secret with CA cert and server cert/key\n              kubectl create secret generic ${K8S_SECRET} \\\n                      --from-file=tls.key=${tmpdir}/server-key.pem \\\n                      --from-file=tls.crt=${tmpdir}/server-cert.pem \\\n                      --dry-run -o yaml |\n                  kubectl -n ${K8S_NAMESPACE} apply -f -\n\n"
  },
  {
    "path": "examples/create_services_in_batch.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Batch create services with given name prefix and port count\n# Equivalent to:\n#   blade create k8s service-self create --name-prefix my-service --namespace default \\\n#     --service-count 2000 --ports-per-service 20 --kubeconfig ~/.kube/config\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: create-services-in-batch\nspec:\n  experiments:\n  - scope: service\n    target: self\n    action: create\n    desc: \"create 2000 services with prefix my-service\"\n    matchers:\n    - name: name-prefix\n      value:\n      - \"my-service\"\n    - name: namespace\n      value:\n      - \"default\"\n    - name: service-count\n      value:\n      - \"2000\"\n    - name: ports-per-service\n      value:\n      - \"20\"\n"
  },
  {
    "path": "examples/delay_pod_network_by_names.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: delay-pod-network-by-names\nspec:\n  experiments:\n  - scope: pod\n    target: network\n    action: delay\n    desc: \"delay pod network by names\"\n    matchers:\n    - name: names\n      value:\n      - \"redis-slave-674d68586-jnf7f\"\n    - name: namespace\n      value:\n      - \"default\"\n    - name: local-port\n      value: [\"6379\"]\n    - name: interface\n      value: [\"eth0\"]\n    - name: time\n      value: [\"3000\"]\n    - name: offset\n      value: [\"1000\"]\n"
  },
  {
    "path": "examples/delete_pod_by_labels.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: delete-two-pod-by-labels\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: delete\n    desc: \"delete pod by labels\"\n    matchers:\n    - name: labels\n      value:\n      - \"app=guestbook\"\n    - name: namespace\n      value:\n      - \"default\"\n    - name: evict-count\n      value:\n      - \"2\""
  },
  {
    "path": "examples/delete_pod_by_names.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: delete-pod-by-names\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: delete\n    desc: \"delete pod by names\"\n    matchers:\n    - name: names\n      value:\n      - \"redis-slave-674d68586-86r2t\"\n      - \"frontend-d89756ff7-hmm62\"\n    - name: namespace\n      value:\n      - \"default\"\n"
  },
  {
    "path": "examples/fail_pod_by_labels.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: delete-two-pod-by-labels\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: fail\n    desc: \"inject fail image to  select pod\"\n    matchers:\n    - name: labels\n      value:\n      - \"app=guestbook\"\n    - name: namespace\n      value:\n      - \"default\""
  },
  {
    "path": "examples/increase_container_cpu_load_by_id.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: increase-container-cpu-load-by-id\nspec:\n  experiments:\n  - scope: container\n    target: cpu\n    action: fullload\n    desc: \"increase container cpu load by id\"\n    matchers:\n    - name: container-ids\n      value:\n      - \"2ff814b246f86\"\n    - name: cpu-percent\n      value: [\"100\"]\n      # pod names\n    - name: names\n      value: [\"frontend-d89756ff7-pbnnc\"]\n      # or use pod labels\n"
  },
  {
    "path": "examples/kill_container_process_by_id.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: kill-container-process-by-id\nspec:\n  experiments:\n  - scope: container\n    target: process\n    action: kill\n    desc: \"kill container process by id\"\n    matchers:\n    - name: container-ids\n      value:\n      - \"f1de335b4eeaf\"\n    - name: process\n      value: [\"top\"]\n    - name: names\n      value: [\"frontend-d89756ff7-tl4xl\"]"
  },
  {
    "path": "examples/modify_service_traffic_policy.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Modify service externalTrafficPolicy and internalTrafficPolicy\n# Equivalent to:\n#   blade create k8s service-self modify --name my-service --namespace default \\\n#     --externalTrafficPolicy Local --internalTrafficPolicy Cluster --kubeconfig ~/.kube/config\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: modify-service-traffic-policy\nspec:\n  experiments:\n  - scope: service\n    target: self\n    action: modify\n    desc: \"modify service traffic policy\"\n    matchers:\n    - name: name\n      value:\n      - \"my-service\"\n    - name: namespace\n      value:\n      - \"default\"\n    - name: externalTrafficPolicy\n      value:\n      - \"Local\"\n    - name: internalTrafficPolicy\n      value:\n      - \"Cluster\"\n"
  },
  {
    "path": "examples/node-cpu-load.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: node-cpu-load.yml\nspec:\n  experiments:\n  - scope: node\n    target: cpu\n    action: fullload\n    desc: \"increase node cpu load by names\"\n    matchers:\n    - name: names\n      value:\n      - \"node-example-01\"\n    - name: cpu-percent\n      value:\n      - \"80\"\n"
  },
  {
    "path": "examples/node-disk-load-burn-read.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: node-disk-load-burn-read\nspec:\n  experiments:\n  - scope: node\n    target: disk\n    action : \"burn\"\n    desc: \"increase disk burn by names\"\n    matchers:\n    - name: names\n      value:\n      - \"node-example-01\"\n    - name: path\n      value:\n      - \"/home\"\n    - name: size\n      value:\n      - \"20\"\n    - name: timeout\n      value:\n      - \"100\"\n    - name: read\n      value:\n      - \"true\"\n"
  },
  {
    "path": "examples/node-disk-load-burn-write.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: node-disk-load-burn-write\nspec:\n  experiments:\n  - scope: node\n    target: disk\n    action : \"burn\"\n    desc: \"increase disk burn by names\"\n    matchers:\n    - name: names\n      value:\n      - \"node-example-01\"\n    - name: path\n      value:\n      - \"/home\"\n    - name: size\n      value:\n      - \"20\"\n    - name: timeout\n      value:\n      - \"100\"\n    - name: write\n      value:\n      - \"true\"\n"
  },
  {
    "path": "examples/node-disk-load-fill.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: node-disk-load-fill\nspec:\n  experiments:\n  - scope: node\n    target: disk\n    action : \"fill\"\n    desc: \"increase disk fill by names\"\n    matchers:\n    - name: names\n      value:\n      - \"node-example-01\"\n    - name: path\n      value:\n      - \"/\"\n    - name: size\n      value:\n      - \"2048\"\n"
  },
  {
    "path": "examples/node-mem-load.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: node-mem-load\nspec:\n  experiments:\n  - scope: node\n    target: mem\n    action : \"load\"\n    desc: \"increase node mem load by names\"\n    matchers:\n    - name: names\n      value:\n      - \"node-example-01\"\n    - name: mode\n      value:\n      - \"ram\"\n    - name: mem-percent\n      value:\n      - \"80\"\n"
  },
  {
    "path": "examples/node-network-delay-by-names.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: node-network-delay-by-names\nspec:\n  experiments:\n  - scope: node\n    target: network\n    action: delay\n    desc: \"delay pod network by names\"\n    matchers:\n    - name: names\n      value: [\"node-example-01\"]\n    - name: interface\n      value: [\"eth0\"]\n    - name: time\n      value: [\"3000\"]\n    - name: offset\n      value: [\"1000\"]\n"
  },
  {
    "path": "examples/node-network-loss-by-names.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: node-network-loss-by-names\nspec:\n  experiments:\n  - scope: node\n    target: network\n    action: loss\n    desc: \"node network loss\"\n    matchers:\n    - name: names\n      value: [\"node-example-01\"]\n    - name: percent\n      value: [\"1\"]\n    - name: interface\n      value: [\"eth0\"]\n"
  },
  {
    "path": "examples/pod-bad-resource-size-cpu-mem.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Modify deployment CPU and memory resource limits to simulate bad resource sizing\n# Equivalent to:\n#   blade create k8s pod-pod badresourcesize --namespace default \\\n#     --workload-type deployment --workload-name nginx-app --cpu 1m --mem 128m --kubeconfig ~/.kube/config\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-bad-resource-size-cpu-mem\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: badresourcesize\n    desc: \"modify deployment CPU and memory resource limits\"\n    matchers:\n    - name: namespace\n      value:\n      - \"default\"\n    - name: workload-type\n      value:\n      - \"deployment\"\n    - name: workload-name\n      value:\n      - \"nginx-app\"\n    - name: cpu\n      value:\n      - \"1m\"\n    - name: mem\n      value:\n      - \"128m\"\n"
  },
  {
    "path": "examples/pod-bad-resource-size-cpu.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Modify deployment CPU resource limits to simulate bad resource sizing\n# Equivalent to:\n#   blade create k8s pod-pod badresourcesize --namespace default \\\n#     --workload-type deployment --workload-name nginx-app --cpu 1m --kubeconfig ~/.kube/config\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-bad-resource-size-cpu\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: badresourcesize\n    desc: \"modify deployment CPU resource limits\"\n    matchers:\n    - name: namespace\n      value:\n      - \"default\"\n    - name: workload-type\n      value:\n      - \"deployment\"\n    - name: workload-name\n      value:\n      - \"nginx-app\"\n    - name: cpu\n      value:\n      - \"1m\"\n"
  },
  {
    "path": "examples/pod-bad-resource-size-mem.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Modify deployment memory resource limits to simulate bad resource sizing\n# Equivalent to:\n#   blade create k8s pod-pod badresourcesize --namespace default \\\n#     --workload-type deployment --workload-name nginx-app --mem 128m --kubeconfig ~/.kube/config\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-bad-resource-size-mem\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: badresourcesize\n    desc: \"modify deployment memory resource limits\"\n    matchers:\n    - name: namespace\n      value:\n      - \"default\"\n    - name: workload-type\n      value:\n      - \"deployment\"\n    - name: workload-name\n      value:\n      - \"nginx-app\"\n    - name: mem\n      value:\n      - \"128m\"\n"
  },
  {
    "path": "examples/pod-configmap-delete.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-configmap-delete\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: configmapdelete\n    desc: \"Delete ConfigMap to simulate Pod startup failure\"\n    matchers:\n    - name: labels\n      value:\n      - \"app=my-app\"\n    - name: namespace\n      value:\n      - \"default\"\n    # Optional: specify which ConfigMap to delete.\n    # If not specified, the first non-optional ConfigMap from the Pod spec will be selected.\n    # - name: configmap-name\n    #   value:\n    #   - \"my-configmap\"\n"
  },
  {
    "path": "examples/pod-containercreating-by-pvc-error.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-containercreating-by-pvc-error\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: containercreating\n    desc: \"Make pod stuck in ContainerCreating state by PVC mount failure\"\n    matchers:\n    - name: namespace\n      value:\n      - \"nginx\"\n    # Optional: customize volume mount path in the container\n    # - name: volume-mount-path\n    #   value:\n    #   - \"/mnt/data\"\n"
  },
  {
    "path": "examples/pod-containercreating-disk.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Make pod stuck in ContainerCreating state by cloud disk PVC creation failure.\n# This simulates the scenario where a StorageClass triggers cloud disk provisioning\n# but the provisioner fails (zone mismatch, disk type not supported, quota exceeded).\n#\n# Equivalent to:\n#   blade create k8s pod-pod containercreating-disk --namespace default \\\n#     --storage-class alicloud-disk-ssd --kubeconfig ~/.kube/config\n#\n# Prerequisites:\n# - The specified StorageClass must exist in the cluster\n# - The operator should have RBAC permissions to create/delete pods and PVCs\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-containercreating-disk\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: containercreating-disk\n    desc: \"Make pod stuck in ContainerCreating state by cloud disk PVC creation failure\"\n    matchers:\n    - name: namespace\n      value:\n      - \"default\"\n    - name: storage-class\n      value:\n      - \"alicloud-disk-ssd\"\n    # Optional: PVC storage capacity, default: 20Gi\n    # - name: pv-capacity\n    #   value:\n    #   - \"50Gi\"\n    # Optional: volume mount path in the container, default: /mnt/data\n    # - name: volume-mount-path\n    #   value:\n    #   - \"/mnt/data\"\n"
  },
  {
    "path": "examples/pod-cpu-load-by-names.yml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: cpu-load\nspec:\n  experiments:\n  - scope: pod\n    target: cpu\n    action: fullload\n    desc: \"increase node cpu load by names\"\n    matchers:\n    - name: names\n      value:\n      - \"pod-example-01\"\n    - name: cpu-percent\n      value:\n      - \"80\"\n"
  },
  {
    "path": "examples/pod-delete_by_names.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: delete-pod-by-names\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: delete\n    desc: \"delete pod by names\"\n    matchers:\n    - name: names\n      value:\n      - \"mypod\"\n    - name: namespace\n      value:\n      - \"default\"\n"
  },
  {
    "path": "examples/pod-failedmount-configmap.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Mount a non-existent ConfigMap volume to a deployment to simulate volume mount failure.\n# This will cause Pods to fail to start because the referenced ConfigMap does not exist.\n#\n# Equivalent to:\n#   blade create k8s pod-pod failedmount --namespace default \\\n#     --workload-type deployment --workload-name nginx-app \\\n#     --volume-type configmap --kubeconfig ~/.kube/config\n#\n# Prerequisites:\n# - A deployment named \"nginx-app\" should exist in the target namespace\n# - The operator should have RBAC permissions to get/update deployments\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-failedmount-configmap\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: failedmount\n    desc: \"mount a non-existent configmap volume to deployment\"\n    matchers:\n    - name: namespace\n      value:\n      - \"default\"\n    - name: workload-type\n      value:\n      - \"deployment\"\n    - name: workload-name\n      value:\n      - \"nginx-app\"\n    - name: volume-type\n      value:\n      - \"configmap\"\n"
  },
  {
    "path": "examples/pod-failedmount-pvc.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Mount a non-existent PVC volume to a statefulset to simulate volume mount failure.\n# This will cause Pods to fail to start because the referenced PVC does not exist.\n#\n# Equivalent to:\n#   blade create k8s pod-pod failedmount --namespace default \\\n#     --workload-type statefulset --workload-name redis-app \\\n#     --volume-type pvc --kubeconfig ~/.kube/config\n#\n# Prerequisites:\n# - A statefulset named \"redis-app\" should exist in the target namespace\n# - The operator should have RBAC permissions to get/update statefulsets\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-failedmount-pvc\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: failedmount\n    desc: \"mount a non-existent pvc volume to statefulset\"\n    matchers:\n    - name: namespace\n      value:\n      - \"default\"\n    - name: workload-type\n      value:\n      - \"statefulset\"\n    - name: workload-name\n      value:\n      - \"redis-app\"\n    - name: volume-type\n      value:\n      - \"pvc\"\n"
  },
  {
    "path": "examples/pod-failedmount-secret.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Mount a non-existent Secret volume to a deployment's init containers\n# to simulate volume mount failure at init stage.\n# This will cause Pods to be stuck because the init container cannot start\n# due to the missing Secret volume.\n#\n# Equivalent to:\n#   blade create k8s pod-pod failedmount --namespace default \\\n#     --workload-type deployment --workload-name nginx-app \\\n#     --volume-type secret --with-initcontainer true --kubeconfig ~/.kube/config\n#\n# Prerequisites:\n# - A deployment named \"nginx-app\" with init containers should exist in the target namespace\n# - The operator should have RBAC permissions to get/update deployments\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-failedmount-secret\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: failedmount\n    desc: \"mount a non-existent secret volume to deployment init containers\"\n    matchers:\n    - name: namespace\n      value:\n      - \"default\"\n    - name: workload-type\n      value:\n      - \"deployment\"\n    - name: workload-name\n      value:\n      - \"nginx-app\"\n    - name: volume-type\n      value:\n      - \"secret\"\n    - name: with-initcontainer\n      value:\n      - \"true\"\n"
  },
  {
    "path": "examples/pod-imagepullsecretserror-by-auth-corruption.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-imagepullsecretserror-by-auth-corruption\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: imagepullsecretserror\n    desc: \"Simulate image pull authentication failure by corrupting imagePullSecrets\"\n    matchers:\n    - name: names\n      value:\n      - \"my-app-pod\"\n    - name: namespace\n      value:\n      - \"default\"\n    # Optional: only corrupt a specific imagePullSecret (by default all are corrupted)\n    # - name: secret-name\n    #   value:\n    #   - \"my-registry-secret\"\n"
  },
  {
    "path": "examples/pod-scheduling-failure.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Inject scheduling failure to a deployment by modifying its affinity configuration.\n# This will cause new Pods to remain in Pending state because no node matches the\n# injected unreachable affinity rules.\n#\n# Prerequisites:\n# - A deployment named \"nginx-sf-test\" should exist in the target namespace\n# - The operator should have RBAC permissions to get/update deployments\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-scheduling-failure\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: schedulingfailure\n    desc: \"Inject scheduling failure by modifying deployment affinity\"\n    matchers:\n    - name: namespace\n      value:\n      - \"nginx\"\n    - name: workload-type\n      value:\n      - \"deployment\"\n    - name: workload-name\n      value:\n      - \"nginx-sf-test\"\n    # Optional: affinity type, default is node-affinity\n    # Supported values: node-affinity, node-selector, pod-affinity, pod-anti-affinity\n    # - name: affinity-type\n    #   value:\n    #   - \"node-affinity\"\n"
  },
  {
    "path": "examples/pod-taint-node.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Inject scheduling failure by adding unreachable taint to nodes.\n# This will cause new Pods without matching tolerations to remain in Pending state.\n#\n# Prerequisites:\n# - The operator should have RBAC permissions to get/update nodes\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-taint-node\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: taintnode\n    desc: \"Inject scheduling failure by adding unreachable taint to nodes\"\n    matchers:\n    - name: nodes\n      value:\n      - \"node1\"\n    # Optional: taint effect, default is NoSchedule\n    # Supported values: NoSchedule, NoExecute, PreferNoSchedule\n    # WARNING: NoExecute will evict running pods without matching tolerations\n    # - name: taint-effect\n    #   value:\n    #   - \"NoSchedule\"\n    # Optional: custom taint key and value\n    # - name: taint-key\n    #   value:\n    #   - \"chaosblade.io/unreachable\"\n    # - name: taint-value\n    #   value:\n    #   - \"true\"\n"
  },
  {
    "path": "examples/pod-terminating-by-finalizer.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: pod-terminating-by-finalizer\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: terminating\n    desc: \"Make pod stuck in Terminating state by adding a finalizer that blocks deletion\"\n    matchers:\n    - name: names\n      value:\n      - \"nginx-140862721796080896-0\"\n    - name: namespace\n      value:\n      - \"nginx\"\n"
  },
  {
    "path": "examples/remove_container_by_id.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: remove-container-by-id\nspec:\n  experiments:\n  - scope: container\n    target: container\n    action: remove\n    desc: \"remove container by id\"\n    matchers:\n    - name: container-ids\n      value: [\"072aa6bbf2e2e2\"]\n      # pod name\n    - name: names\n      value: [\"frontend-d89756ff7-szblb\"]\n    - name: namespace\n      value: [\"default\"]\n"
  },
  {
    "path": "examples/tamper_container_dns_by_id.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: tamper-container-dns-by-id\nspec:\n  experiments:\n  - scope: container\n    target: network\n    action: dns\n    desc: \"tamper container dns by id\"\n    matchers:\n    - name: container-ids\n      value:\n      - \"4b25f66580c4\"\n    - name: domain\n      value: [\"www.baidu.com\"]\n    - name: ip\n      value: [\"10.0.0.1\"]\n      # pod names\n    - name: names\n      value: [\"frontend-d89756ff7-trsxf\"]\n      # or use pod labels\n"
  },
  {
    "path": "examples/test-configmap-delete.yaml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\napiVersion: chaosblade.io/v1alpha1\nkind: ChaosBlade\nmetadata:\n  name: test-configmap-delete\nspec:\n  experiments:\n  - scope: pod\n    target: pod\n    action: configmapdelete\n    desc: \"Delete ConfigMap to simulate Pod startup failure\"\n    matchers:\n    - name: names\n      value:\n      - \"my-pod-name\"\n    - name: namespace\n      value:\n      - \"default\"\n    # 可选：指定要删除的 ConfigMap 名称，不指定则自动选择 Pod 依赖的第一个非 optional ConfigMap\n    # - name: configmap-name\n    #   value:\n    #   - \"my-config\"\n"
  },
  {
    "path": "exec/container/application.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n\t\"github.com/chaosblade-io/chaosblade-operator/version\"\n)\n\nvar JvmSpecPathForYaml = \"\"\n\n// getJvmModels returns java experiment specs\nfunc getJvmModels() []spec.ExpModelCommandSpec {\n\tjvmSpecFile := path.Join(chaosblade.OperatorChaosBladeYaml, fmt.Sprintf(\"chaosblade-jvm-spec-%s.yaml\", version.Version))\n\tif JvmSpecPathForYaml != \"\" {\n\t\tjvmSpecFile = fmt.Sprintf(\"%s/chaosblade-jvm-spec-%s.yaml\", JvmSpecPathForYaml, version.Version)\n\t}\n\tmodelCommandSpecs := make([]spec.ExpModelCommandSpec, 0)\n\tmodels, err := util.ParseSpecsToModel(jvmSpecFile, nil)\n\tif err != nil {\n\t\tlogrus.Warningf(\"parse java spec failed, so skip it, %s\", err)\n\t\treturn modelCommandSpecs\n\t}\n\tfor idx := range models.Models {\n\t\tmodelCommandSpecs = append(modelCommandSpecs, &models.Models[idx])\n\t}\n\treturn modelCommandSpecs\n}\n"
  },
  {
    "path": "exec/container/container.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage container\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tcriexec \"github.com/chaosblade-io/chaosblade-exec-cri/exec\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/cpu\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/disk\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/file\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/mem\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/network\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/network/tc\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/process\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/script\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n)\n\ntype ResourceModelSpec struct {\n\tmodel.BaseResourceExpModelSpec\n}\n\n// NewResourceModelSpec returns the container model spec\nfunc NewResourceModelSpec(client *channel.Client) model.ResourceExpModelSpec {\n\tresourceModelSpec := &ResourceModelSpec{\n\t\tmodel.NewBaseResourceExpModelSpec(\"container\", client),\n\t}\n\n\tcontainerSelfModelSpec := criexec.NewContainerCommandSpec()\n\tjavaExpModelSpecs := getJvmModels()\n\n\tsubExpModelCommandSpecs := make([]spec.ExpModelCommandSpec, 0)\n\tsubExpModelCommandSpecs = append(subExpModelCommandSpecs, containerSelfModelSpec)\n\tsubExpModelCommandSpecs = append(subExpModelCommandSpecs, javaExpModelSpecs...)\n\n\tspec.AddExecutorToModelSpec(&model.ExecCommandInPodExecutor{Client: client}, subExpModelCommandSpecs...)\n\n\t// nsexec\n\tosSubExpModelSpecs := model.NewOSSubResourceModelSpec().ExpModels()\n\tspec.AddExecutorToModelSpec(&model.CommonExecutor{Client: client}, osSubExpModelSpecs...)\n\n\tsubExpModelCommandSpecs = append(subExpModelCommandSpecs, osSubExpModelSpecs...)\n\n\tspec.AddFlagsToModelSpec(getResourceFlags, subExpModelCommandSpecs...)\n\tresourceModelSpec.RegisterExpModels(subExpModelCommandSpecs...)\n\taddActionExamples(resourceModelSpec)\n\treturn resourceModelSpec\n}\n\nfunc addActionExamples(modelSpec *ResourceModelSpec) {\n\tfor _, expModelSpec := range modelSpec.ExpModelSpecs {\n\t\tfor _, action := range expModelSpec.Actions() {\n\t\t\tv := interface{}(action)\n\t\t\tswitch v.(type) {\n\t\t\tcase *process.KillProcessActionCommandSpec:\n\t\t\t\taction.SetLongDesc(\"The process scenario in container is the same as the basic resource process scenario\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`\n# Kill the nginx process in the container\nblade create k8s container-process kill --process nginx --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Use blade CLI\n# Specifies the signal and local port to kill the process in the container\nblade create k8s container-process kill --local-port 8080 --signal 15 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\n\t\t\tcase *process.StopProcessActionCommandSpec:\n\t\t\t\taction.SetLongDesc(\"The process scenario in container is the same as the basic resource process scenario\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`\n# Pause the process that contains the \"nginx\" keyword in the container\nblade create k8s container-process stop --process nginx --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Pause the Java process in the container\nblade create k8s container-process stop --process-cmd java --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\n\t\t\tcase *cpu.FullLoadActionCommand:\n\t\t\t\taction.SetLongDesc(\"The CPU load experiment scenario in container is the same as the CPU scenario of basic resources\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`\n# Create a CPU full load experiment in the container\nblade create k8s container-cpu load --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n#Specifies two random kernel's full load in the container\nblade create k8s container-cpu load --cpu-percent 60 --cpu-count 2 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Specifies that the kernel is full load with index 0, 3, and that the kernel's index starts at 0\nblade create k8s container-cpu load --cpu-list 0,3 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Specify the kernel full load of indexes 1-3\nblade create k8s container-cpu load --cpu-list 1-3 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Specified percentage load in the container\nblade create k8s container-cpu load --cpu-percent 60 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\n\t\t\tcase *disk.FillActionSpec:\n\t\t\t\taction.SetLongDesc(\"The disk fill scenario experiment in the container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`\n# Fill the /home directory with 40G of disk space in the container\nblade create k8s container-disk fill --path /home --size 40000 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Fill the /home directory with 80% of the disk space in the container and retains the file handle that populates the disk\nblade create k8s container-disk fill --path /home --percent 80 --retain-handle --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Perform a fixed-size experimental scenario in the container\nblade c k8s container-disk fill --path /home --reserve 1024 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *disk.BurnActionSpec:\n\t\t\t\taction.SetLongDesc(\"Disk read and write IO load experiment in the container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The data of rkB/s, wkB/s and % Util were mainly observed. Perform disk read IO high-load scenarios\nblade create k8s container-disk burn --read --path /home --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Perform disk write IO high-load scenarios\nblade create k8s container-disk burn --write --path /home --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default8\n\n# Read and write IO load scenarios are performed at the same time. Path is not specified. The default is /\nblade create k8s container-disk burn --read --write --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\n\t\t\tcase *mem.MemLoadActionCommand:\n\t\t\t\taction.SetLongDesc(\"The memory fill experiment scenario in container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The execution memory footprint is 50%\nblade create k8s container-mem load --mode ram --mem-percent 50 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# The execution memory footprint is 50%, cache model\nblade create k8s container-mem load --mode cache --mem-percent 50 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# The execution memory footprint is 50%, usage contains buffer/cache\nblade create k8s container-mem load --mode ram --mem-percent 50 --include-buffer-cache --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# The execution memory footprint is 50% for 200 seconds\nblade create k8s container-mem load --mode ram --mem-percent 50 --timeout 200 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# 200M memory is reserved\nblade create k8s container-mem load --mode ram --reserve 200 --rate 100 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *file.FileAppendActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file append experiment scenario in container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file\nblade create k8s container-file append --filepath=/home/logs/nginx.log --content=\"HELL WORLD\" --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file, interval 10 seconds\nblade create k8s container-file append --filepath=/home/logs/nginx.log --content=\"HELL WORLD\" --interval 10 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file, enable base64 encoding\nblade create k8s container-file append --filepath=/home/logs/nginx.log --content=SEVMTE8gV09STEQ= --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# mock interface timeout exception\nblade create k8s container-file append --filepath=/home/logs/nginx.log --content=\"@{DATE:+%Y-%m-%d %H:%M:%S} ERROR invoke getUser timeout [@{RANDOM:100-200}]ms abc  mock exception\" --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *file.FileAddActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file add experiment scenario in container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Create a file named nginx.log in the /home directory\nblade create k8s container-file add --filepath /home/nginx.log --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Create a file named nginx.log in the /home directory with the contents of HELLO WORLD\nblade create k8s container-file add --filepath /home/nginx.log --content \"HELLO WORLD\" --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Create a file named nginx.log in the /temp directory and automatically create directories that don't exist\nblade create k8s container-file add --filepath /temp/nginx.log --auto-create-dir --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Create a directory named /nginx in the /temp directory and automatically create directories that don't exist\nblade create k8s container-file add --directory --filepath /temp/nginx --auto-create-dir --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\n\t\t\tcase *file.FileChmodActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file permission modification scenario in container\")\n\t\t\t\taction.SetExample(`# Modify /home/logs/nginx.log file permissions to 777\nblade create k8s container-file chmod --filepath /home/logs/nginx.log --mark=777 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`)\n\t\t\tcase *file.FileDeleteActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file delete scenario in container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Delete the file /home/logs/nginx.log\nblade create k8s container-file delete --filepath /home/logs/nginx.log --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Force delete the file /home/logs/nginx.log unrecoverable\nblade create k8s container-file delete --filepath /home/logs/nginx.log --force --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *file.FileMoveActionSpec:\n\t\t\t\taction.SetExample(\"The file move scenario in container\")\n\t\t\t\taction.SetExample(`# Move the file /home/logs/nginx.log to /tmp\nblade create k8s container-file delete --filepath /home/logs/nginx.log --target /tmp --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Force Move the file /home/logs/nginx.log to /temp\nblade create k8s container-file delete --filepath /home/logs/nginx.log --target /tmp --force --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Move the file /home/logs/nginx.log to /temp/ and automatically create directories that don't exist\nblade create k8s container-file delete --filepath /home/logs/nginx.log --target /temp --auto-create-dir --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`)\n\t\t\tcase *tc.DelayActionSpec:\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Access to native 8080 and 8081 ports is delayed by 3 seconds, and the delay time fluctuates by 1 second\nblade create k8s container-network delay --time 3000 --offset 1000 --interface eth0 --local-port 8080,8081 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Local access to external 14.215.177.39 machine (ping www.baidu.com obtained IP) port 80 delay of 3 seconds\nblade create k8s container-network delay --time 3000 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Do a 5 second delay for the entire network card eth0, excluding ports 22 and 8000 to 8080\nblade create k8s container-network delay --time 5000 --interface eth0 --exclude-port 22,8000-8080 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *network.DropActionSpec:\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Experimental scenario of network shielding\nblade create k8s container-network drop --source-port 80 --network-traffic in --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *network.DnsActionSpec:\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The domain name www.baidu.com is not accessible\nblade create k8s container-network dns --domain www.baidu.com --ip 10.0.0.0 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *tc.LossActionSpec:\n\t\t\t\taction.SetExample(`# Access to native 8080 and 8081 ports lost 70% of packets\nblade create k8s container-network loss --percent 70 --interface eth0 --local-port 8080,8081 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# The machine accesses external 14.215.177.39 machine (ping www.baidu.com) 80 port packet loss rate 100%\nblade create k8s container-network loss --percent 100 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Do 60% packet loss for the entire network card Eth0, excluding ports 22 and 8000 to 8080\nblade create k8s container-network loss --percent 60 --interface eth0 --exclude-port 22,8000-8080 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Realize the whole network card is not accessible, not accessible time 20 seconds. After executing the following command, the current network is disconnected and restored in 20 seconds. Remember!! Don't forget -timeout parameter\nblade create k8s container-network loss --percent 100 --interface eth0 --timeout 20 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *tc.DuplicateActionSpec:\n\t\t\t\taction.SetExample(`# Specify the network card eth0 and repeat the packet by 10%\nblade create k8s container-network duplicate --percent=10 --interface=eth0 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *tc.CorruptActionSpec:\n\t\t\t\taction.SetExample(`# Access to the specified IP request packet is corrupted, 80% of the time\nblade create k8s container-network corrupt --percent 80 --destination-ip 180.101.49.12 --interface eth0 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *tc.ReorderActionSpec:\n\t\t\t\taction.SetExample(`# Access the specified IP request packet disorder\nblade create k8s container-network reorder --correlation 80 --percent 50 --gap 2 --time 500 --interface eth0 --destination-ip 180.101.49.12 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *network.OccupyActionSpec:\n\t\t\t\taction.SetExample(`#Specify port 8080 occupancy\nblade create k8s container-network occupy --port 8080 --force --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# The machine accesses external 14.215.177.39 machine (ping www.baidu.com) 80 port packet loss rate 100%\nblade create k8s container-network loss --percent 100 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *script.ScriptDelayActionCommand:\n\t\t\t\taction.SetExample(`\n# Add commands to the script \"start0() { sleep 10.000000 ...}\"\nblade create k8s container-script delay --time 10000 --file test.sh --function-name start0 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *script.ScriptExitActionCommand:\n\t\t\t\taction.SetExample(`\n# Add commands to the script \"start0() { echo this-is-error-message; exit 1; ... }\"\nblade create k8s container-script exit --exit-code 1 --exit-message this-is-error-message --file test.sh --function-name start0 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *criexec.RemoveActionCommand:\n\t\t\t\taction.SetExample(`\n# Remove container in pod\nblade create k8s container-container remove --names cart-redis-77 --container-names cart-redis --namespace default --kubeconfig ~/.kube/config`)\n\t\t\tdefault:\n\t\t\t\taction.SetExample(strings.Replace(\n\t\t\t\t\taction.Example(),\n\t\t\t\t\tfmt.Sprintf(\"blade create %s %s\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\tfmt.Sprintf(\"blade create k8s container-%s %s --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\t-1,\n\t\t\t\t))\n\t\t\t\taction.SetExample(strings.Replace(\n\t\t\t\t\taction.Example(),\n\t\t\t\t\tfmt.Sprintf(\"blade c %s %s\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\tfmt.Sprintf(\"blade c k8s container-%s %s --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\t-1,\n\t\t\t\t))\n\t\t\t\taction.SetExample(strings.Replace(\n\t\t\t\t\taction.Example(),\n\t\t\t\t\tfmt.Sprintf(\"blade create docker %s %s\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\tfmt.Sprintf(\"blade create k8s container-%s %s --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\t-1,\n\t\t\t\t))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc getResourceFlags() []spec.ExpFlagSpec {\n\tcoverageFlags := model.GetResourceCoverageFlags()\n\tcommonFlags := model.GetResourceCommonFlags()\n\tcontainerFlags := model.GetContainerFlags()\n\tchaosbladeFlags := model.GetChaosBladeFlags()\n\tnetworkFlags := model.GetNetworkFlags()\n\treturn append(append(append(append(coverageFlags, commonFlags...), containerFlags...), chaosbladeFlags...), networkFlags...)\n}\n"
  },
  {
    "path": "exec/container/controller.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage container\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-exec-cri/exec/container\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\ntype ExpController struct {\n\tmodel.BaseExperimentController\n}\n\nfunc NewExpController(client *channel.Client) model.ExperimentController {\n\treturn &ExpController{\n\t\tmodel.BaseExperimentController{\n\t\t\tClient:            client,\n\t\t\tResourceModelSpec: NewResourceModelSpec(client),\n\t\t},\n\t}\n}\n\nfunc (*ExpController) Name() string {\n\treturn \"container\"\n}\n\n// Create an experiment about container\nfunc (e *ExpController) Create(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response {\n\texpModel := model.ExtractExpModelFromExperimentSpec(expSpec)\n\t// priority: id > name > index\n\tcontainerIdsValue := strings.TrimSpace(expModel.ActionFlags[model.ContainerIdsFlag.Name])\n\tcontainerNamesValue := strings.TrimSpace(expModel.ActionFlags[model.ContainerNamesFlag.Name])\n\tcontainerIndexValue := strings.TrimSpace(expModel.ActionFlags[model.ContainerIndexFlag.Name])\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId).WithField(\"location\", util.GetRunFuncName())\n\tlessParameter := fmt.Sprintf(\"%s|%s|%s\", model.ContainerIdsFlag.Name, model.ContainerNamesFlag.Name, model.ContainerIndexFlag.Name)\n\tif containerIdsValue == \"\" && containerNamesValue == \"\" && containerIndexValue == \"\" {\n\t\terrMsg := spec.ParameterLess.Sprintf(lessParameter)\n\t\tlogrusField.Errorln(errMsg)\n\t\treturn spec.ResponseFailWithResult(spec.ParameterLess, v1alpha1.CreateFailExperimentStatus(errMsg, nil), lessParameter)\n\t}\n\tpods, resp := e.GetMatchedPodResources(ctx, *expModel)\n\tif !resp.Success {\n\t\tlogrusField.Errorf(\"uid: %s, get matched pod resources failed, %v\", experimentId, resp.Err)\n\t\tresp.Result = v1alpha1.CreateFailExperimentStatus(resp.Err, []v1alpha1.ResourceStatus{})\n\t\treturn resp\n\t}\n\tcontainerObjectMetaList, resp := getMatchedContainerMetaList(pods, containerIdsValue, containerNamesValue, containerIndexValue)\n\tif !resp.Success {\n\t\tlogrusField.Errorf(\"get matched container meta list failed, %v\", resp.Err)\n\t\tresp.Result = v1alpha1.CreateFailExperimentStatus(resp.Err, []v1alpha1.ResourceStatus{})\n\t\treturn resp\n\t}\n\tif len(containerObjectMetaList) == 0 {\n\t\t// TODO need to optimize\n\t\terrMsg := spec.ParameterInvalid.Sprintf(\n\t\t\tstrings.Join([]string{model.ContainerIdsFlag.Name, model.ContainerNamesFlag.Name, model.ContainerIndexFlag.Name}, \"|\"),\n\t\t\tstrings.Join([]string{containerIdsValue, containerNamesValue, containerIndexValue}, \"|\"),\n\t\t\t\"cannot find the containers\",\n\t\t)\n\t\tlogrusField.Errorln(errMsg)\n\t\tresponse := spec.ResponseFailWithResult(\n\t\t\tspec.ParameterInvalid,\n\t\t\tv1alpha1.CreateFailExperimentStatus(errMsg, []v1alpha1.ResourceStatus{}),\n\t\t\tstrings.Join([]string{model.ContainerIdsFlag.Name, model.ContainerNamesFlag.Name, model.ContainerIndexFlag.Name}, \"|\"),\n\t\t\tstrings.Join([]string{containerIdsValue, containerNamesValue, containerIndexValue}, \"|\"),\n\t\t\t\"cannot find the containers\",\n\t\t)\n\t\treturn response\n\t}\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn e.Exec(ctx, expModel)\n}\n\n// Destroy\nfunc (e *ExpController) Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) *spec.Response {\n\tlogrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx)).WithField(\"location\", util.GetRunFuncName()).Infof(\"start to destroy\")\n\texpModel := model.ExtractExpModelFromExperimentSpec(expSpec)\n\tstatuses := oldExpStatus.ResStatuses\n\tif statuses == nil {\n\t\treturn spec.ReturnSuccess(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{}))\n\t}\n\tcontainerObjectMetaList := model.ContainerMatchedList{}\n\tfor _, status := range statuses {\n\t\tif !status.Success {\n\t\t\t// does not need to destroy\n\t\t\tcontinue\n\t\t}\n\t\tcontainerObjectMeta := model.ParseIdentifier(status.Identifier)\n\t\tcontainerObjectMeta.Id = status.Id\n\t\tcontainerObjectMetaList = append(containerObjectMetaList, containerObjectMeta)\n\t}\n\tif len(containerObjectMetaList) == 0 {\n\t\treturn spec.ReturnSuccess(v1alpha1.CreateSuccessExperimentStatus(statuses))\n\t}\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn e.Exec(ctx, expModel)\n}\n\n// getMatchedContainerMetaList which will be used in the executor\nfunc getMatchedContainerMetaList(pods []v1.Pod, containerIdsValue, containerNamesValue, containerIndexValue string) (\n\tmodel.ContainerMatchedList, *spec.Response,\n) {\n\tcontainerObjectMetaList := model.ContainerMatchedList{}\n\texpectedContainerIds := strings.Split(containerIdsValue, \",\")\n\texpectedContainerNames := strings.Split(containerNamesValue, \",\")\n\t// priority id>name>index\n\tfor _, pod := range pods {\n\t\tcontainerStatuses := pod.Status.ContainerStatuses\n\t\tif containerStatuses == nil {\n\t\t\tcontinue\n\t\t}\n\t\tvar containerStatusErr error\n\t\tfor _, containerStatus := range containerStatuses {\n\t\t\t// If target container's status is not running, the containerId can not be obtained\n\t\t\t// The container's status should be checked and return err if not running\n\t\t\tvar containerRuntime, containerId string\n\t\t\tcontainerName := containerStatus.Name\n\t\t\tif containerStatus.ContainerID == \"\" {\n\t\t\t\tcontainerStatusErr = errors.New(\"containerId is empty\")\n\t\t\t} else {\n\t\t\t\tcontainerRuntime, containerId = model.TruncateContainerObjectMetaUid(containerStatus.ContainerID)\n\t\t\t\tif containerRuntime == container.DockerRuntime {\n\t\t\t\t\tcontainerId = containerId[:12]\n\t\t\t\t}\n\t\t\t}\n\t\t\tif containerStatus.State.Running == nil {\n\t\t\t\tif containerStatusErr != nil {\n\t\t\t\t\tcontainerStatusErr = errors.New(\"is not running, \" + containerStatusErr.Error())\n\t\t\t\t} else {\n\t\t\t\t\tcontainerStatusErr = errors.New(\"is not running, containerId: \" + containerStatus.ContainerID)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif containerIdsValue != \"\" {\n\t\t\t\tfor _, expectedContainerId := range expectedContainerIds {\n\t\t\t\t\tif expectedContainerId == \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif strings.HasPrefix(expectedContainerId, containerId) {\n\t\t\t\t\t\tif containerStatusErr != nil {\n\t\t\t\t\t\t\treturn containerObjectMetaList, spec.ResponseFailWithFlags(spec.ParameterInvalid,\n\t\t\t\t\t\t\t\tmodel.ContainerIdsFlag.Name, expectedContainerId,\n\t\t\t\t\t\t\t\tfmt.Sprintf(\"container: %s %s\", containerName, containerStatusErr.Error()))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontainerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{\n\t\t\t\t\t\t\tContainerRuntime: containerRuntime,\n\t\t\t\t\t\t\tContainerId:      containerId,\n\t\t\t\t\t\t\tContainerName:    containerName,\n\t\t\t\t\t\t\tPodName:          pod.Name,\n\t\t\t\t\t\t\tNamespace:        pod.Namespace,\n\t\t\t\t\t\t\tNodeName:         pod.Spec.NodeName,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if containerNamesValue != \"\" {\n\t\t\t\tfor _, expectedName := range expectedContainerNames {\n\t\t\t\t\tif expectedName == \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif expectedName == containerName {\n\t\t\t\t\t\t// matched\n\t\t\t\t\t\tif containerStatusErr != nil {\n\t\t\t\t\t\t\treturn containerObjectMetaList, spec.ResponseFailWithFlags(spec.ParameterInvalid,\n\t\t\t\t\t\t\t\tmodel.ContainerNamesFlag.Name, expectedName,\n\t\t\t\t\t\t\t\tfmt.Sprintf(\"container: %s %s\", containerName, containerStatusErr.Error()))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontainerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{\n\t\t\t\t\t\t\tContainerRuntime: containerRuntime,\n\t\t\t\t\t\t\tContainerId:      containerId,\n\t\t\t\t\t\t\tContainerName:    containerName,\n\t\t\t\t\t\t\tPodName:          pod.Name,\n\t\t\t\t\t\t\tNamespace:        pod.Namespace,\n\t\t\t\t\t\t\tNodeName:         pod.Spec.NodeName,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif containerIdsValue == \"\" && containerNamesValue == \"\" && containerIndexValue != \"\" {\n\t\t\tidx, err := strconv.Atoi(containerIndexValue)\n\t\t\tif err != nil {\n\t\t\t\treturn containerObjectMetaList,\n\t\t\t\t\tspec.ResponseFailWithFlags(spec.ParameterIllegal, model.ContainerIndexFlag.Name, containerIndexValue, err)\n\t\t\t}\n\t\t\tif idx > len(containerStatuses)-1 {\n\t\t\t\treturn containerObjectMetaList,\n\t\t\t\t\tspec.ResponseFailWithFlags(spec.ParameterIllegal, model.ContainerIndexFlag.Name, containerIndexValue, \"out of bound\")\n\t\t\t}\n\t\t\t// If target container's status is not running, the containerId can not be obtained\n\t\t\t// The container's status should be checked and return err if not running\n\t\t\tif containerStatuses[idx].ContainerID == \"\" {\n\t\t\t\tcontainerStatusErr = errors.New(\"containerId is empty\")\n\t\t\t}\n\t\t\tif containerStatuses[idx].State.Running == nil {\n\t\t\t\tif containerStatusErr != nil {\n\t\t\t\t\tcontainerStatusErr = errors.New(\"is not running, \" + containerStatusErr.Error())\n\t\t\t\t} else {\n\t\t\t\t\tcontainerStatusErr = errors.New(\"is not running, containerId: \" + containerStatuses[idx].ContainerID)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif containerStatusErr != nil {\n\t\t\t\treturn containerObjectMetaList, spec.ResponseFailWithFlags(spec.ParameterInvalid,\n\t\t\t\t\tmodel.ContainerIndexFlag.Name, idx,\n\t\t\t\t\tfmt.Sprintf(\"container: %s %s\", containerStatuses[idx].Name, containerStatusErr.Error()))\n\t\t\t}\n\t\t\tcontainerRuntime, containerId := model.TruncateContainerObjectMetaUid(containerStatuses[idx].ContainerID)\n\t\t\tif containerRuntime == container.DockerRuntime {\n\t\t\t\tcontainerId = containerId[:12]\n\t\t\t}\n\t\t\tcontainerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{\n\t\t\t\tContainerRuntime: containerRuntime,\n\t\t\t\tContainerId:      containerId,\n\t\t\t\tContainerName:    containerStatuses[idx].Name,\n\t\t\t\tPodName:          pod.Name,\n\t\t\t\tNamespace:        pod.Namespace,\n\t\t\t\tNodeName:         pod.Spec.NodeName,\n\t\t\t})\n\t\t}\n\t}\n\treturn containerObjectMetaList, spec.Success()\n}\n"
  },
  {
    "path": "exec/controller.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage exec\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/container\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/node\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/pod\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/service\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\n// ResourceDispatchedController contains all resource controllers exclude node resource\ntype ResourceDispatchedController struct {\n\tControllers map[string]model.ExperimentController\n}\n\nvar (\n\texecutor *ResourceDispatchedController\n\tonce     sync.Once\n)\n\n// NewDispatcherExecutor initialized when operator starting\nfunc NewDispatcherExecutor(client *channel.Client) *ResourceDispatchedController {\n\tonce.Do(func() {\n\t\texecutor = &ResourceDispatchedController{\n\t\t\tControllers: make(map[string]model.ExperimentController, 0),\n\t\t}\n\t\texecutor.register(\n\t\t\tnode.NewExpController(client),\n\t\t\tpod.NewExpController(client),\n\t\t\tcontainer.NewExpController(client),\n\t\t\tservice.NewExpController(client),\n\t\t)\n\t})\n\treturn executor\n}\n\nfunc (e *ResourceDispatchedController) Name() string {\n\treturn \"dispatch\"\n}\n\nfunc (e *ResourceDispatchedController) Create(bladeName string, expSpec v1alpha1.ExperimentSpec) v1alpha1.ExperimentStatus {\n\tlogrus.WithField(\"experiment\", bladeName).Infof(\"start to create experiment\")\n\tcontroller := e.Controllers[expSpec.Scope]\n\tif controller == nil {\n\t\tlogrus.WithField(\"experiment\", bladeName).WithField(\"scope\", expSpec.Scope).Errorf(\"controller not found\")\n\t\treturn v1alpha1.ExperimentStatus{\n\t\t\tState: \"Error\",\n\t\t\tError: \"can not find the scope controller for creating\",\n\t\t}\n\t}\n\tctx := model.SetExperimentIdToContext(context.Background(), bladeName)\n\tresponse := controller.Create(ctx, expSpec)\n\texperimentStatus := createExperimentStatusByResponse(response)\n\texperimentStatus.Scope = expSpec.Scope\n\texperimentStatus.Target = expSpec.Target\n\texperimentStatus.Action = expSpec.Action\n\treturn experimentStatus\n}\n\nfunc (e *ResourceDispatchedController) Destroy(bladeName string, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) v1alpha1.ExperimentStatus {\n\tcontroller := e.Controllers[expSpec.Scope]\n\tif controller == nil {\n\t\treturn v1alpha1.ExperimentStatus{\n\t\t\tState: \"Error\",\n\t\t\tError: \"can not find the scope controller for destroying\",\n\t\t}\n\t}\n\tif oldExpStatus.ResStatuses == nil ||\n\t\tlen(oldExpStatus.ResStatuses) == 0 {\n\t\t// Still attempt to destroy - the action may have succeeded but status wasn't recorded\n\t\t// (e.g., due to status update conflict). The action's destroy logic should handle\n\t\t// the case where the resource was never modified.\n\t}\n\tctx := spec.SetDestroyFlag(context.Background(), bladeName)\n\tctx = model.SetExperimentIdToContext(ctx, bladeName)\n\tresponse := controller.Destroy(ctx, expSpec, oldExpStatus)\n\tnewExpStatus := createExperimentStatusByResponse(response)\n\tnewExpStatus = validateAndSetNecessaryFields(newExpStatus, oldExpStatus)\n\treturn newExpStatus\n}\n\n// validateAndSetNecessaryFields to resolve status overwriting when the experiment is destroyed.\nfunc validateAndSetNecessaryFields(status v1alpha1.ExperimentStatus, oldExpStatus v1alpha1.ExperimentStatus) v1alpha1.ExperimentStatus {\n\tstatus.Scope = oldExpStatus.Scope\n\tstatus.Target = oldExpStatus.Target\n\tstatus.Action = oldExpStatus.Action\n\tif status.State == \"Error\" {\n\t\tstatus.State = oldExpStatus.State\n\t}\n\tif status.ResStatuses == nil {\n\t\treturn status\n\t}\n\tfor _, s := range status.ResStatuses {\n\t\tfor _, os := range oldExpStatus.ResStatuses {\n\t\t\tif s.Id != os.Id {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif s.State == \"Error\" {\n\t\t\t\ts.State = os.State\n\t\t\t}\n\t\t}\n\t}\n\treturn status\n}\n\n// createExperimentStatusByResponse wraps experiment statuses\nfunc createExperimentStatusByResponse(response *spec.Response) v1alpha1.ExperimentStatus {\n\texperimentStatus := v1alpha1.ExperimentStatus{}\n\tif response.Result != nil {\n\t\texperimentStatus = response.Result.(v1alpha1.ExperimentStatus)\n\t} else {\n\t\tif response.Success {\n\t\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{})\n\t\t} else {\n\t\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(response.Err, []v1alpha1.ResourceStatus{})\n\t\t}\n\t}\n\treturn experimentStatus\n}\n\nfunc (e *ResourceDispatchedController) register(cs ...model.ExperimentController) {\n\tfor _, c := range cs {\n\t\te.Controllers[c.Name()] = c\n\t}\n}\n"
  },
  {
    "path": "exec/model/category.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nconst CategorySystemContainer = \"system_container\"\n"
  },
  {
    "path": "exec/model/context.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tContainerObjectMetaListKey = \"ContainerObjectMetaListKey\"\n\tExperimentIdKey            = \"ExperimentIdKey\"\n)\n\ntype ContainerObjectMeta struct {\n\t// experiment id\n\tId               string\n\tContainerRuntime string\n\tContainerId      string\n\tContainerName    string\n\tPodName          string\n\tNodeName         string\n\tNamespace        string\n}\n\ntype ContainerMatchedList []ContainerObjectMeta\n\n// GetExperimentIdFromContext\nfunc GetExperimentIdFromContext(ctx context.Context) string {\n\texperimentId := ctx.Value(ExperimentIdKey)\n\tif experimentId == nil {\n\t\treturn \"UnknownId\"\n\t}\n\treturn experimentId.(string)\n}\n\n// SetExperimentIdToContext\nfunc SetExperimentIdToContext(ctx context.Context, experimentId string) context.Context {\n\treturn context.WithValue(ctx, ExperimentIdKey, experimentId)\n}\n\n// GetContainerObjectMetaListFromContext returns the matched container list\nfunc GetContainerObjectMetaListFromContext(ctx context.Context) (ContainerMatchedList, error) {\n\tcontainerObjectMetaListValue := ctx.Value(ContainerObjectMetaListKey)\n\tif containerObjectMetaListValue == nil {\n\t\treturn nil, fmt.Errorf(\"less container object meta in context\")\n\t}\n\tcontainerObjectMetaList := containerObjectMetaListValue.(ContainerMatchedList)\n\treturn containerObjectMetaList, nil\n}\n\n// SetContainerObjectMetaListToContext\nfunc SetContainerObjectMetaListToContext(ctx context.Context, containerMatchedList ContainerMatchedList) context.Context {\n\tlogrus.WithField(\"experiment\", GetExperimentIdFromContext(ctx)).Infof(\"set container list: %+v\", containerMatchedList)\n\treturn context.WithValue(ctx, ContainerObjectMetaListKey, containerMatchedList)\n}\n\nfunc (c *ContainerObjectMeta) GetIdentifier() string {\n\tidentifier := fmt.Sprintf(\"%s/%s/%s\", c.Namespace, c.NodeName, c.PodName)\n\tif c.ContainerName != \"\" {\n\t\tidentifier = fmt.Sprintf(\"%s/%s\", identifier, c.ContainerName)\n\t}\n\tif c.ContainerId != \"\" {\n\t\tidentifier = fmt.Sprintf(\"%s/%s\", identifier, c.ContainerId)\n\t}\n\tif c.ContainerRuntime != \"\" {\n\t\tidentifier = fmt.Sprintf(\"%s/%s\", identifier, c.ContainerRuntime)\n\t}\n\treturn identifier\n}\n\n// Namespace/Node/Pod/ContainerName/ContainerId/containerRuntime\nfunc ParseIdentifier(identifier string) ContainerObjectMeta {\n\tss := strings.SplitN(identifier, \"/\", 6)\n\tmeta := ContainerObjectMeta{}\n\tswitch len(ss) {\n\tcase 0:\n\t\treturn meta\n\tcase 1:\n\t\tmeta.Namespace = ss[0]\n\tcase 2:\n\t\tmeta.Namespace = ss[0]\n\t\tmeta.NodeName = ss[1]\n\tcase 3:\n\t\tmeta.Namespace = ss[0]\n\t\tmeta.NodeName = ss[1]\n\t\tmeta.PodName = ss[2]\n\tcase 4:\n\t\tmeta.Namespace = ss[0]\n\t\tmeta.NodeName = ss[1]\n\t\tmeta.PodName = ss[2]\n\t\tmeta.ContainerName = ss[3]\n\tcase 5:\n\t\tmeta.Namespace = ss[0]\n\t\tmeta.NodeName = ss[1]\n\t\tmeta.PodName = ss[2]\n\t\tmeta.ContainerName = ss[3]\n\t\tmeta.ContainerId = ss[4]\n\tcase 6:\n\t\tmeta.Namespace = ss[0]\n\t\tmeta.NodeName = ss[1]\n\t\tmeta.PodName = ss[2]\n\t\tmeta.ContainerName = ss[3]\n\t\tmeta.ContainerId = ss[4]\n\t\tmeta.ContainerRuntime = ss[5]\n\t}\n\treturn meta\n}\n"
  },
  {
    "path": "exec/model/controller.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\ntype ExpController interface {\n\t// controller Name\n\tName() string\n\t// Create\n\tCreate(bladeName string, expSpec v1alpha1.ExperimentSpec) v1alpha1.ExperimentStatus\n\t// Destroy\n\tDestroy(bladeName string, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) v1alpha1.ExperimentStatus\n}\n\ntype ExperimentController interface {\n\t// controller Name\n\tName() string\n\t// Create experiment\n\tCreate(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response\n\t// Destroy\n\tDestroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) *spec.Response\n}\n\ntype BaseExperimentController struct {\n\tClient            *channel.Client\n\tResourceModelSpec ResourceExpModelSpec\n}\n\nfunc (b *BaseExperimentController) Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response {\n\texpModel := ExtractExpModelFromExperimentSpec(expSpec)\n\treturn b.Exec(ctx, expModel)\n}\n\n// Exec gets action executor and execute experiments\nfunc (b *BaseExperimentController) Exec(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\tlogrusField.Infof(\"start to execute: %+v\", expModel)\n\t// get action spec\n\tactionSpec := b.ResourceModelSpec.GetExpActionModelSpec(expModel.Target, expModel.ActionName)\n\tif actionSpec == nil {\n\t\terrMsg := \"can not find the action handler\"\n\t\tlogrusField.WithFields(logrus.Fields{\n\t\t\t\"target\": expModel.Target,\n\t\t\t\"action\": expModel.ActionName,\n\t\t}).Error(errMsg)\n\t\thandler := fmt.Sprintf(\"%s.%s\", expModel.Target, expModel.ActionName)\n\t\terrMsg = spec.HandlerExecNotFound.Sprintf(handler)\n\t\treturn spec.ResponseFailWithResult(spec.HandlerExecNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(errMsg, []v1alpha1.ResourceStatus{}), handler)\n\t}\n\texpModel.ActionPrograms = actionSpec.Programs()\n\t// invoke action executor\n\tresponse := actionSpec.Executor().Exec(experimentId, ctx, expModel)\n\treturn response\n}\n\n// ExtractExpModelFromExperimentSpec convert ExperimentSpec to ExpModel\nfunc ExtractExpModelFromExperimentSpec(experimentSpec v1alpha1.ExperimentSpec) *spec.ExpModel {\n\texpModel := &spec.ExpModel{\n\t\tTarget:      experimentSpec.Target,\n\t\tScope:       experimentSpec.Scope,\n\t\tActionName:  experimentSpec.Action,\n\t\tActionFlags: make(map[string]string, 0),\n\t}\n\tif experimentSpec.Matchers != nil {\n\t\tfor _, flag := range experimentSpec.Matchers {\n\t\t\texpModel.ActionFlags[flag.Name] = strings.Join(flag.Value, \",\")\n\t\t}\n\t}\n\treturn expModel\n}\n\nfunc GetResourceCount(resourceCount int, flags map[string]string) (int, *spec.Response) {\n\tif resourceCount == 0 {\n\t\treturn 0, spec.Success()\n\t}\n\tcount := math.MaxInt32\n\tpercent := 100\n\tvar err error\n\tcountValue := flags[ResourceCountFlag.Name]\n\tif countValue != \"\" {\n\t\tcount, err = strconv.Atoi(countValue)\n\t\tif err != nil {\n\t\t\treturn 0, spec.ResponseFailWithFlags(spec.ParameterIllegal, ResourceCountFlag.Name, countValue, err)\n\t\t}\n\t\tif count == 0 {\n\t\t\treturn 0, spec.ResponseFailWithFlags(spec.ParameterIllegal, ResourceCountFlag.Name, countValue,\n\t\t\t\t\"it must be a positive integer\")\n\t\t}\n\t}\n\tpercentValue := flags[ResourcePercentFlag.Name]\n\tif percentValue != \"\" {\n\t\tpercent, err = strconv.Atoi(percentValue)\n\t\tif err != nil {\n\t\t\treturn 0, spec.ResponseFailWithFlags(spec.ParameterIllegal, ResourcePercentFlag.Name, percentValue, err)\n\t\t}\n\t\tif percent == 0 {\n\t\t\treturn 0, spec.ResponseFailWithFlags(spec.ParameterIllegal, ResourcePercentFlag.Name, percentValue,\n\t\t\t\t\"it must be a positive integer\")\n\t\t}\n\t}\n\tpercentCount := int(math.Round(float64(percent) / 100.0 * float64(resourceCount)))\n\tif count > percentCount {\n\t\tcount = percentCount\n\t}\n\tif count > resourceCount {\n\t\treturn resourceCount, spec.Success()\n\t}\n\treturn count, spec.Success()\n}\n\n// CreateDestroyedStatus returns the ExperimentStatus with destroyed state\nfunc CreateDestroyedStatus(oldExpStatus v1alpha1.ExperimentStatus) v1alpha1.ExperimentStatus {\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tif oldExpStatus.ResStatuses != nil {\n\t\tfor _, status := range oldExpStatus.ResStatuses {\n\t\t\tstatuses = append(statuses, v1alpha1.ResourceStatus{\n\t\t\t\t// experiment uid in chaosblade\n\t\t\t\tId: status.Id,\n\t\t\t\t// experiment state\n\t\t\t\tState:   v1alpha1.DestroyedState,\n\t\t\t\tSuccess: true,\n\t\t\t\t// resource name\n\t\t\t\tKind:       status.Kind,\n\t\t\t\tIdentifier: status.Identifier,\n\t\t\t})\n\t\t}\n\t}\n\treturn v1alpha1.CreateDestroyedExperimentStatus(statuses)\n}\n"
  },
  {
    "path": "exec/model/controller_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGetResourceCount(t *testing.T) {\n\ttype args struct {\n\t\tresourceCount int\n\t\tflags         map[string]string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    int\n\t\twantErr bool\n\t}{\n\t\t{name: \"evict-percent=0\", args: args{10, map[string]string{\"evict-count\": \"\", \"evict-percent\": \"0\"}}, want: 0, wantErr: true},\n\t\t{name: \"evict-percent=10\", args: args{10, map[string]string{\"evict-count\": \"\", \"evict-percent\": \"10\"}}, want: 1, wantErr: false},\n\t\t{name: \"evict-percent=55\", args: args{10, map[string]string{\"evict-count\": \"\", \"evict-percent\": \"55\"}}, want: 6, wantErr: false},\n\t\t{name: \"evict-percent=100\", args: args{10, map[string]string{\"evict-count\": \"\", \"evict-percent\": \"100\"}}, want: 10, wantErr: false},\n\t\t{name: \"evict-count=5,evict-percent==10\", args: args{10, map[string]string{\"evict-count\": \"5\", \"evict-percent\": \"10\"}}, want: 1, wantErr: false},\n\t\t{name: \"evict-count=20\", args: args{10, map[string]string{\"evict-count\": \"20\", \"evict-percent\": \"\"}}, want: 10, wantErr: false},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, resp := GetResourceCount(tt.args.resourceCount, tt.args.flags)\n\t\t\thasErr := resp != nil && !resp.Success\n\t\t\tif hasErr != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetResourceCount() error = %v, wantErr %v\", resp, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !hasErr && got != tt.want {\n\t\t\t\tt.Errorf(\"GetResourceCount() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "exec/model/copy.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"archive/tar\"\n\t\"errors\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n)\n\ntype CopyOptions struct {\n\tDeployOptions\n}\n\nfunc makeTar(srcPath, destPath string, writer io.Writer) error {\n\ttarWriter := tar.NewWriter(writer)\n\tdefer tarWriter.Close()\n\n\tsrcPath = path.Clean(srcPath)\n\tdestPath = path.Clean(destPath)\n\terr := recursiveTar(path.Dir(srcPath), path.Base(srcPath), path.Dir(destPath), path.Base(destPath), tarWriter)\n\treturn err\n}\n\nfunc recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"srcBase\":  srcBase,\n\t\t\"srcFile\":  srcFile,\n\t\t\"destBase\": destBase,\n\t\t\"destFile\": destFile,\n\t}).Debugln(\"recursiveTar\")\n\tsrcPath := path.Join(srcBase, srcFile)\n\tmatchedPaths, err := filepath.Glob(srcPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, fpath := range matchedPaths {\n\t\tstat, err := os.Lstat(fpath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif stat.IsDir() {\n\t\t\tfiles, err := ioutil.ReadDir(fpath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif len(files) == 0 {\n\t\t\t\t// case empty directory\n\t\t\t\thdr, _ := tar.FileInfoHeader(stat, fpath)\n\t\t\t\thdr.Name = destFile\n\t\t\t\tif err := tw.WriteHeader(hdr); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, f := range files {\n\t\t\t\tif err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t} else if stat.Mode()&os.ModeSymlink != 0 {\n\t\t\t// case soft link\n\t\t\thdr, _ := tar.FileInfoHeader(stat, fpath)\n\t\t\ttarget, err := os.Readlink(fpath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\thdr.Linkname = target\n\t\t\thdr.Name = destFile\n\t\t\tif err := tw.WriteHeader(hdr); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\t// case regular file or other file type like pipe\n\t\t\thdr, err := tar.FileInfoHeader(stat, fpath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\thdr.Name = destFile\n\n\t\t\tif err := tw.WriteHeader(hdr); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tf, err := os.Open(fpath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer f.Close()\n\n\t\t\tif _, err := io.Copy(tw, f); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn f.Close()\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (o *CopyOptions) execute(options *channel.ExecOptions) error {\n\tif len(options.PodNamespace) == 0 {\n\t\toptions.PodNamespace = o.Namespace\n\t}\n\n\tif len(o.Container) > 0 {\n\t\toptions.ContainerName = o.Container\n\t}\n\tif err := o.client.Exec(options); err != nil {\n\t\treturn err.(error)\n\t}\n\treturn nil\n}\n\n// DeployToPod copies src file or directory to specify container\nfunc (o *CopyOptions) DeployToPod(experimentId, src, dest string) error {\n\tif len(src) == 0 || len(dest) == 0 {\n\t\treturn errors.New(\"filepath can not be empty\")\n\t}\n\treader, writer := io.Pipe()\n\n\t// strip trailing slash (if any)\n\tif dest != \"/\" && strings.HasSuffix(string(dest[len(dest)-1]), \"/\") {\n\t\tdest = dest[:len(dest)-1]\n\t}\n\n\tgo func() error {\n\t\tdefer writer.Close()\n\t\terr := makeTar(src, dest, writer)\n\t\tif err != nil {\n\t\t\tutil.Errorf(experimentId, util.GetRunFuncName(), spec.K8sExecFailed.Sprintf(\"tar\", err.Error()))\n\t\t\treturn spec.ResponseFailWithFlags(spec.K8sExecFailed, \"tar\", err)\n\t\t}\n\t\treturn nil\n\t}()\n\tcmdArr := []string{\"tar\", \"--no-same-permissions\", \"--no-same-owner\", \"-xmf\", \"-\"}\n\tdestDir := path.Dir(dest)\n\tif len(destDir) > 0 {\n\t\tcmdArr = append(cmdArr, \"-C\", destDir)\n\t}\n\toptions := &channel.ExecOptions{\n\t\tStreamOptions: channel.StreamOptions{\n\t\t\tIOStreams: channel.IOStreams{\n\t\t\t\tIn: reader,\n\t\t\t},\n\t\t\tStdin: true,\n\t\t\tErrDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn errors.New(string(bytes))\n\t\t\t},\n\t\t\tOutDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\tPodName:       o.PodName,\n\t\tPodNamespace:  o.Namespace,\n\t\tContainerName: o.Container,\n\t\tCommand:       cmdArr,\n\t\tIgnoreOutput:  true,\n\t}\n\treturn o.execute(options)\n}\n"
  },
  {
    "path": "exec/model/deploy.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"errors\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n)\n\ntype DeployMode interface {\n\tDeployToPod(experimentId, src, dest string) error\n}\n\ntype DeployOptions struct {\n\tContainer string\n\tNamespace string\n\tPodName   string\n\tclient    *channel.Client\n}\n\n// CheckFileExists return nil if dest file exists\nfunc (o *DeployOptions) CheckFileExists(dest string) error {\n\toptions := &channel.ExecOptions{\n\t\tStreamOptions: channel.StreamOptions{\n\t\t\tErrDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn errors.New(string(bytes))\n\t\t\t},\n\t\t\tOutDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\tPodNamespace:  o.Namespace,\n\t\tPodName:       o.PodName,\n\t\tContainerName: o.Container,\n\t\tCommand:       []string{\"test\", \"-e\", dest},\n\t\tIgnoreOutput:  true,\n\t}\n\tif err := o.client.Exec(options); err != nil {\n\t\treturn err.(error)\n\t}\n\treturn nil\n}\n\nfunc (o *DeployOptions) CreateDir(dir string) error {\n\tif len(dir) == 0 {\n\t\treturn errors.New(\"illegal directory name\")\n\t}\n\toptions := &channel.ExecOptions{\n\t\tStreamOptions: channel.StreamOptions{\n\t\t\tErrDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn errors.New(string(bytes))\n\t\t\t},\n\t\t\tOutDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\tPodName:       o.PodName,\n\t\tPodNamespace:  o.Namespace,\n\t\tContainerName: o.Container,\n\t\tCommand:       []string{\"mkdir\", \"-p\", dir},\n\t\tIgnoreOutput:  true,\n\t}\n\tif err := o.client.Exec(options); err != nil {\n\t\treturn err.(error)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "exec/model/download.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n)\n\n/*\n.\n├── bin\n├── blade\n├── lib.tar.gz\n└── yaml.tar.gz\n*/\n\nconst (\n\tbin   = \"bin\"\n\tblade = \"blade\"\n\tlib   = \"lib.tar.gz\"\n\tyaml  = \"yaml.tar.gz\"\n)\n\ntype DownloadOptions struct {\n\tDeployOptions\n\turl string\n}\n\nfunc (d *DownloadOptions) DeployToPod(experimentId, src, dest string) error {\n\tif len(src) == 0 {\n\t\treturn errors.New(\"the chaosblade downloaded address is empty\")\n\t}\n\turl := d.getUrl(src)\n\t// code=$( curl -s -L -w %{http_code} -o /opt/yaml.tar.gz https://xxx/temp/yaml.tar.gz ) && [ $code = 200 ] && tar -zxf /opt/yaml.tar.gz -C /opt && echo $code || echo $code\n\tvar command []string\n\tisTarFile := strings.HasSuffix(url, \"tar.gz\")\n\tif isTarFile {\n\t\tdest = fmt.Sprintf(\"%s.%s\", dest, \"tar.gz\")\n\t}\n\tcommand = []string{\"sh\", \"-c\", \"curl -s -L -w %{http_code} \" + fmt.Sprintf(\"-o %s %s && chmod 755 %s\", dest, url, dest)}\n\toptions := &channel.ExecOptions{\n\t\tStreamOptions: channel.StreamOptions{\n\t\t\tErrDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn string(bytes)\n\t\t\t},\n\t\t\tOutDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn string(bytes)\n\t\t\t},\n\t\t},\n\t\tPodNamespace:  d.Namespace,\n\t\tPodName:       d.PodName,\n\t\tContainerName: d.Container,\n\t\tCommand:       command,\n\t\tIgnoreOutput:  false,\n\t}\n\tstatusCode := d.client.Exec(options).(string)\n\tlogrus.WithFields(\n\t\tlogrus.Fields{\n\t\t\t\"experimentId\": experimentId,\n\t\t\t\"pod\":          d.PodName,\n\t\t\t\"container\":    d.Container,\n\t\t\t\"command\":      command,\n\t\t\t\"result\":       statusCode,\n\t\t},\n\t).Infof(\"download to the target container\")\n\tcode, err := strconv.Atoi(strings.TrimSpace(statusCode))\n\tif err != nil {\n\t\treturn errors.New(statusCode)\n\t}\n\tif code != 200 {\n\t\treturn fmt.Errorf(\"response code is %d\", code)\n\t}\n\tif isTarFile {\n\t\treturn d.uncompress(experimentId, dest)\n\t}\n\treturn nil\n}\n\nfunc (d *DownloadOptions) uncompress(experimentId, file string) error {\n\tdir := path.Dir(file)\n\tcommand := []string{\"/bin/sh\", \"-c\", fmt.Sprintf(\"tar -zxf %s -C %s && chmod -R 755 %s\", file, dir, dir)}\n\toptions := &channel.ExecOptions{\n\t\tStreamOptions: channel.StreamOptions{\n\t\t\tErrDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn string(bytes)\n\t\t\t},\n\t\t\tOutDecoder: func(bytes []byte) interface{} {\n\t\t\t\treturn string(bytes)\n\t\t\t},\n\t\t},\n\t\tPodNamespace:  d.Namespace,\n\t\tPodName:       d.PodName,\n\t\tContainerName: d.Container,\n\t\tCommand:       command,\n\t\tIgnoreOutput:  true,\n\t}\n\terror := d.client.Exec(options)\n\tlogrus.WithFields(\n\t\tlogrus.Fields{\n\t\t\t\"experimentId\": experimentId,\n\t\t\t\"pod\":          d.PodName,\n\t\t\t\"container\":    d.Container,\n\t\t\t\"command\":      command,\n\t\t\t\"result\":       error,\n\t\t},\n\t).Infof(\"uncompress in the target container\")\n\tif error == nil {\n\t\treturn nil\n\t}\n\treturn errors.New(error.(string))\n}\n\nfunc (d *DownloadOptions) getUrl(srcFile string) string {\n\tobj := srcFile\n\tswitch srcFile {\n\tcase chaosblade.OperatorChaosBladeBlade:\n\t\tobj = blade\n\t\tbreak\n\tcase chaosblade.OperatorChaosBladeYaml:\n\t\tobj = yaml\n\t\tbreak\n\tcase chaosblade.OperatorChaosBladeLib:\n\t\tobj = lib\n\t\tbreak\n\tdefault:\n\t\tobj = strings.TrimPrefix(srcFile, chaosblade.OperatorChaosBladePath+\"/\")\n\t}\n\treturn fmt.Sprintf(\"%s/%s\", d.url, obj)\n}\n"
  },
  {
    "path": "exec/model/executor.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/chaosblade-io/chaosblade-exec-cri/exec\"\n\t\"github.com/chaosblade-io/chaosblade-exec-cri/exec/container\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tpkglabels \"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tcli \"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n)\n\nfunc checkExperimentStatus(ctx context.Context, expModel *spec.ExpModel, statuses []v1alpha1.ResourceStatus, identifiers []ExperimentIdentifierInPod, client *channel.Client) {\n\ttt := expModel.ActionFlags[\"timeout\"]\n\tif _, ok := spec.IsDestroy(ctx); !ok && tt != \"\" && len(statuses) > 0 {\n\t\texperimentId := GetExperimentIdFromContext(ctx)\n\t\tgo func() {\n\t\t\ttimeout, err := strconv.ParseUint(tt, 10, 64)\n\t\t\tif err != nil {\n\t\t\t\t// the err checked in RunE function\n\t\t\t\ttimeDuartion, _ := time.ParseDuration(tt)\n\t\t\t\ttimeout = uint64(timeDuartion.Seconds())\n\t\t\t}\n\t\t\ttime.Sleep(time.Duration(timeout) * time.Second)\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Second*30)\n\t\t\tdefer cancel()\n\n\t\t\tticker := time.NewTicker(time.Second)\n\t\tTickerLoop:\n\t\t\tfor range ticker.C {\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\tticker.Stop()\n\t\t\t\t\tbreak TickerLoop\n\t\t\t\tdefault:\n\t\t\t\t\tisDestroyed := true\n\t\t\t\t\tfor i, status := range statuses {\n\t\t\t\t\t\tif !status.Success {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontainerObjectMeta := ParseIdentifier(status.Identifier)\n\t\t\t\t\t\tidentifier := identifiers[i]\n\t\t\t\t\t\tpodName := containerObjectMeta.PodName\n\t\t\t\t\t\tpodNamespace := containerObjectMeta.Namespace\n\t\t\t\t\t\tcontainerName := containerObjectMeta.ContainerName\n\t\t\t\t\t\tif identifier.ChaosBladePodName != \"\" {\n\t\t\t\t\t\t\tpodName = identifier.ChaosBladePodName\n\t\t\t\t\t\t\tpodNamespace = identifier.ChaosBladeNamespace\n\t\t\t\t\t\t\tcontainerName = identifier.ChaosBladeContainerName\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresponse := client.Exec(&channel.ExecOptions{\n\t\t\t\t\t\t\tStreamOptions: channel.StreamOptions{\n\t\t\t\t\t\t\t\tErrDecoder: func(bytes []byte) interface{} {\n\t\t\t\t\t\t\t\t\tcontent := string(bytes)\n\t\t\t\t\t\t\t\t\tutil.Errorf(identifier.Id, util.GetRunFuncName(), spec.K8sExecFailed.Sprintf(\"pods/exec\", content))\n\t\t\t\t\t\t\t\t\treturn spec.Decode(content, spec.ResponseFailWithFlags(spec.K8sExecFailed, \"pods/exec\", content))\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tOutDecoder: func(bytes []byte) interface{} {\n\t\t\t\t\t\t\t\t\tcontent := string(bytes)\n\t\t\t\t\t\t\t\t\tutil.Errorf(identifier.Id, util.GetRunFuncName(), spec.K8sExecFailed.Sprintf(\"pods/exec\", content))\n\t\t\t\t\t\t\t\t\treturn spec.Decode(content, spec.ResponseFailWithFlags(spec.K8sExecFailed, \"pods/exec\", content))\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tPodName:       podName,\n\t\t\t\t\t\t\tPodNamespace:  podNamespace,\n\t\t\t\t\t\t\tContainerName: containerName,\n\t\t\t\t\t\t\tCommand:       []string{getTargetChaosBladeBin(expModel), \"status\", status.Id},\n\t\t\t\t\t\t\tIgnoreOutput:  false,\n\t\t\t\t\t\t}).(*spec.Response)\n\t\t\t\t\t\tif response.Success {\n\t\t\t\t\t\t\tresult := response.Result.(map[string]interface{})\n\t\t\t\t\t\t\tif result[\"Status\"] != v1alpha1.DestroyedState {\n\t\t\t\t\t\t\t\tisDestroyed = false\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tisDestroyed = false\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif isDestroyed {\n\t\t\t\t\t\tlogrus.Info(\"The experiment was destroyed, ExperimentId: \", experimentId)\n\t\t\t\t\t\tcb := &v1alpha1.ChaosBlade{}\n\t\t\t\t\t\terr := client.Client.Get(context.TODO(), types.NamespacedName{Name: experimentId}, cb)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlogrus.Warn(err.Error())\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif cb.Status.Phase != v1alpha1.ClusterPhaseDestroyed {\n\t\t\t\t\t\t\tcb.Status.Phase = v1alpha1.ClusterPhaseDestroyed\n\t\t\t\t\t\t\terr = client.Client.Status().Update(context.TODO(), cb)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tlogrus.Warn(err.Error())\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tobjectMeta := metav1.ObjectMeta{Name: experimentId}\n\n\t\t\t\t\t\terr = client.Client.Delete(context.TODO(), &v1alpha1.ChaosBlade{\n\t\t\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\t\t\tAPIVersion: \"chaosblade.io/v1alpha1\",\n\t\t\t\t\t\t\t\tKind:       \"ChaosBlade\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tObjectMeta: objectMeta,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlogrus.Warn(err.Error())\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tticker.Stop()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n}\n\nfunc execCommands(isDestroy bool, rsStatus v1alpha1.ResourceStatus,\n\tidentifier ExperimentIdentifierInPod, client *channel.Client,\n) (bool, v1alpha1.ResourceStatus) {\n\tsuccess := false\n\t// handle chaos experiments using daemonset mode\n\tpodName := identifier.PodName\n\tpodNamespace := identifier.Namespace\n\tcontainerName := identifier.ContainerName\n\tif identifier.ChaosBladePodName != \"\" {\n\t\tpodName = identifier.ChaosBladePodName\n\t\tpodNamespace = identifier.ChaosBladeNamespace\n\t\tcontainerName = identifier.ChaosBladeContainerName\n\t}\n\tresponse := client.Exec(&channel.ExecOptions{\n\t\tStreamOptions: channel.StreamOptions{\n\t\t\tIOStreams: channel.IOStreams{\n\t\t\t\tOut:    bytes.NewBuffer([]byte{}),\n\t\t\t\tErrOut: bytes.NewBuffer([]byte{}),\n\t\t\t},\n\t\t\tErrDecoder: func(bytes []byte) interface{} {\n\t\t\t\tcontent := string(bytes)\n\t\t\t\tutil.Errorf(identifier.Id, util.GetRunFuncName(), spec.K8sExecFailed.Sprintf(\"pods/exec\", content))\n\t\t\t\treturn spec.Decode(content, spec.ResponseFailWithFlags(spec.K8sExecFailed, \"pods/exec\", content))\n\t\t\t},\n\t\t\tOutDecoder: func(bytes []byte) interface{} {\n\t\t\t\tcontent := string(bytes)\n\t\t\t\tutil.Infof(identifier.Id, util.GetRunFuncName(), fmt.Sprintf(\"exec output: %s\", content))\n\t\t\t\t// TODO ?? 不应该返回错我\n\t\t\t\treturn spec.Decode(content, spec.ResponseFailWithFlags(spec.K8sExecFailed, \"pods/exec\", content))\n\t\t\t},\n\t\t},\n\t\tPodName:       podName,\n\t\tPodNamespace:  podNamespace,\n\t\tContainerName: containerName,\n\t\tCommand:       strings.Split(identifier.Command, \" \"),\n\t}).(*spec.Response)\n\n\tif response.Success {\n\t\tif !isDestroy {\n\t\t\trsStatus.Id = response.Result.(string)\n\t\t}\n\t\trsStatus = rsStatus.CreateSuccessResourceStatus()\n\t\tsuccess = true\n\t} else {\n\t\trsStatus = rsStatus.CreateFailResourceStatus(response.Err, response.Code)\n\t}\n\treturn success, rsStatus\n}\n\nfunc generateDestroyCommands(experimentId string, expModel *spec.ExpModel,\n\tcontainerObjectMetaList ContainerMatchedList, matchers string, client *channel.Client,\n) ([]ExperimentIdentifierInPod, error) {\n\tcommand := fmt.Sprintf(\"%s destroy %s %s %s\", getTargetChaosBladeBin(expModel), expModel.Target, expModel.ActionName, matchers)\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tfor idx, obj := range containerObjectMetaList {\n\t\tgeneratedCommand := command\n\t\tif obj.Id != \"\" {\n\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --uid %s\", command, obj.Id)\n\t\t}\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta: containerObjectMetaList[idx],\n\t\t\tCommand:             generatedCommand,\n\t\t}\n\t\tresp := deployChaosBlade(experimentId, expModel, obj, false, client)\n\t\tif !resp.Success {\n\t\t\tidentifierInPod.Error = resp.Err\n\t\t\tidentifierInPod.Code = resp.Code\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n\nfunc generateCreateCommands(experimentId string, expModel *spec.ExpModel, containerObjectMetaList ContainerMatchedList,\n\tmatchers string, client *channel.Client,\n) ([]ExperimentIdentifierInPod, error) {\n\tcommand := fmt.Sprintf(\"%s create %s %s %s\", getTargetChaosBladeBin(expModel), expModel.Target, expModel.ActionName, matchers)\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tchaosBladeOverride := expModel.ActionFlags[exec.ChaosBladeOverrideFlag.Name] == \"true\"\n\tfor idx, obj := range containerObjectMetaList {\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta: containerObjectMetaList[idx],\n\t\t\tCommand:             command,\n\t\t}\n\t\tresp := deployChaosBlade(experimentId, expModel, obj, chaosBladeOverride, client)\n\t\tif !resp.Success {\n\t\t\tidentifierInPod.Error = resp.Err\n\t\t\tidentifierInPod.Code = resp.Code\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n\n// GetChaosBladeDaemonsetPodName\nfunc GetChaosBladeDaemonsetPodName(nodeName string, client *channel.Client) (string, error) {\n\tpodName := chaosblade.DaemonsetPodNames[nodeName]\n\tif podName == \"\" {\n\t\tif err := refreshChaosBladeDaemonsetPodNames(client); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn chaosblade.DaemonsetPodNames[nodeName], nil\n\t}\n\t// check\n\tpod := v1.Pod{}\n\terr := client.Get(context.Background(), cli.ObjectKey{\n\t\tNamespace: chaosblade.DaemonsetPodNamespace,\n\t\tName:      podName,\n\t}, &pod)\n\tif err == nil {\n\t\treturn podName, nil\n\t}\n\t// refresh\n\tif err := refreshChaosBladeDaemonsetPodNames(client); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn chaosblade.DaemonsetPodNames[nodeName], nil\n}\n\nfunc refreshChaosBladeDaemonsetPodNames(client *channel.Client) error {\n\tpodList := v1.PodList{}\n\topts := cli.ListOptions{\n\t\tNamespace:     chaosblade.DaemonsetPodNamespace,\n\t\tLabelSelector: pkglabels.SelectorFromSet(chaosblade.DaemonsetPodLabels),\n\t}\n\tif err := client.List(context.TODO(), &podList, &opts); err != nil {\n\t\treturn err\n\t}\n\tpodNames := make(map[string]string, len(podList.Items))\n\tfor _, pod := range podList.Items {\n\t\tpodNames[pod.Spec.NodeName] = pod.Name\n\t}\n\tchaosblade.DaemonsetPodNames = podNames\n\treturn nil\n}\n\nfunc getNodeExperimentIdentifiers(experimentId string, expModel *spec.ExpModel, containerMatchedList ContainerMatchedList, matchers string, destroy bool, client *channel.Client) ([]ExperimentIdentifierInPod, error) {\n\tif destroy {\n\t\treturn generateDestroyNodeCommands(experimentId, expModel, containerMatchedList, matchers, client)\n\t}\n\treturn generateCreateNodeCommands(experimentId, expModel, containerMatchedList, matchers, client)\n}\n\nfunc generateDestroyNodeCommands(experimentId string, expModel *spec.ExpModel, containerObjectMetaList ContainerMatchedList, matchers string, client *channel.Client) ([]ExperimentIdentifierInPod, error) {\n\tcommand := fmt.Sprintf(\"%s destroy %s %s %s\", getTargetChaosBladeBin(expModel), expModel.Target, expModel.ActionName, matchers)\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tfor idx, obj := range containerObjectMetaList {\n\t\tgeneratedCommand := command\n\t\tif obj.Id != \"\" {\n\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --uid %s\", command, obj.Id)\n\t\t}\n\t\tdaemonsetPodName, err := GetChaosBladeDaemonsetPodName(obj.NodeName, client)\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).\n\t\t\t\tErrorf(\"get chaosblade tool pod for destroying failed on %s node, %v\", obj.NodeName, err)\n\t\t\treturn identifiers, err\n\t\t}\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta:     containerObjectMetaList[idx],\n\t\t\tCommand:                 generatedCommand,\n\t\t\tChaosBladeContainerName: chaosblade.DaemonsetPodName,\n\t\t\tChaosBladeNamespace:     chaosblade.DaemonsetPodNamespace,\n\t\t\tChaosBladePodName:       daemonsetPodName,\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n\nfunc generateCreateNodeCommands(experimentId string, expModel *spec.ExpModel, containerObjectMetaList ContainerMatchedList, matchers string, client *channel.Client) ([]ExperimentIdentifierInPod, error) {\n\tcommand := fmt.Sprintf(\"%s create %s %s %s\", getTargetChaosBladeBin(expModel), expModel.Target, expModel.ActionName, matchers)\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tfor idx, obj := range containerObjectMetaList {\n\t\tdaemonsetPodName, err := GetChaosBladeDaemonsetPodName(obj.NodeName, client)\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).\n\t\t\t\tErrorf(\"get chaosblade tool pod for creating failed on %s node, %v\", obj.NodeName, err)\n\t\t\treturn identifiers, err\n\t\t}\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta:     containerObjectMetaList[idx],\n\t\t\tCommand:                 command,\n\t\t\tChaosBladeContainerName: chaosblade.DaemonsetPodName,\n\t\t\tChaosBladeNamespace:     chaosblade.DaemonsetPodNamespace,\n\t\t\tChaosBladePodName:       daemonsetPodName,\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n\n// getTargetChaosBladePath return the chaosblade deployed path in target container\nfunc getTargetChaosBladePath(expModel *spec.ExpModel) string {\n\tchaosbladePath := expModel.ActionFlags[ChaosBladePathFlag.Name]\n\tif chaosbladePath == \"\" {\n\t\treturn chaosblade.OperatorChaosBladePath\n\t}\n\treturn path.Join(chaosbladePath, \"chaosblade\")\n}\n\n// getTargetChaosBladeBin returns the blade deployed path in target container\nfunc getTargetChaosBladeBin(expModel *spec.ExpModel) string {\n\treturn path.Join(getTargetChaosBladePath(expModel), \"blade\")\n}\n\nfunc ExcludeKeyFunc() func() map[string]spec.Empty {\n\treturn GetResourceFlagNames\n}\n\nfunc TruncateContainerObjectMetaUid(uid string) (containerRuntime, containerId string) {\n\tif strings.HasPrefix(uid, \"containerd://\") {\n\t\treturn container.ContainerdRuntime, strings.ReplaceAll(uid, \"containerd://\", \"\")\n\t}\n\n\treturn container.DockerRuntime, strings.ReplaceAll(uid, \"docker://\", \"\")\n}\n\nfunc getDeployMode(options DeployOptions, expModel *spec.ExpModel) (DeployMode, error) {\n\tmode := expModel.ActionFlags[ChaosBladeDeployModeFlag.Name]\n\turl := expModel.ActionFlags[ChaosBladeDownloadUrlFlag.Name]\n\tswitch mode {\n\tcase CopyMode:\n\t\treturn &CopyOptions{options}, nil\n\tcase DownloadMode:\n\t\tif url == \"\" {\n\t\t\turl = chaosblade.DownloadUrl\n\t\t}\n\t\tif url == \"\" {\n\t\t\treturn nil, errors.New(\"must config the chaosblade-download-url flag\")\n\t\t}\n\t\treturn &DownloadOptions{options, url}, nil\n\tdefault:\n\t\treturn &CopyOptions{options}, nil\n\t}\n}\n"
  },
  {
    "path": "exec/model/executor_copy.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"path\"\n\t\"sync\"\n\n\t\"github.com/chaosblade-io/chaosblade-exec-cri/exec/container\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n\t\"github.com/chaosblade-io/chaosblade-operator/version\"\n)\n\ntype ExperimentIdentifierInPod struct {\n\tContainerObjectMeta\n\tCommand string\n\tError   string\n\tCode    int32\n\t// For daemonset\n\tChaosBladePodName       string\n\tChaosBladeNamespace     string\n\tChaosBladeContainerName string\n}\n\ntype ExecCommandInPodExecutor struct {\n\tClient *channel.Client\n}\n\nfunc (e *ExecCommandInPodExecutor) Name() string {\n\treturn \"execInPod\"\n}\n\nfunc (e *ExecCommandInPodExecutor) SetChannel(channel spec.Channel) {\n}\n\n// execInMatchedPod will execute the experiment in the target pod\nfunc (e *ExecCommandInPodExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", GetExperimentIdFromContext(ctx))\n\texperimentStatus := v1alpha1.ExperimentStatus{\n\t\tResStatuses: make([]v1alpha1.ResourceStatus, 0),\n\t}\n\texperimentIdentifiers, err := getExperimentIdentifiers(ctx, expModel, e.Client)\n\tif err != nil {\n\t\tlogrusField.Errorf(\"get experiment identifiers failed, err: %s\", err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.GetIdentifierFailed,\n\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{}),\n\t\t\terr)\n\t}\n\tlogrusField.Infof(\"experiment identifiers: %v\", experimentIdentifiers)\n\n\tstatuses := experimentStatus.ResStatuses\n\tsuccess := true\n\t_, isDestroy := spec.IsDestroy(ctx)\n\tupdateResultLock := &sync.Mutex{}\n\n\texecCommandInPod := func(i int) {\n\t\texecSuccess := true\n\t\tidentifier := experimentIdentifiers[i]\n\n\t\trsStatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       expModel.Scope,\n\t\t\tIdentifier: identifier.GetIdentifier(),\n\t\t\tId:         identifier.Id,\n\t\t}\n\n\t\tif identifier.Error != \"\" {\n\t\t\trsStatus.CreateFailResourceStatus(identifier.Error, spec.K8sExecFailed.Code)\n\t\t\texecSuccess = false\n\t\t} else if identifier.PodName != \"\" {\n\t\t\t// check if pod exist\n\t\t\tpod := &v1.Pod{}\n\t\t\terr := e.Client.Get(context.TODO(), types.NamespacedName{\n\t\t\t\tNamespace: identifier.Namespace,\n\t\t\t\tName:      identifier.PodName,\n\t\t\t}, pod)\n\t\t\tif err != nil {\n\t\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\t\t// pod if not exist, the execution is considered successful.\n\t\t\t\t\tmsg := fmt.Sprintf(\"pod: %s in %s not found, skip to execute command in it\",\n\t\t\t\t\t\tidentifier.PodName, identifier.Namespace)\n\t\t\t\t\tlogrusField.Warningln(msg)\n\t\t\t\t\trsStatus.CreateSuccessResourceStatus()\n\t\t\t\t\trsStatus.Error = msg\n\t\t\t\t\tsuccess = true\n\t\t\t\t} else {\n\t\t\t\t\t// if get pod error, the execution is considered failure\n\t\t\t\t\tmsg := fmt.Sprintf(\"get pod: %s in %s error\",\n\t\t\t\t\t\tidentifier.PodName, identifier.Namespace)\n\t\t\t\t\trsStatus.CreateFailResourceStatus(msg, spec.K8sExecFailed.Code)\n\t\t\t\t\texecSuccess = false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif execSuccess {\n\t\t\tlogrusField.Infof(\"execute identifier: %+v\", identifier)\n\t\t\texecSuccess, rsStatus = execCommands(isDestroy, rsStatus, identifier, e.Client)\n\t\t}\n\t\tupdateResultLock.Lock()\n\t\tstatuses = append(statuses, rsStatus)\n\t\t// If false occurs once, the result is fails\n\t\tsuccess = success && execSuccess\n\t\tupdateResultLock.Unlock()\n\t}\n\n\tParallelizeExec(len(experimentIdentifiers), execCommandInPod)\n\n\tlogrusField.Infof(\"success: %t, statuses: %+v\", success, statuses)\n\tif success {\n\t\texperimentStatus.State = v1alpha1.SuccessState\n\t} else {\n\t\texperimentStatus.State = v1alpha1.ErrorState\n\t\tif len(statuses) == 0 {\n\t\t\texperimentStatus.Error = \"the resources not found\"\n\t\t} else {\n\t\t\texperimentStatus.Error = \"see resStatus for the error details\"\n\t\t}\n\t}\n\texperimentStatus.Success = success\n\texperimentStatus.ResStatuses = append(experimentStatus.ResStatuses, statuses...)\n\n\tcheckExperimentStatus(ctx, expModel, statuses, experimentIdentifiers, e.Client)\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc getExperimentIdentifiers(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) ([]ExperimentIdentifierInPod, error) {\n\tdelete(expModel.ActionFlags, \"uid\")\n\tcontainerObjectMetaList, err := GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\treturn []ExperimentIdentifierInPod{}, err\n\t}\n\texcludeFlagsFunc := ExcludeKeyFunc()\n\tmatchers := spec.ConvertExpMatchersToString(expModel, excludeFlagsFunc)\n\texperimentId := GetExperimentIdFromContext(ctx)\n\t_, destroy := spec.IsDestroy(ctx)\n\n\tisDockerNetwork := expModel.ActionFlags[IsDockerNetworkFlag.Name] == \"true\"\n\tUseSidecarContainerNetwork := expModel.ActionFlags[UseSidecarContainerNetworkFlag.Name] == \"true\"\n\tisContainerSelfTarget := expModel.Target == \"container\"\n\tisContainerNetworkTarget := expModel.Target == \"network\"\n\tisNodeScope := expModel.Scope == \"node\"\n\tif isNodeScope {\n\t\treturn getNodeExperimentIdentifiers(experimentId, expModel, containerObjectMetaList, matchers, destroy, client)\n\t}\n\tif isContainerSelfTarget || (isContainerNetworkTarget && (isDockerNetwork || UseSidecarContainerNetwork)) {\n\t\tif version.CheckVerisonHaveCriCommand() || containerObjectMetaList[0].ContainerRuntime == container.ContainerdRuntime {\n\t\t\treturn getCriExperimentIdentifiers(experimentId, expModel, containerObjectMetaList, matchers, destroy, isContainerNetworkTarget, client)\n\t\t}\n\t\treturn getDockerExperimentIdentifiers(experimentId, expModel, containerObjectMetaList, matchers, destroy, isContainerNetworkTarget, client)\n\t}\n\tif destroy {\n\t\treturn generateDestroyCommands(experimentId, expModel, containerObjectMetaList, matchers, client)\n\t}\n\treturn generateCreateCommands(experimentId, expModel, containerObjectMetaList, matchers, client)\n}\n\nfunc getDockerExperimentIdentifiers(experimentId string, expModel *spec.ExpModel,\n\tcontainerObjectMetaList ContainerMatchedList, matchers string, destroy, isNetworkTarget bool, client *channel.Client,\n) ([]ExperimentIdentifierInPod, error) {\n\tif isNetworkTarget {\n\t\tmatchers = fmt.Sprintf(\"%s --image-repo %s --image-version %s\",\n\t\t\tmatchers, chaosblade.Constant.ImageRepoFunc(), chaosblade.Version)\n\t}\n\tif destroy {\n\t\treturn generateDestroyDockerCommands(experimentId, expModel, containerObjectMetaList, matchers, isNetworkTarget, client)\n\t}\n\treturn generateCreateDockerCommands(experimentId, expModel, containerObjectMetaList, matchers, client)\n}\n\nfunc getCriExperimentIdentifiers(experimentId string, expModel *spec.ExpModel,\n\tcontainerObjectMetaList ContainerMatchedList, matchers string, destroy, isNetworkTarget bool, client *channel.Client,\n) ([]ExperimentIdentifierInPod, error) {\n\tif isNetworkTarget {\n\t\tmatchers = fmt.Sprintf(\"%s --image-repo %s --image-version %s\",\n\t\t\tmatchers, chaosblade.Constant.ImageRepoFunc(), chaosblade.Version)\n\t}\n\tif destroy {\n\t\treturn generateDestroyCriCommands(experimentId, expModel, containerObjectMetaList, matchers, client)\n\t}\n\treturn generateCreateCriCommands(experimentId, expModel, containerObjectMetaList, matchers, client)\n}\n\nfunc generateDestroyDockerCommands(experimentId string, expModel *spec.ExpModel,\n\tcontainerObjectMetaList ContainerMatchedList, matchers string, isNetworkTarget bool, client *channel.Client,\n) ([]ExperimentIdentifierInPod, error) {\n\tcommand := fmt.Sprintf(\"%s destroy docker %s %s %s\", getTargetChaosBladeBin(expModel), expModel.Target, expModel.ActionName, matchers)\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tfor idx, obj := range containerObjectMetaList {\n\t\tdaemonsetPodName, err := GetChaosBladeDaemonsetPodName(obj.NodeName, client)\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).\n\t\t\t\tErrorf(\"get chaosblade tool pod for destroying failed on %s node, %v\", obj.NodeName, err)\n\t\t\treturn identifiers, err\n\t\t}\n\t\tgeneratedCommand := command\n\t\tif isNetworkTarget {\n\t\t\tnewContainerId, err := getNewContainerIdByPod(obj.PodName, obj.Namespace, obj.ContainerName, experimentId, client)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.WithField(\"experiment\", experimentId).Errorf(\"generate destroy docker command failed, %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --container-id %s\", generatedCommand, newContainerId)\n\t\t} else {\n\t\t\tif obj.Id != \"\" {\n\t\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --uid %s\", command, obj.Id)\n\t\t\t}\n\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --container-name %s\", generatedCommand, obj.ContainerName)\n\t\t}\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta:     containerObjectMetaList[idx],\n\t\t\tCommand:                 generatedCommand,\n\t\t\tChaosBladeContainerName: chaosblade.DaemonsetPodName,\n\t\t\tChaosBladeNamespace:     chaosblade.DaemonsetPodNamespace,\n\t\t\tChaosBladePodName:       daemonsetPodName,\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n\nfunc generateCreateDockerCommands(experimentId string, expModel *spec.ExpModel,\n\tcontainerObjectMetaList ContainerMatchedList, matchers string, client *channel.Client,\n) ([]ExperimentIdentifierInPod, error) {\n\tcommand := fmt.Sprintf(\"%s create docker %s %s %s\", getTargetChaosBladeBin(expModel), expModel.Target, expModel.ActionName, matchers)\n\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tfor idx, obj := range containerObjectMetaList {\n\t\tdaemonsetPodName, err := GetChaosBladeDaemonsetPodName(obj.NodeName, client)\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).\n\t\t\t\tErrorf(\"get chaosblade tool pod for creating failed on %s node, %v\", obj.NodeName, err)\n\t\t\treturn identifiers, err\n\t\t}\n\t\tgeneratedCommand := fmt.Sprintf(\"%s --container-id %s\", command, obj.ContainerId)\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta:     containerObjectMetaList[idx],\n\t\t\tCommand:                 generatedCommand,\n\t\t\tChaosBladeContainerName: chaosblade.DaemonsetPodName,\n\t\t\tChaosBladeNamespace:     chaosblade.DaemonsetPodNamespace,\n\t\t\tChaosBladePodName:       daemonsetPodName,\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n\nfunc generateDestroyCriCommands(\n\texperimentId string,\n\texpModel *spec.ExpModel,\n\tcontainerObjectMetaList ContainerMatchedList,\n\tmatchers string,\n\tclient *channel.Client,\n) ([]ExperimentIdentifierInPod, error) {\n\tcommand := fmt.Sprintf(\"%s destroy cri %s %s %s\", getTargetChaosBladeBin(expModel), expModel.Target, expModel.ActionName, matchers)\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tfor idx, obj := range containerObjectMetaList {\n\t\tdaemonsetPodName, err := GetChaosBladeDaemonsetPodName(obj.NodeName, client)\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).\n\t\t\t\tErrorf(\"get chaosblade tool pod for destroying failed on %s node, %v\", obj.NodeName, err)\n\t\t\treturn identifiers, err\n\t\t}\n\t\tgeneratedCommand := command\n\t\tif obj.Id != \"\" {\n\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --uid %s\", generatedCommand, obj.Id)\n\t\t}\n\t\tgeneratedCommand = fmt.Sprintf(\"%s --container-name %s --container-runtime %s\", generatedCommand, obj.ContainerName, obj.ContainerRuntime)\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta:     containerObjectMetaList[idx],\n\t\t\tCommand:                 generatedCommand,\n\t\t\tChaosBladeContainerName: chaosblade.DaemonsetPodName,\n\t\t\tChaosBladeNamespace:     chaosblade.DaemonsetPodNamespace,\n\t\t\tChaosBladePodName:       daemonsetPodName,\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n\nfunc generateCreateCriCommands(experimentId string, expModel *spec.ExpModel,\n\tcontainerObjectMetaList ContainerMatchedList, matchers string, client *channel.Client,\n) ([]ExperimentIdentifierInPod, error) {\n\tcommand := fmt.Sprintf(\"%s create cri %s %s %s\", getTargetChaosBladeBin(expModel), expModel.Target, expModel.ActionName, matchers)\n\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tfor idx, obj := range containerObjectMetaList {\n\t\tdaemonsetPodName, err := GetChaosBladeDaemonsetPodName(obj.NodeName, client)\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).\n\t\t\t\tErrorf(\"get chaosblade tool pod for creating failed on %s node, %v\", obj.NodeName, err)\n\t\t\treturn identifiers, err\n\t\t}\n\t\tgeneratedCommand := fmt.Sprintf(\"%s --container-id %s --container-runtime %s\", command, obj.ContainerId,\n\t\t\tcontainerObjectMetaList[idx].ContainerRuntime)\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta:     containerObjectMetaList[idx],\n\t\t\tCommand:                 generatedCommand,\n\t\t\tChaosBladeContainerName: chaosblade.DaemonsetPodName,\n\t\t\tChaosBladeNamespace:     chaosblade.DaemonsetPodNamespace,\n\t\t\tChaosBladePodName:       daemonsetPodName,\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n\nfunc deployChaosBlade(experimentId string, expModel *spec.ExpModel,\n\tobj ContainerObjectMeta, override bool, client *channel.Client,\n) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\tchaosBladePath := getTargetChaosBladePath(expModel)\n\toptions := DeployOptions{\n\t\tContainer: obj.ContainerName,\n\t\tNamespace: obj.Namespace,\n\t\tPodName:   obj.PodName,\n\t\tclient:    client,\n\t}\n\tdeploy, err := getDeployMode(options, expModel)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), spec.ParameterLess.Sprintf(ChaosBladeDownloadUrlFlag.Name))\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, ChaosBladeDownloadUrlFlag.Name)\n\t}\n\tlogrusField.Infof(\"deploy chaosblade under override with %t value\", override)\n\tchaosBladeBinPath := path.Join(chaosBladePath, \"bin\")\n\tif err := options.CheckFileExists(chaosBladeBinPath); err != nil {\n\t\t// create chaosblade path\n\t\tif err := options.CreateDir(chaosBladeBinPath); err != nil {\n\t\t\tutil.Errorf(experimentId, util.GetRunFuncName(), fmt.Sprintf(\"create chaosblade dir: %s, failed! err: %s\", chaosBladeBinPath, err.Error()))\n\t\t\treturn spec.ResponseFailWithFlags(spec.ParameterInvalidBladePathError, ChaosBladePathFlag.Name, chaosBladeBinPath, err)\n\t\t}\n\t}\n\tbladePath := path.Join(chaosBladePath, \"blade\")\n\tif override || options.CheckFileExists(bladePath) != nil {\n\t\tif err := deploy.DeployToPod(experimentId, chaosblade.OperatorChaosBladeBlade, bladePath); err != nil {\n\t\t\tutil.Errorf(experimentId, util.GetRunFuncName(), fmt.Sprintf(\"deploy blade failed! dir: %s, err: %s\", bladePath, err.Error()))\n\t\t\treturn spec.ResponseFailWithFlags(spec.DeployChaosBladeFailed, bladePath, err)\n\t\t}\n\t}\n\tyamlPath := path.Join(chaosBladePath, \"yaml\")\n\tif override || options.CheckFileExists(yamlPath) != nil {\n\t\tif err := deploy.DeployToPod(experimentId, chaosblade.OperatorChaosBladeYaml, yamlPath); err != nil {\n\t\t\tutil.Errorf(experimentId, util.GetRunFuncName(), fmt.Sprintf(\"deploy yaml failed! dir: %s, err: %s\", yamlPath, err.Error()))\n\t\t\treturn spec.ResponseFailWithFlags(spec.DeployChaosBladeFailed, yamlPath, err)\n\t\t}\n\t}\n\tchaosOSPath := path.Join(chaosBladePath, \"bin\", \"chaos_os\")\n\tif override || options.CheckFileExists(chaosOSPath) != nil {\n\t\tif err := deploy.DeployToPod(experimentId, path.Join(chaosblade.OperatorChaosBladeBin, \"chaos_os\"), chaosOSPath); err != nil {\n\t\t\tutil.Errorf(experimentId, util.GetRunFuncName(), fmt.Sprintf(\"deploy chaos_os failed! dir: %s, err: %s\", chaosOSPath, err.Error()))\n\t\t\treturn spec.ResponseFailWithFlags(spec.DeployChaosBladeFailed, chaosOSPath, err)\n\t\t}\n\t}\n\t// copy files as needed\n\tfor _, program := range expModel.ActionPrograms {\n\t\tvar programFile, operatorProgramFile string\n\t\tswitch program {\n\t\tcase \"java\":\n\t\t\tprogramFile = path.Join(chaosBladePath, \"lib\")\n\t\t\toperatorProgramFile = chaosblade.OperatorChaosBladeLib\n\t\tdefault:\n\t\t\tprogramFile = path.Join(chaosBladePath, \"bin\", program)\n\t\t\toperatorProgramFile = path.Join(chaosblade.OperatorChaosBladeBin, program)\n\t\t}\n\t\tif !override && options.CheckFileExists(programFile) == nil {\n\t\t\tlogrusField.WithField(\"program\", programFile).Infof(\"program exists\")\n\t\t\tcontinue\n\t\t}\n\t\terr := deploy.DeployToPod(experimentId, operatorProgramFile, programFile)\n\t\tlogrusField = logrusField.WithFields(logrus.Fields{\n\t\t\t\"container\": obj.ContainerName,\n\t\t\t\"pod\":       obj.PodName,\n\t\t\t\"namespace\": obj.Namespace,\n\t\t})\n\t\tif err != nil {\n\t\t\tutil.Errorf(experimentId, util.GetRunFuncName(), fmt.Sprintf(\"copy chaosblade to pod failed! dir: %s, err: %s\", yamlPath, err.Error()))\n\t\t\treturn spec.ResponseFailWithFlags(spec.K8sExecFailed, \"copyToPod\", err)\n\t\t}\n\t\tlogrusField.Infof(\"deploy %s success\", programFile)\n\t}\n\treturn spec.Success()\n}\n\nfunc getNewContainerIdByPod(podName, podNamespace, containerName, experimentId string, client *channel.Client) (string, error) {\n\tpod := v1.Pod{}\n\terr := client.Get(context.TODO(), types.NamespacedName{Namespace: podNamespace, Name: podName}, &pod)\n\tif err != nil {\n\t\tlogrus.WithFields(\n\t\t\tlogrus.Fields{\n\t\t\t\t\"experiment\":    experimentId,\n\t\t\t\t\"containerName\": containerName,\n\t\t\t},\n\t\t).Warningf(\"can not find the pod by %s name in %s namespace, %v\", podName, podNamespace, err)\n\t\treturn \"\", err\n\t}\n\tcontainerStatuses := pod.Status.ContainerStatuses\n\tif containerStatuses == nil {\n\t\treturn \"\", fmt.Errorf(\"cannot find containers in %s pod\", podName)\n\t}\n\tfor _, containerStatus := range containerStatuses {\n\t\tif containerName == containerStatus.Name {\n\t\t\t_, containerLongId := TruncateContainerObjectMetaUid(containerStatus.ContainerID)\n\t\t\tif len(containerLongId) > 12 {\n\t\t\t\treturn containerLongId[:12], nil\n\t\t\t}\n\t\t\treturn \"\", fmt.Errorf(\"the container %s id is illegal\", containerLongId)\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"cannot find the %s container in %s pod\", containerName, podName)\n}\n"
  },
  {
    "path": "exec/model/executor_nsexec.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/chaosblade-io/chaosblade-exec-cri/exec/container\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n)\n\ntype CommonExecutor struct {\n\tClient *channel.Client\n}\n\nfunc (e *CommonExecutor) Name() string {\n\treturn \"CommonExecutor\"\n}\n\nfunc (e *CommonExecutor) SetChannel(channel spec.Channel) {\n}\n\nfunc (e *CommonExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", GetExperimentIdFromContext(ctx))\n\texperimentStatus := v1alpha1.ExperimentStatus{\n\t\tResStatuses: make([]v1alpha1.ResourceStatus, 0),\n\t}\n\texperimentIdentifiers, err := getExperimentIdentifiersWithNsexec(ctx, expModel, e.Client)\n\tif err != nil {\n\t\tlogrusField.Errorf(\"get experiment identifiers failed, err: %s\", err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.GetIdentifierFailed,\n\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{}),\n\t\t\terr)\n\t}\n\tlogrusField.Infof(\"experiment identifiers: %v\", experimentIdentifiers)\n\n\tstatuses := experimentStatus.ResStatuses\n\tsuccess := true\n\t_, isDestroy := spec.IsDestroy(ctx)\n\tupdateResultLock := &sync.Mutex{}\n\n\texecCommandInPod := func(i int) {\n\t\texecSuccess := true\n\t\tidentifier := experimentIdentifiers[i]\n\n\t\trsStatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       expModel.Scope,\n\t\t\tIdentifier: identifier.GetIdentifier(),\n\t\t\tId:         identifier.Id,\n\t\t}\n\n\t\tif identifier.Error != \"\" {\n\t\t\trsStatus.CreateFailResourceStatus(identifier.Error, spec.K8sExecFailed.Code)\n\t\t\texecSuccess = false\n\t\t} else if identifier.PodName != \"\" {\n\t\t\t// check if pod exist\n\t\t\tpod := &v1.Pod{}\n\t\t\terr := e.Client.Get(context.TODO(), types.NamespacedName{\n\t\t\t\tNamespace: identifier.Namespace,\n\t\t\t\tName:      identifier.PodName,\n\t\t\t}, pod)\n\t\t\tif err != nil {\n\t\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\t\t// pod if not exist, the execution is considered successful.\n\t\t\t\t\tmsg := fmt.Sprintf(\"pod: %s in %s not found, skip to execute command in it\",\n\t\t\t\t\t\tidentifier.PodName, identifier.Namespace)\n\t\t\t\t\tlogrusField.Warningln(msg)\n\t\t\t\t\trsStatus.CreateSuccessResourceStatus()\n\t\t\t\t\trsStatus.Error = msg\n\t\t\t\t\tsuccess = true\n\t\t\t\t} else {\n\t\t\t\t\t// if get pod error, the execution is considered failure\n\t\t\t\t\tmsg := fmt.Sprintf(\"get pod: %s in %s error\",\n\t\t\t\t\t\tidentifier.PodName, identifier.Namespace)\n\t\t\t\t\trsStatus.CreateFailResourceStatus(msg, spec.K8sExecFailed.Code)\n\t\t\t\t\texecSuccess = false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif execSuccess {\n\t\t\tlogrusField.Infof(\"execute identifier: %+v\", identifier)\n\t\t\texecSuccess, rsStatus = execCommands(isDestroy, rsStatus, identifier, e.Client)\n\t\t}\n\t\tupdateResultLock.Lock()\n\t\tstatuses = append(statuses, rsStatus)\n\t\t// If false occurs once, the result is fails\n\t\tsuccess = success && execSuccess\n\t\tupdateResultLock.Unlock()\n\t}\n\n\tParallelizeExec(len(experimentIdentifiers), execCommandInPod)\n\n\tlogrusField.Infof(\"success: %t, statuses: %+v\", success, statuses)\n\tif success {\n\t\texperimentStatus.State = v1alpha1.SuccessState\n\t} else {\n\t\texperimentStatus.State = v1alpha1.ErrorState\n\t\tif len(statuses) == 0 {\n\t\t\texperimentStatus.Error = \"the resources not found\"\n\t\t} else {\n\t\t\texperimentStatus.Error = \"see resStatus for the error details\"\n\t\t}\n\t}\n\texperimentStatus.Success = success\n\texperimentStatus.ResStatuses = append(experimentStatus.ResStatuses, statuses...)\n\n\tcheckExperimentStatus(ctx, expModel, statuses, experimentIdentifiers, e.Client)\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc getExperimentIdentifiersWithNsexec(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) ([]ExperimentIdentifierInPod, error) {\n\tdelete(expModel.ActionFlags, \"uid\")\n\tcontainerObjectMetaList, err := GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\treturn []ExperimentIdentifierInPod{}, err\n\t}\n\texcludeFlagsFunc := ExcludeKeyFunc()\n\tmatchers := spec.ConvertExpMatchersToString(expModel, excludeFlagsFunc)\n\texperimentId := GetExperimentIdFromContext(ctx)\n\t_, destroy := spec.IsDestroy(ctx)\n\n\tisNodeScope := expModel.Scope == \"node\"\n\tif isNodeScope {\n\t\treturn getNodeExperimentIdentifiers(experimentId, expModel, containerObjectMetaList, matchers, destroy, client)\n\t}\n\n\tvar (\n\t\tscope  = \"cri\"\n\t\thandle = \"create\"\n\t)\n\n\tif destroy {\n\t\thandle = \"destroy\"\n\t}\n\n\tcommand := fmt.Sprintf(\"%s %s %s %s %s %s\",\n\t\tgetTargetChaosBladeBin(expModel),\n\t\thandle,\n\t\tscope,\n\t\texpModel.Target,\n\t\texpModel.ActionName,\n\t\tmatchers)\n\n\tidentifiers := make([]ExperimentIdentifierInPod, 0)\n\tfor idx, obj := range containerObjectMetaList {\n\t\tvar generatedCommand string\n\t\tif expModel.Target == \"network\" && handle == \"destroy\" && expModel.ActionName != \"dns\" {\n\t\t\tlabels := []string{\n\t\t\t\tfmt.Sprintf(\"io.kubernetes.pod.name=%s\", obj.PodName),\n\t\t\t\tfmt.Sprintf(\"io.kubernetes.pod.namespace=%s\", obj.Namespace),\n\t\t\t}\n\t\t\tif obj.ContainerRuntime == container.DockerRuntime {\n\t\t\t\tlabels = append(labels, \"io.kubernetes.docker.type=podsandbox\")\n\t\t\t} else if obj.ContainerRuntime == container.ContainerdRuntime {\n\t\t\t\tlabels = append(labels, \"io.cri-containerd.kind=sandbox\")\n\t\t\t} else {\n\t\t\t\tlogrus.WithField(\"experiment\", experimentId).\n\t\t\t\t\tErrorf(\"unsupported container runtime %s\", obj.ContainerRuntime)\n\t\t\t\treturn identifiers, fmt.Errorf(\"unsupported container runtime %s\", obj.ContainerRuntime)\n\t\t\t}\n\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --container-label-selector %s --container-runtime %s\", command, strings.Join(labels, \",\"), obj.ContainerRuntime)\n\t\t} else {\n\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --container-id %s\", command, obj.ContainerId)\n\t\t\tif expModel.ActionProcessHang {\n\t\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --cgroup-root /host-sys/fs/cgroup\", generatedCommand)\n\t\t\t}\n\t\t\tif len(obj.ContainerRuntime) > 0 {\n\t\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --container-runtime %s\", generatedCommand, obj.ContainerRuntime)\n\t\t\t}\n\t\t\tif obj.Id != \"\" {\n\t\t\t\tgeneratedCommand = fmt.Sprintf(\"%s --uid %s\", generatedCommand, obj.Id)\n\t\t\t}\n\t\t}\n\n\t\tdaemonsetPodName, err := GetChaosBladeDaemonsetPodName(obj.NodeName, client)\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).\n\t\t\t\tErrorf(\"get chaosblade tool pod for destroying failed on %s node, %v\", obj.NodeName, err)\n\t\t\treturn identifiers, err\n\t\t}\n\t\tidentifierInPod := ExperimentIdentifierInPod{\n\t\t\tContainerObjectMeta:     containerObjectMetaList[idx],\n\t\t\tCommand:                 generatedCommand,\n\t\t\tChaosBladeContainerName: chaosblade.DaemonsetPodName,\n\t\t\tChaosBladeNamespace:     chaosblade.DaemonsetPodNamespace,\n\t\t\tChaosBladePodName:       daemonsetPodName,\n\t\t}\n\t\tidentifiers = append(identifiers, identifierInPod)\n\t}\n\treturn identifiers, nil\n}\n"
  },
  {
    "path": "exec/model/filter.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tpkglabels \"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/selection\"\n)\n\nfunc GetOneAvailableContainerIdFromPod(pod v1.Pod) (containerId, containerName, runtime string, err error) {\n\tcontainerStatuses := pod.Status.ContainerStatuses\n\tif len(containerStatuses) == 0 {\n\t\treturn \"\", \"\", \"\", fmt.Errorf(\"the container statues is empty in %s pod\", pod.Name)\n\t}\n\tfor _, containerStatus := range containerStatuses {\n\t\tif containerStatus.State.Running == nil {\n\t\t\tcontinue\n\t\t}\n\t\truntime, containerId := TruncateContainerObjectMetaUid(containerStatus.ContainerID)\n\t\treturn containerId, containerStatus.Name, runtime, nil\n\t}\n\treturn \"\", \"\", \"\", fmt.Errorf(\"cannot find a valiable container in %s pod\", pod.Name)\n}\n\nfunc ParseLabels(labels string) []pkglabels.Requirement {\n\tlabelArr := strings.Split(labels, \",\")\n\trequirements := make([]pkglabels.Requirement, 0, len(labelArr))\n\tlabelsMap := make(map[string][]string, 0)\n\tif labels == \"\" {\n\t\treturn requirements\n\t}\n\n\tfor _, label := range labelArr {\n\t\tkeyValue := strings.SplitN(label, \"=\", 2)\n\t\tif len(keyValue) != 2 {\n\t\t\tlogrus.Warningf(\"label %s is illegal\", label)\n\t\t\tcontinue\n\t\t}\n\t\tif labelsMap[keyValue[0]] == nil {\n\t\t\tvalueArr := make([]string, 0)\n\t\t\tvalueArr = append(valueArr, keyValue[1])\n\t\t\tlabelsMap[keyValue[0]] = valueArr\n\t\t} else {\n\t\t\tlabelsMap[keyValue[0]] = append(labelsMap[keyValue[0]], keyValue[1])\n\t\t}\n\t}\n\n\tfor label, value := range labelsMap {\n\t\trequirement, err := pkglabels.NewRequirement(label, selection.In, value)\n\t\tif err != nil {\n\t\t\tlogrus.Warningf(\"requirement %s-%s is illegal\", label, value)\n\t\t\tcontinue\n\t\t}\n\t\trequirements = append(requirements, *requirement)\n\t}\n\treturn requirements\n}\n\nfunc MapContains(bigMap map[string]string, requirements []pkglabels.Requirement) bool {\n\tif bigMap == nil || requirements == nil {\n\t\treturn false\n\t}\n\tlabelSet := pkglabels.Set(bigMap)\n\tfor i := 0; i < len(requirements); i++ {\n\t\tif requirements[i].Matches(labelSet) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc CheckFlags(flags map[string]string) *spec.Response {\n\t// Must include one flag in the count, percent, labels and names\n\texpFlags := []*spec.ExpFlag{\n\t\tResourceCountFlag,\n\t\tResourcePercentFlag,\n\t\tResourceLabelsFlag,\n\t\tResourceNamesFlag,\n\t}\n\tvalue := \"\"\n\tflagsNames := make([]string, 0)\n\tfor _, flag := range expFlags {\n\t\tflagsNames = append(flagsNames, flag.Name)\n\t\tvalue = fmt.Sprintf(\"%s%s\", value, flags[flag.Name])\n\t}\n\tif value == \"\" {\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, strings.Join(flagsNames, \"|\"))\n\t}\n\treturn spec.Success()\n}\n"
  },
  {
    "path": "exec/model/filter_pod.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"context\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tpkglabels \"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n)\n\nconst DefaultNamespace = \"default\"\n\nfunc CheckPodFlags(flags map[string]string) *spec.Response {\n\tnamespace := flags[ResourceNamespaceFlag.Name]\n\tif namespace == \"\" {\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, ResourceNamespaceFlag.Name)\n\t}\n\tnamespacesValue := strings.Split(namespace, \",\")\n\tif len(namespacesValue) > 1 {\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, ResourceNamespaceFlag.Name)\n\t}\n\treturn CheckFlags(flags)\n}\n\n// GetMatchedPodResources return matched pods\nfunc (b *BaseExperimentController) GetMatchedPodResources(ctx context.Context, expModel spec.ExpModel) ([]v1.Pod, *spec.Response) {\n\tflags := expModel.ActionFlags\n\tif flags[ResourceNamespaceFlag.Name] == \"\" {\n\t\texpModel.ActionFlags[ResourceNamespaceFlag.Name] = DefaultNamespace\n\t}\n\tif resp := CheckPodFlags(flags); !resp.Success {\n\t\treturn nil, resp\n\t}\n\tpods, resp := resourceFunc(ctx, b.Client, flags)\n\tif !resp.Success {\n\t\treturn pods, resp\n\t}\n\treturn b.filterByOtherFlags(pods, flags)\n}\n\nfunc (b *BaseExperimentController) filterByOtherFlags(pods []v1.Pod, flags map[string]string) ([]v1.Pod, *spec.Response) {\n\trandom := flags[\"random\"] == \"true\"\n\tgroupKey := flags[ResourceGroupKeyFlag.Name]\n\tif groupKey == \"\" {\n\t\tcount, resp := GetResourceCount(len(pods), flags)\n\t\tif !resp.Success {\n\t\t\treturn pods[:count], resp\n\t\t}\n\t\tif random {\n\t\t\treturn randomPodSelected(pods, count), spec.Success()\n\t\t}\n\t\treturn pods[:count], spec.Success()\n\t}\n\tgroupPods := make(map[string][]v1.Pod, 0)\n\tkeys := strings.Split(groupKey, \",\")\n\tfor _, pod := range pods {\n\t\tfor _, key := range keys {\n\t\t\tlabelValue := pod.Labels[key]\n\t\t\tpodList := groupPods[labelValue]\n\t\t\tif podList == nil {\n\t\t\t\tpodList = []v1.Pod{}\n\t\t\t\tgroupPods[labelValue] = podList\n\t\t\t}\n\t\t\tgroupPods[labelValue] = append(podList, pod)\n\t\t}\n\t}\n\tresult := make([]v1.Pod, 0)\n\tfor _, podList := range groupPods {\n\t\tcount, resp := GetResourceCount(len(podList), flags)\n\t\tif !resp.Success {\n\t\t\treturn pods[:count], resp\n\t\t}\n\t\tif random {\n\t\t\tresult = append(result, randomPodSelected(podList, count)...)\n\t\t} else {\n\t\t\tresult = append(result, podList[:count]...)\n\t\t}\n\t}\n\tif len(result) == 0 {\n\t\treturn result, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sPodQuery, ResourceGroupKeyFlag.Name)\n\t}\n\treturn result, spec.Success()\n}\n\n// resourceFunc is used to query the target resource\nvar resourceFunc = func(ctx context.Context, client2 *channel.Client, flags map[string]string) ([]v1.Pod, *spec.Response) {\n\tnamespace := flags[ResourceNamespaceFlag.Name]\n\tlabels := flags[ResourceLabelsFlag.Name]\n\trequirements := ParseLabels(labels)\n\tlogrusField := logrus.WithField(\"experiment\", GetExperimentIdFromContext(ctx))\n\tpods := make([]v1.Pod, 0)\n\tnames := flags[ResourceNamesFlag.Name]\n\tlogrusField.Debugf(\"namespace: %s, labels: %s, names: %s\", namespace, labels, names)\n\tif names != \"\" {\n\t\tnameArr := strings.Split(names, \",\")\n\t\tfor _, name := range nameArr {\n\t\t\tpod := v1.Pod{}\n\t\t\terr := client2.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name}, &pod)\n\t\t\tif err != nil {\n\t\t\t\tlogrusField.Warningf(\"can not find the pod by %s name in %s namespace, %v\", name, namespace, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(requirements) > 0 {\n\t\t\t\tif MapContains(pod.Labels, requirements) {\n\t\t\t\t\tpods = append(pods, pod)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tpods = append(pods, pod)\n\t\t\t}\n\t\t}\n\t\tlogrusField.Infof(\"get pods by names %s, len is %d\", names, len(pods))\n\t\tif len(pods) == 0 {\n\t\t\treturn pods, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sPodQuery, names)\n\t\t}\n\t\treturn pods, spec.Success()\n\t}\n\tif labels != \"\" && len(requirements) == 0 {\n\t\tmsg := spec.ParameterIllegal.Sprintf(ResourceLabelsFlag.Name, labels, \"data format error\")\n\t\tlogrusField.Warningln(msg)\n\t\treturn pods, spec.ResponseFailWithFlags(spec.ParameterLess, ResourceLabelsFlag.Name, labels, \"data format error, example: key=value\")\n\t}\n\tif len(requirements) > 0 {\n\t\tpodList := v1.PodList{}\n\t\tselector := pkglabels.NewSelector().Add(requirements...)\n\t\topts := client.ListOptions{Namespace: namespace, LabelSelector: selector}\n\t\terr := client2.List(context.TODO(), &podList, &opts)\n\t\tif err != nil {\n\t\t\treturn pods, spec.ResponseFailWithFlags(spec.K8sExecFailed, \"PodList\", err)\n\t\t}\n\t\tif len(podList.Items) == 0 {\n\t\t\treturn pods, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sPodQuery, ResourceLabelsFlag.Name)\n\t\t}\n\t\t// filter out running but TERMINATING pods\n\t\tfor _, p := range podList.Items {\n\t\t\tif p.ObjectMeta.DeletionTimestamp != nil {\n\t\t\t\tlogrusField.Debugf(\"the pod is being deleted: %s\", p.Name)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpods = append(pods, p)\n\t\t}\n\t\tlogrusField.Infof(\"get pods by labels %s, len is %d\", labels, len(pods))\n\t}\n\treturn pods, spec.Success()\n}\n\nfunc randomPodSelected(pods []v1.Pod, count int) []v1.Pod {\n\tif len(pods) == 0 {\n\t\treturn pods\n\t}\n\trand.Seed(time.Now().UnixNano())\n\tfor i := len(pods) - 1; i > 0; i-- {\n\t\tnum := rand.Intn(i + 1)\n\t\tpods[i], pods[num] = pods[num], pods[i]\n\t}\n\treturn pods[:count]\n}\n"
  },
  {
    "path": "exec/model/filter_pod_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tv12 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc Test_randomSelected(t *testing.T) {\n\toriginList := []v1.Pod{\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"1\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"2\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"3\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"4\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"5\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"6\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"7\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"8\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"9\"}},\n\t\t{ObjectMeta: v12.ObjectMeta{Name: \"10\"}},\n\t}\n\trandomList := randomPodSelected(originList, 5)\n\tvar randomNameList []string\n\tfor _, item := range randomList {\n\t\trandomNameList = append(randomNameList, item.ObjectMeta.Name)\n\t}\n\tt.Logf(\"randomNameList()=%v\", randomNameList)\n\tif reflect.DeepEqual(randomNameList, []string{\"1\", \"2\", \"3\", \"4\", \"5\"}) {\n\t\tt.Errorf(\"randomPodSelected() is invalid\")\n\t}\n}\n"
  },
  {
    "path": "exec/model/model.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"context\"\n\n\t\"github.com/chaosblade-io/chaosblade-exec-cri/exec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\n// ActionPreProcessor defines the interface for action-specific pre-processing\n// before the main create/destroy flow. Actions that don't require pod matching\n// or need custom validation should implement this interface.\ntype ActionPreProcessor interface {\n\t// PreCreate is called before the main create flow.\n\t// Returns a modified context for downstream processing, or a Response\n\t// if validation fails or early return is needed.\n\t// If Response is nil, the main flow continues with the returned context.\n\tPreCreate(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) (context.Context, *spec.Response)\n\t// PreDestroy is called before the main destroy flow.\n\t// Returns a modified context for downstream processing, or a Response\n\t// if validation fails or early return is needed.\n\t// If Response is nil, the main flow continues with the returned context.\n\tPreDestroy(ctx context.Context, expModel *spec.ExpModel, client *channel.Client, oldExpStatus v1alpha1.ExperimentStatus) (context.Context, *spec.Response)\n}\n\n// ResourceExpModelSpec contains node, pod, container\ntype ResourceExpModelSpec interface {\n\tScope() string\n\tExpModels() map[string]spec.ExpModelCommandSpec\n\n\tGetExpActionModelSpec(target, action string) spec.ExpActionCommandSpec\n}\n\nfunc NewBaseResourceExpModelSpec(scopeName string, client *channel.Client) BaseResourceExpModelSpec {\n\treturn BaseResourceExpModelSpec{\n\t\tScopeName:     scopeName,\n\t\tClient:        client,\n\t\tExpModelSpecs: make(map[string]spec.ExpModelCommandSpec, 0),\n\t}\n}\n\ntype BaseResourceExpModelSpec struct {\n\tScopeName     string\n\tClient        *channel.Client\n\tExpModelSpecs map[string]spec.ExpModelCommandSpec\n}\n\nfunc (b *BaseResourceExpModelSpec) Scope() string {\n\treturn b.ScopeName\n}\n\nfunc (b *BaseResourceExpModelSpec) ExpModels() map[string]spec.ExpModelCommandSpec {\n\treturn b.ExpModelSpecs\n}\n\nfunc (b *BaseResourceExpModelSpec) GetExpActionModelSpec(target, actionName string) spec.ExpActionCommandSpec {\n\tcommandSpec := b.ExpModelSpecs[target]\n\tif commandSpec == nil {\n\t\treturn nil\n\t}\n\tactions := commandSpec.Actions()\n\tif actions == nil {\n\t\treturn nil\n\t}\n\tfor _, action := range actions {\n\t\tif action.Name() == actionName {\n\t\t\treturn action\n\t\t}\n\t\tfor _, alias := range action.Aliases() {\n\t\t\tif alias == actionName {\n\t\t\t\treturn action\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (b *BaseResourceExpModelSpec) RegisterExpModels(expModel ...spec.ExpModelCommandSpec) {\n\tfor _, model := range expModel {\n\t\tb.ExpModelSpecs[model.Name()] = model\n\t}\n}\n\n// SubResourceExpModelSpec contains os exps in node, network exp in pod and os exps in container\ntype SubResourceExpModelSpec interface {\n\tExpModels() []spec.ExpModelCommandSpec\n\n\tExecutor() spec.Executor\n}\n\ntype BaseSubResourceExpModelSpec struct {\n\tExpModelSpecs []spec.ExpModelCommandSpec\n\tExpExecutor   spec.Executor\n}\n\nfunc (b *BaseSubResourceExpModelSpec) ExpModels() []spec.ExpModelCommandSpec {\n\treturn b.ExpModelSpecs\n}\n\nfunc (b *BaseSubResourceExpModelSpec) Executor() spec.Executor {\n\treturn b.ExpExecutor\n}\n\nvar ResourceCountFlag = &spec.ExpFlag{\n\tName:     \"evict-count\",\n\tDesc:     \"Count of affected resource\",\n\tNoArgs:   false,\n\tRequired: false,\n}\n\nvar ResourcePercentFlag = &spec.ExpFlag{\n\tName:     \"evict-percent\",\n\tDesc:     \"Percent of affected resource, integer value without %\",\n\tNoArgs:   false,\n\tRequired: false,\n}\n\nfunc GetResourceCoverageFlags() []spec.ExpFlagSpec {\n\treturn []spec.ExpFlagSpec{\n\t\tResourceCountFlag,\n\t\tResourcePercentFlag,\n\t}\n}\n\nvar ResourceNamesFlag = &spec.ExpFlag{\n\tName:     \"names\",\n\tDesc:     \"Resource names, such as pod name. You must add namespace flag for it. Multiple parameters are separated directly by commas\",\n\tNoArgs:   false,\n\tRequired: false,\n}\n\nvar ResourceNamespaceFlag = &spec.ExpFlag{\n\tName:     \"namespace\",\n\tDesc:     \"Namespace, such as default, only one value can be specified\",\n\tNoArgs:   false,\n\tRequired: true,\n}\n\nvar ResourceLabelsFlag = &spec.ExpFlag{\n\tName:     \"labels\",\n\tDesc:     \"Label selector, the relationship between values that are or\",\n\tNoArgs:   false,\n\tRequired: false,\n}\n\nvar ResourceGroupKeyFlag = &spec.ExpFlag{\n\tName:     \"evict-group\",\n\tDesc:     \"Group key from labels\",\n\tNoArgs:   false,\n\tRequired: false,\n}\n\nvar ContainerIdsFlag = &spec.ExpFlag{\n\tName:     \"container-ids\",\n\tDesc:     \"Container ids\",\n\tNoArgs:   false,\n\tRequired: false,\n}\n\nvar ContainerNamesFlag = &spec.ExpFlag{\n\tName:     \"container-names\",\n\tDesc:     \"Container names\",\n\tNoArgs:   false,\n\tRequired: false,\n}\n\nvar ContainerIndexFlag = &spec.ExpFlag{\n\tName: \"container-index\",\n\tDesc: \"Container index, start from 0\",\n}\n\nvar ChaosBladePathFlag = &spec.ExpFlag{\n\tName: \"chaosblade-path\",\n\tDesc: \"Chaosblade tool deployment path, default value is /opt. Please select a path with write permission\",\n}\n\nvar ChaosBladeDownloadUrlFlag = &spec.ExpFlag{\n\tName: \"chaosblade-download-url\",\n\tDesc: \"The chaosblade downloaded address. If you use download deployment mode, you must specify the value, or config chaosblade-download-url when deploying the operator\",\n}\n\nvar (\n\tDownloadMode = \"download\"\n\tCopyMode     = \"copy\"\n)\n\nvar ChaosBladeDeployModeFlag = &spec.ExpFlag{\n\tName: \"chaosblade-deploy-mode\",\n\tDesc: \"The mode of chaosblade deployment in container, the values are copy and download, the default value is copy which copy tool from the operator to the target container. If you select download mode, the operator will download chaosblade tool from the chaosblade-download-url.\",\n}\n\nvar IsDockerNetworkFlag = &spec.ExpFlag{\n\tName:     \"is-docker-network\",\n\tDesc:     \"Used when a docker container is used and there is no tc command in the target container. Just for docker command, Deprecated！ Please use use-sidecar-container-network flag.\",\n\tNoArgs:   true,\n\tRequired: false,\n}\n\nvar UseSidecarContainerNetworkFlag = &spec.ExpFlag{\n\tName:     \"use-sidecar-container-network\",\n\tDesc:     \"When there is no tc command in the target container. Set the sidecar container network true.\",\n\tNoArgs:   true,\n\tRequired: false,\n}\n\nfunc GetNetworkFlags() []spec.ExpFlagSpec {\n\treturn []spec.ExpFlagSpec{\n\t\tIsDockerNetworkFlag,\n\t\tUseSidecarContainerNetworkFlag,\n\t}\n}\n\nfunc GetContainerFlags() []spec.ExpFlagSpec {\n\treturn []spec.ExpFlagSpec{\n\t\tContainerIdsFlag,\n\t\tContainerNamesFlag,\n\t\tContainerIndexFlag,\n\t}\n}\n\nfunc GetResourceCommonFlags() []spec.ExpFlagSpec {\n\treturn []spec.ExpFlagSpec{\n\t\tResourceNamesFlag,\n\t\tResourceNamespaceFlag,\n\t\tResourceLabelsFlag,\n\t\tResourceGroupKeyFlag,\n\t}\n}\n\nfunc GetChaosBladeFlags() []spec.ExpFlagSpec {\n\treturn []spec.ExpFlagSpec{\n\t\tChaosBladePathFlag,\n\t\texec.ChaosBladeOverrideFlag,\n\t\tChaosBladeDeployModeFlag,\n\t\tChaosBladeDownloadUrlFlag,\n\t}\n}\n\nfunc GetResourceFlagNames() map[string]spec.Empty {\n\tflagNames := []string{\n\t\tResourceCountFlag.Name,\n\t\tResourcePercentFlag.Name,\n\t\tResourceNamesFlag.Name,\n\t\tResourceNamespaceFlag.Name,\n\t\tResourceLabelsFlag.Name,\n\t\tContainerIdsFlag.Name,\n\t\tContainerNamesFlag.Name,\n\t\tContainerIndexFlag.Name,\n\t\tChaosBladePathFlag.Name,\n\t\texec.ChaosBladeOverrideFlag.Name,\n\t\tChaosBladeDeployModeFlag.Name,\n\t\tChaosBladeDownloadUrlFlag.Name,\n\t\tIsDockerNetworkFlag.Name,\n\t\tUseSidecarContainerNetworkFlag.Name,\n\t}\n\tnames := make(map[string]spec.Empty, 0)\n\tfor _, name := range flagNames {\n\t\tnames[name] = spec.Empty{}\n\t}\n\treturn names\n}\n"
  },
  {
    "path": "exec/model/osexp.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport (\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/cpu\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/disk\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/file\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/mem\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/network\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/process\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/script\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n)\n\ntype OSSubResourceModelSpec struct {\n\tBaseSubResourceExpModelSpec\n}\n\nfunc NewOSSubResourceModelSpec() SubResourceExpModelSpec {\n\tmodelSpec := &OSSubResourceModelSpec{\n\t\tBaseSubResourceExpModelSpec{\n\t\t\tExpModelSpecs: []spec.ExpModelCommandSpec{\n\t\t\t\tcpu.NewCpuCommandModelSpec(),\n\t\t\t\tnetwork.NewNetworkCommandSpec(),\n\t\t\t\tprocess.NewProcessCommandModelSpec(),\n\t\t\t\tdisk.NewDiskCommandSpec(),\n\t\t\t\tmem.NewMemCommandModelSpec(),\n\t\t\t\tfile.NewFileCommandSpec(),\n\t\t\t\tscript.NewScriptCommandModelSpec(),\n\t\t\t},\n\t\t},\n\t}\n\treturn modelSpec\n}\n"
  },
  {
    "path": "exec/model/parallelizer.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage model\n\nimport \"sync\"\n\nconst (\n\tmaxWorkers = 64 // magic number\n)\n\ntype DoWorkFunc func(workID int)\n\nfunc ParallelizeExec(workCount int, doWork DoWorkFunc) {\n\tworkers := maxWorkers\n\ttoExec := make(chan int, workCount)\n\n\tfor i := 0; i < workCount; i++ {\n\t\ttoExec <- i\n\t}\n\tclose(toExec)\n\n\tif workCount < workers {\n\t\tworkers = workCount\n\t}\n\n\twg := sync.WaitGroup{}\n\twg.Add(workers)\n\tfor i := 0; i < workers; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor workID := range toExec {\n\t\t\t\tdoWork(workID)\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n}\n"
  },
  {
    "path": "exec/node/cniexp.go",
    "content": "/*\n * Copyright 1999-2020 Alibaba Group Holding Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage node\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\tCniBinPathFlag  = \"cni-bin-path\"\n\tCniErrorMsgFlag = \"error-msg\"\n)\n\nfunc NewCniExpModelCommandSpec(client *channel.Client) spec.ExpModelCommandSpec {\n\treturn &CniExpModelCommandSpec{\n\t\tspec.BaseExpModelCommandSpec{\n\t\t\tExpActions: []spec.ExpActionCommandSpec{\n\t\t\t\tNewCniAddFaultActionSpec(client),\n\t\t\t\tNewCniDelFaultActionSpec(client),\n\t\t\t},\n\t\t\tExpFlags: []spec.ExpFlagSpec{},\n\t\t},\n\t}\n}\n\ntype CniExpModelCommandSpec struct {\n\tspec.BaseExpModelCommandSpec\n}\n\nfunc (*CniExpModelCommandSpec) Name() string {\n\treturn \"cni\"\n}\n\nfunc (*CniExpModelCommandSpec) ShortDesc() string {\n\treturn \"CNI fault experiment\"\n}\n\nfunc (*CniExpModelCommandSpec) LongDesc() string {\n\treturn \"CNI fault experiment, simulate CNI plugin failures on the node\"\n}\n\nfunc (*CniExpModelCommandSpec) Example() string {\n\treturn `# Auto-discover CNI binary\nblade create k8s node-cni add_fault --names cn-hangzhou.192.168.0.205 --kubeconfig ~/.kube/config\n# Or specify explicitly\nblade create k8s node-cni add_fault --cni-bin-path /opt/cni/bin/calico --names cn-hangzhou.192.168.0.205 --kubeconfig ~/.kube/config`\n}\n\n// CniAddFaultActionSpec\n\nfunc NewCniAddFaultActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &CniAddFaultActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: CniBinPathFlag,\n\t\t\t\t\tDesc: \"The full path of the CNI plugin binary. If not specified, auto-discovered from kubelet CNI config\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: CniErrorMsgFlag,\n\t\t\t\t\tDesc: \"Custom error message returned by the CNI plugin failure\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor:   &CniFaultExecutor{client: client, cniCommand: \"ADD\"},\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t\tActionExample: `# Simulate CNI ADD failure with auto-discovered CNI binary\nblade create k8s node-cni add_fault --names cn-hangzhou.192.168.0.205 --kubeconfig ~/.kube/config\n# Or specify the CNI binary path explicitly\nblade create k8s node-cni add_fault --cni-bin-path /opt/cni/bin/calico --names cn-hangzhou.192.168.0.205 --kubeconfig ~/.kube/config\n# With custom error message\nblade create k8s node-cni add_fault --cni-bin-path /opt/cni/bin/calico --error-msg \"network unavailable\" --names cn-hangzhou.192.168.0.205 --kubeconfig ~/.kube/config`,\n\t\t},\n\t}\n}\n\ntype CniAddFaultActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc (*CniAddFaultActionSpec) Name() string {\n\treturn \"add_fault\"\n}\n\nfunc (*CniAddFaultActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*CniAddFaultActionSpec) ShortDesc() string {\n\treturn \"Simulate CNI ADD failure\"\n}\n\nfunc (*CniAddFaultActionSpec) LongDesc() string {\n\treturn \"Simulate CNI ADD failure, new pods will be stuck in ContainerCreating\"\n}\n\n// CniDelFaultActionSpec\n\nfunc NewCniDelFaultActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &CniDelFaultActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: CniBinPathFlag,\n\t\t\t\t\tDesc: \"The full path of the CNI plugin binary. If not specified, auto-discovered from kubelet CNI config\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: CniErrorMsgFlag,\n\t\t\t\t\tDesc: \"Custom error message returned by the CNI plugin failure\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor:   &CniFaultExecutor{client: client, cniCommand: \"DEL\"},\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t\tActionExample: `# Simulate CNI DEL failure with auto-discovered CNI binary\nblade create k8s node-cni del_fault --names cn-hangzhou.192.168.0.205 --kubeconfig ~/.kube/config\n# Or specify the CNI binary path explicitly\nblade create k8s node-cni del_fault --cni-bin-path /opt/cni/bin/calico --names cn-hangzhou.192.168.0.205 --kubeconfig ~/.kube/config`,\n\t\t},\n\t}\n}\n\ntype CniDelFaultActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc (*CniDelFaultActionSpec) Name() string {\n\treturn \"del_fault\"\n}\n\nfunc (*CniDelFaultActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*CniDelFaultActionSpec) ShortDesc() string {\n\treturn \"Simulate CNI DEL failure\"\n}\n\nfunc (*CniDelFaultActionSpec) LongDesc() string {\n\treturn \"Simulate CNI DEL failure, terminating pods will be stuck\"\n}\n\n// CniFaultExecutor\n\ntype CniFaultExecutor struct {\n\tclient     *channel.Client\n\tcniCommand string // \"ADD\" or \"DEL\"\n}\n\nfunc (e *CniFaultExecutor) Name() string {\n\treturn \"cni_fault\"\n}\n\nfunc (e *CniFaultExecutor) SetChannel(channel spec.Channel) {\n}\n\nfunc (e *CniFaultExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn e.destroy(uid, ctx, expModel)\n\t}\n\treturn e.create(uid, ctx, expModel)\n}\n\nfunc (e *CniFaultExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx))\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{}))\n\t}\n\n\tcniBinPath := expModel.ActionFlags[CniBinPathFlag]\n\n\terrorMsg := expModel.ActionFlags[CniErrorMsgFlag]\n\tif errorMsg == \"\" {\n\t\terrorMsg = fmt.Sprintf(\"chaosblade: simulated CNI %s failure\", e.cniCommand)\n\t}\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := true\n\tupdateLock := &sync.Mutex{}\n\n\texecFunc := func(i int) {\n\t\tmeta := containerObjectMetaList[i]\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       \"node\",\n\t\t\tIdentifier: meta.GetIdentifier(),\n\t\t\tId:         uid,\n\t\t}\n\n\t\tdaemonsetPodName, err := model.GetChaosBladeDaemonsetPodName(meta.NodeName, e.client)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"get chaosblade daemonset pod on node %s failed: %v\", meta.NodeName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t\tupdateLock.Lock()\n\t\t\tstatuses = append(statuses, status)\n\t\t\tsuccess = false\n\t\t\tupdateLock.Unlock()\n\t\t\treturn\n\t\t}\n\t\tif daemonsetPodName == \"\" {\n\t\t\terrMsg := fmt.Sprintf(\"chaosblade daemonset pod not found on node %s\", meta.NodeName)\n\t\t\tlogrusField.Error(errMsg)\n\t\t\tstatus = status.CreateFailResourceStatus(errMsg, spec.K8sExecFailed.Code)\n\t\t\tupdateLock.Lock()\n\t\t\tstatuses = append(statuses, status)\n\t\t\tsuccess = false\n\t\t\tupdateLock.Unlock()\n\t\t\treturn\n\t\t}\n\n\t\tresolvedPath := cniBinPath\n\t\tif resolvedPath == \"\" {\n\t\t\tdiscovered, discoverErr := discoverCniBinPath(e.client, daemonsetPodName)\n\t\t\tif discoverErr != nil {\n\t\t\t\terrMsg := fmt.Sprintf(\"auto-discover CNI binary on node %s failed: %v\", meta.NodeName, discoverErr)\n\t\t\t\tlogrusField.Error(errMsg)\n\t\t\t\tstatus = status.CreateFailResourceStatus(errMsg, spec.K8sExecFailed.Code)\n\t\t\t\tupdateLock.Lock()\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tsuccess = false\n\t\t\t\tupdateLock.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogrusField.Infof(\"auto-discovered CNI binary: %s on node %s\", discovered, meta.NodeName)\n\t\t\tresolvedPath = discovered\n\t\t}\n\n\t\tscript := generateCniCreateScript(resolvedPath, e.cniCommand, errorMsg)\n\t\tresp := execScriptInDaemonsetPod(e.client, daemonsetPodName, script)\n\t\tif resp.Success {\n\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t} else {\n\t\t\tstatus = status.CreateFailResourceStatus(resp.Err, spec.K8sExecFailed.Code)\n\t\t}\n\t\tupdateLock.Lock()\n\t\tif !resp.Success {\n\t\t\tsuccess = false\n\t\t}\n\t\tstatuses = append(statuses, status)\n\t\tupdateLock.Unlock()\n\t}\n\n\tmodel.ParallelizeExec(len(containerObjectMetaList), execFunc)\n\tlogrusField.Infof(\"cni %s fault create result, success: %t, statuses: %+v\", e.cniCommand, success, statuses)\n\n\tif success {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateSuccessExperimentStatus(statuses))\n\t}\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses))\n}\n\nfunc (e *CniFaultExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx))\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{}))\n\t}\n\n\tcniBinPath := expModel.ActionFlags[CniBinPathFlag]\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := true\n\tupdateLock := &sync.Mutex{}\n\n\texecFunc := func(i int) {\n\t\tmeta := containerObjectMetaList[i]\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       \"node\",\n\t\t\tIdentifier: meta.GetIdentifier(),\n\t\t\tId:         meta.Id,\n\t\t\tState:      v1alpha1.DestroyedState,\n\t\t}\n\n\t\tdaemonsetPodName, err := model.GetChaosBladeDaemonsetPodName(meta.NodeName, e.client)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"get chaosblade daemonset pod on node %s failed: %v\", meta.NodeName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t\tupdateLock.Lock()\n\t\t\tstatuses = append(statuses, status)\n\t\t\tsuccess = false\n\t\t\tupdateLock.Unlock()\n\t\t\treturn\n\t\t}\n\t\tif daemonsetPodName == \"\" {\n\t\t\terrMsg := fmt.Sprintf(\"chaosblade daemonset pod not found on node %s\", meta.NodeName)\n\t\t\tlogrusField.Error(errMsg)\n\t\t\tstatus = status.CreateFailResourceStatus(errMsg, spec.K8sExecFailed.Code)\n\t\t\tupdateLock.Lock()\n\t\t\tstatuses = append(statuses, status)\n\t\t\tsuccess = false\n\t\t\tupdateLock.Unlock()\n\t\t\treturn\n\t\t}\n\n\t\tresolvedPath := cniBinPath\n\t\tif resolvedPath == \"\" {\n\t\t\tdiscovered, discoverErr := discoverCniBinPath(e.client, daemonsetPodName)\n\t\t\tif discoverErr != nil {\n\t\t\t\terrMsg := fmt.Sprintf(\"auto-discover CNI binary on node %s failed: %v\", meta.NodeName, discoverErr)\n\t\t\t\tlogrusField.Error(errMsg)\n\t\t\t\tstatus = status.CreateFailResourceStatus(errMsg, spec.K8sExecFailed.Code)\n\t\t\t\tupdateLock.Lock()\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tsuccess = false\n\t\t\t\tupdateLock.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogrusField.Infof(\"auto-discovered CNI binary: %s on node %s\", discovered, meta.NodeName)\n\t\t\tresolvedPath = discovered\n\t\t}\n\n\t\tscript := generateCniDestroyScript(resolvedPath)\n\t\tresp := execScriptInDaemonsetPod(e.client, daemonsetPodName, script)\n\t\tif resp.Success {\n\t\t\tstatus.Success = true\n\t\t} else {\n\t\t\tstatus = status.CreateFailResourceStatus(resp.Err, spec.K8sExecFailed.Code)\n\t\t}\n\t\tupdateLock.Lock()\n\t\tif !resp.Success {\n\t\t\tsuccess = false\n\t\t}\n\t\tstatuses = append(statuses, status)\n\t\tupdateLock.Unlock()\n\t}\n\n\tmodel.ParallelizeExec(len(containerObjectMetaList), execFunc)\n\tlogrusField.Infof(\"cni fault destroy result, success: %t, statuses: %+v\", success, statuses)\n\n\tif success {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus(statuses))\n\t}\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses))\n}\n\nfunc generateCniCreateScript(cniBinPath string, cniCommand string, errorMsg string) string {\n\tbackupPath := cniBinPath + \".chaosblade.bak\"\n\n\t// Build the CNI error JSON template using encoding/json for proper escaping.\n\t// Use a placeholder for cniVersion which is determined at runtime from stdin.\n\ttype cniError struct {\n\t\tCniVersion string `json:\"cniVersion\"`\n\t\tCode       int    `json:\"code\"`\n\t\tMsg        string `json:\"msg\"`\n\t}\n\terrJSON, _ := json.Marshal(cniError{CniVersion: \"@@CNI_VER@@\", Code: 100, Msg: errorMsg})\n\tb64ErrJSON := base64.StdEncoding.EncodeToString(errJSON)\n\n\t// The wrapper script: fail for targeted CNI_COMMAND, passthrough for all others\n\t// Per CNI spec: error result MUST be written to stdout (not stderr), and exit non-zero\n\t// We read stdin to extract cniVersion from network config for spec compliance\n\twrapperContent := fmt.Sprintf(`#!/bin/sh\nif [ \"$CNI_COMMAND\" = \"%s\" ]; then\n  cni_input=$(cat)\n  cni_ver=$(echo \"$cni_input\" | grep -o '\"cniVersion\" *: *\"[^\"]*\"' | head -1 | grep -o '[0-9][0-9.]*')\n  [ -z \"$cni_ver\" ] && cni_ver=\"0.3.1\"\n  printf '%%s' '%s' | base64 -d | sed \"s/@@CNI_VER@@/$cni_ver/\"\n  echo\n  exit 1\nfi\nexec %s \"$@\"\n`, cniCommand, b64ErrJSON, backupPath)\n\n\t// Use base64 encoding to safely transport the wrapper content through shell\n\t// This avoids all complex single-quote escaping issues\n\tb64Content := base64.StdEncoding.EncodeToString([]byte(wrapperContent))\n\n\tscript := fmt.Sprintf(`BIN_PATH='%s'\nBACKUP_PATH='%s'\nB64_CONTENT='%s'\nif [ ! -f \"$BIN_PATH\" ]; then\n  echo '{\"code\":404,\"success\":false,\"error\":\"CNI binary not found: '\"$BIN_PATH\"'\"}'\n  exit 0\nfi\nif [ -f \"$BACKUP_PATH\" ]; then\n  echo '{\"code\":409,\"success\":false,\"error\":\"CNI fault already injected, backup exists: '\"$BACKUP_PATH\"'\"}'\n  exit 0\nfi\nmv \"$BIN_PATH\" \"$BACKUP_PATH\" 2>/dev/null\nif [ $? -ne 0 ]; then\n  echo '{\"code\":500,\"success\":false,\"error\":\"failed to backup CNI binary, permission denied or read-only filesystem\"}'\n  exit 0\nfi\necho \"$B64_CONTENT\" | base64 -d > \"$BIN_PATH\"\nif [ $? -ne 0 ]; then\n  mv \"$BACKUP_PATH\" \"$BIN_PATH\" 2>/dev/null\n  echo '{\"code\":500,\"success\":false,\"error\":\"failed to write wrapper script, rolling back\"}'\n  exit 0\nfi\nchmod +x \"$BIN_PATH\"\necho '{\"code\":200,\"success\":true}'\n`, cniBinPath, backupPath, b64Content)\n\n\treturn script\n}\n\nfunc generateCniDiscoverScript() string {\n\treturn `# Find kubelet PID (take first match in case of multiple)\nKUBELET_PID=$(pgrep -x kubelet 2>/dev/null | head -1)\nif [ -z \"$KUBELET_PID\" ]; then\n  for p in /proc/[0-9]*/comm; do\n    if [ -f \"$p\" ] && grep -qx kubelet \"$p\" 2>/dev/null; then\n      KUBELET_PID=$(echo \"$p\" | cut -d/ -f3)\n      break\n    fi\n  done\nfi\nif [ -z \"$KUBELET_PID\" ]; then\n  echo '{\"code\":500,\"success\":false,\"error\":\"kubelet process not found\"}'\n  exit 0\nfi\n\n# Parse kubelet cmdline\nCMDLINE=$(cat /proc/$KUBELET_PID/cmdline 2>/dev/null | tr '\\0' ' ')\nif [ -z \"$CMDLINE\" ]; then\n  echo '{\"code\":500,\"success\":false,\"error\":\"failed to read kubelet cmdline\"}'\n  exit 0\nfi\n\n# Extract --cni-bin-dir (supports both --flag=value and --flag value forms)\nCNI_BIN_DIR=$(echo \"$CMDLINE\" | grep -o '\\-\\-cni-bin-dir=[^ ]*' | head -1 | cut -d= -f2)\nif [ -z \"$CNI_BIN_DIR\" ]; then\n  CNI_BIN_DIR=$(echo \"$CMDLINE\" | sed -n 's/.*--cni-bin-dir  *\\([^ ]*\\).*/\\1/p')\nfi\n[ -z \"$CNI_BIN_DIR\" ] && CNI_BIN_DIR=\"/opt/cni/bin\"\n\n# Extract --cni-conf-dir (supports both --flag=value and --flag value forms)\nCNI_CONF_DIR=$(echo \"$CMDLINE\" | grep -o '\\-\\-cni-conf-dir=[^ ]*' | head -1 | cut -d= -f2)\nif [ -z \"$CNI_CONF_DIR\" ]; then\n  CNI_CONF_DIR=$(echo \"$CMDLINE\" | sed -n 's/.*--cni-conf-dir  *\\([^ ]*\\).*/\\1/p')\nfi\n[ -z \"$CNI_CONF_DIR\" ] && CNI_CONF_DIR=\"/etc/cni/net.d\"\n\n# Find first conflist or conf file (alphabetically, same as kubelet)\nCONF_FILE=$(ls -1 \"$CNI_CONF_DIR\"/*.conflist 2>/dev/null | sort | head -1)\nif [ -z \"$CONF_FILE\" ]; then\n  CONF_FILE=$(ls -1 \"$CNI_CONF_DIR\"/*.conf 2>/dev/null | sort | head -1)\nfi\nif [ -z \"$CONF_FILE\" ]; then\n  echo '{\"code\":500,\"success\":false,\"error\":\"no CNI config files found in '\"$CNI_CONF_DIR\"'\"}'\n  exit 0\nfi\n\n# Extract \"type\" field from config\nCNI_TYPE=$(grep -o '\"type\" *: *\"[^\"]*\"' \"$CONF_FILE\" | head -1 | grep -o '\"[^\"]*\"$' | tr -d '\"')\nif [ -z \"$CNI_TYPE\" ]; then\n  echo '{\"code\":500,\"success\":false,\"error\":\"cannot extract CNI type from '\"$CONF_FILE\"'\"}'\n  exit 0\nfi\n\n# Compose full binary path and verify\nCNI_BIN_PATH=\"${CNI_BIN_DIR}/${CNI_TYPE}\"\nif [ ! -f \"$CNI_BIN_PATH\" ]; then\n  echo '{\"code\":500,\"success\":false,\"error\":\"CNI binary not found at '\"$CNI_BIN_PATH\"'\"}'\n  exit 0\nfi\n\necho '{\"code\":200,\"success\":true,\"result\":\"'\"$CNI_BIN_PATH\"'\"}'\n`\n}\n\nfunc discoverCniBinPath(client *channel.Client, daemonsetPodName string) (string, error) {\n\tscript := generateCniDiscoverScript()\n\tresp := execScriptInDaemonsetPod(client, daemonsetPodName, script)\n\tif !resp.Success {\n\t\treturn \"\", fmt.Errorf(\"%s\", resp.Err)\n\t}\n\tpath, ok := resp.Result.(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"unexpected discovery result type: %T\", resp.Result)\n\t}\n\treturn path, nil\n}\n\nfunc generateCniDestroyScript(cniBinPath string) string {\n\tbackupPath := cniBinPath + \".chaosblade.bak\"\n\tscript := fmt.Sprintf(`BIN_PATH='%s'\nBACKUP_PATH='%s'\nif [ ! -f \"$BACKUP_PATH\" ]; then\n  echo '{\"code\":200,\"success\":true}'\n  exit 0\nfi\nrm -f \"$BIN_PATH\" 2>/dev/null\nmv \"$BACKUP_PATH\" \"$BIN_PATH\" 2>/dev/null\nif [ $? -ne 0 ]; then\n  echo '{\"code\":500,\"success\":false,\"error\":\"failed to restore CNI binary\"}'\n  exit 0\nfi\necho '{\"code\":200,\"success\":true}'\n`, cniBinPath, backupPath)\n\n\treturn script\n}\n"
  },
  {
    "path": "exec/node/controller.go",
    "content": "/*\n * Copyright 1999-2020 Alibaba Group Holding Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage node\n\nimport (\n\t\"context\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\ntype ExpController struct {\n\tmodel.BaseExperimentController\n}\n\nfunc NewExpController(client *channel.Client) model.ExperimentController {\n\treturn &ExpController{\n\t\tmodel.BaseExperimentController{\n\t\t\tClient:            client,\n\t\t\tResourceModelSpec: NewResourceModelSpec(client),\n\t\t},\n\t}\n}\n\nfunc (*ExpController) Name() string {\n\treturn \"node\"\n}\n\nfunc (e *ExpController) Create(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response {\n\texpModel := model.ExtractExpModelFromExperimentSpec(expSpec)\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\t// get nodes\n\tnodes, resp := e.getMatchedNodeResources(ctx, *expModel)\n\tif !resp.Success {\n\t\tlogrusField.Errorf(\"uid: %s, get matched node resources failed, %v\", experimentId, resp.Err)\n\t\tresp.Result = v1alpha1.CreateFailExperimentStatus(resp.Err, []v1alpha1.ResourceStatus{})\n\t\treturn resp\n\t}\n\tlogrusField.Infof(\"creating node experiment, node count is %d\", len(nodes))\n\tcontainerMatchedList := getContainerMatchedList(nodes)\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerMatchedList)\n\treturn e.Exec(ctx, expModel)\n}\n\nfunc (e *ExpController) Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) *spec.Response {\n\tlogrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx)).Infoln(\"start to destroy\")\n\texpModel := model.ExtractExpModelFromExperimentSpec(expSpec)\n\tstatuses := oldExpStatus.ResStatuses\n\tif statuses == nil {\n\t\treturn spec.ReturnSuccess(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{}))\n\t}\n\tcontainerObjectMetaList := model.ContainerMatchedList{}\n\tfor _, status := range statuses {\n\t\tif !status.Success {\n\t\t\tcontinue\n\t\t}\n\t\tcontainerObjectMeta := model.ParseIdentifier(status.Identifier)\n\t\tcontainerObjectMeta.Id = status.Id\n\t\tcontainerObjectMetaList = append(containerObjectMetaList, containerObjectMeta)\n\t}\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn e.Exec(ctx, expModel)\n}\n\n// getContainerMatchedList transports selected pods\nfunc getContainerMatchedList(nodes []v1.Node) model.ContainerMatchedList {\n\tcontainerObjectMetaList := model.ContainerMatchedList{}\n\tfor _, n := range nodes {\n\t\tcontainerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{\n\t\t\tNodeName: n.Name,\n\t\t})\n\t}\n\treturn containerObjectMetaList\n}\n"
  },
  {
    "path": "exec/node/exec_helper.go",
    "content": "/*\n * Copyright 1999-2020 Alibaba Group Holding Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage node\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n)\n\n// execScriptInDaemonsetPod executes a shell script inside the chaosblade daemonset pod\n// using nsenter to enter the host mount namespace.\nfunc execScriptInDaemonsetPod(client *channel.Client, podName string, script string) *spec.Response {\n\t// Prepend exec 2>/dev/null to suppress stderr from script commands (mv, chmod, etc.)\n\t// This is critical because client.Exec prioritizes stderr over stdout -\n\t// any stderr output would cause the JSON response from stdout to be ignored.\n\t// Note: nsenter's own errors (e.g., command not found, permission denied) happen\n\t// BEFORE the script runs, so those still properly surface as errors.\n\tfullScript := \"exec 2>/dev/null\\n\" + script\n\tcmd := []string{\"nsenter\", \"-t\", \"1\", \"-m\", \"--\", \"sh\", \"-c\", fullScript}\n\tlogrus.Infof(\"exec in daemonset pod %s/%s, container: %s\", chaosblade.DaemonsetPodNamespace, podName, chaosblade.DaemonsetPodName)\n\tresponse := client.Exec(&channel.ExecOptions{\n\t\tStreamOptions: channel.StreamOptions{\n\t\t\tIOStreams: channel.IOStreams{\n\t\t\t\tOut:    bytes.NewBuffer([]byte{}),\n\t\t\t\tErrOut: bytes.NewBuffer([]byte{}),\n\t\t\t},\n\t\t\tErrDecoder: func(bytes []byte) interface{} {\n\t\t\t\tcontent := string(bytes)\n\t\t\t\treturn spec.Decode(content, spec.ResponseFailWithFlags(spec.K8sExecFailed, \"pods/exec\", content))\n\t\t\t},\n\t\t\tOutDecoder: func(bytes []byte) interface{} {\n\t\t\t\tcontent := string(bytes)\n\t\t\t\treturn spec.Decode(content, spec.ResponseFailWithFlags(spec.K8sExecFailed, \"pods/exec\", content))\n\t\t\t},\n\t\t},\n\t\tPodName:       podName,\n\t\tPodNamespace:  chaosblade.DaemonsetPodNamespace,\n\t\tContainerName: chaosblade.DaemonsetPodName,\n\t\tCommand:       cmd,\n\t}).(*spec.Response)\n\treturn response\n}\n"
  },
  {
    "path": "exec/node/filter.go",
    "content": "/*\n * Copyright 1999-2020 Alibaba Group Holding Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage node\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tpkglabels \"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n)\n\nfunc (e *ExpController) getMatchedNodeResources(ctx context.Context, expModel spec.ExpModel) ([]v1.Node, *spec.Response) {\n\tflags := expModel.ActionFlags\n\tif resp := model.CheckFlags(flags); !resp.Success {\n\t\treturn nil, resp\n\t}\n\tnodes, resp := resourceFunc(ctx, e.Client, flags)\n\tif !resp.Success {\n\t\treturn nil, resp\n\t}\n\treturn e.filterByOtherFlags(nodes, flags)\n}\n\nfunc (e *ExpController) filterByOtherFlags(nodes []v1.Node, flags map[string]string) ([]v1.Node, *spec.Response) {\n\tgroupKey := flags[model.ResourceGroupKeyFlag.Name]\n\tif groupKey == \"\" {\n\t\tcount, resp := model.GetResourceCount(len(nodes), flags)\n\t\treturn nodes[:count], resp\n\t}\n\tgroupNodes := make(map[string][]v1.Node, 0)\n\tkeys := strings.Split(groupKey, \",\")\n\tfor _, node := range nodes {\n\t\tfor _, key := range keys {\n\t\t\tnodeList := groupNodes[node.Labels[key]]\n\t\t\tif nodeList == nil {\n\t\t\t\tnodeList = make([]v1.Node, 0)\n\t\t\t}\n\t\t\tnodeList = append(nodeList, node)\n\t\t}\n\t}\n\tresult := make([]v1.Node, 0)\n\tfor _, nodeList := range groupNodes {\n\t\tcount, resp := model.GetResourceCount(len(nodeList), flags)\n\t\tif !resp.Success {\n\t\t\treturn nodes[:count], resp\n\t\t}\n\t\tresult = append(result, nodeList[:count]...)\n\t}\n\treturn result, spec.Success()\n}\n\nvar resourceFunc = func(ctx context.Context, client2 *channel.Client, flags map[string]string) ([]v1.Node, *spec.Response) {\n\tlabels := flags[model.ResourceLabelsFlag.Name]\n\trequirements := model.ParseLabels(labels)\n\tlogrusField := logrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx))\n\tnodes := make([]v1.Node, 0)\n\tnames := flags[model.ResourceNamesFlag.Name]\n\tif names != \"\" {\n\t\tnameArr := strings.Split(names, \",\")\n\t\tfor _, name := range nameArr {\n\t\t\tnode := v1.Node{}\n\t\t\terr := client2.Get(context.TODO(), types.NamespacedName{Name: name}, &node)\n\t\t\tif err != nil {\n\t\t\t\t// Skip the invalid name\n\t\t\t\tlogrusField.Warningf(\"can not find the node by %s name, %v\", name, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(requirements) > 0 {\n\t\t\t\tif model.MapContains(node.Labels, requirements) {\n\t\t\t\t\tnodes = append(nodes, node)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tnodes = append(nodes, node)\n\t\t\t}\n\t\t}\n\t\tlogrusField.Infof(\"get nodes by name %s, len is %d\", names, len(nodes))\n\t\tif len(nodes) == 0 {\n\t\t\treturn nodes, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sNodeQuery, names)\n\t\t}\n\t\treturn nodes, spec.Success()\n\t}\n\tif labels != \"\" && len(requirements) == 0 {\n\t\tlogrusField.Warningln(spec.ParameterIllegal.Sprintf(model.ResourceLabelsFlag.Name, labels, \"illegal labels\"))\n\t\treturn nodes, spec.ResponseFailWithFlags(spec.ParameterIllegal, model.ResourceLabelsFlag.Name, labels, \"illegal labels\")\n\t}\n\tif len(requirements) > 0 {\n\t\tnodeList := v1.NodeList{}\n\t\tselector := pkglabels.NewSelector().Add(requirements...)\n\t\topts := client.ListOptions{LabelSelector: selector}\n\t\terr := client2.List(context.TODO(), &nodeList, &opts)\n\t\tif err != nil {\n\t\t\treturn nodes, spec.ResponseFailWithFlags(spec.K8sExecFailed, \"ListNode\", err)\n\t\t}\n\t\tnodes = nodeList.Items\n\t\tlogrusField.Infof(\"get nodes by labels %s, len is %d\", labels, len(nodes))\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sNodeQuery, labels)\n\t}\n\treturn nodes, spec.Success()\n}\n"
  },
  {
    "path": "exec/node/node.go",
    "content": "/*\n * Copyright 1999-2020 Alibaba Group Holding Ltd.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage node\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/cpu\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/disk\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/file\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/mem\"\n\tosModel \"github.com/chaosblade-io/chaosblade-exec-os/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/network\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/network/tc\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/process\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/script\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n)\n\ntype ResourceModelSpec struct {\n\tmodel.BaseResourceExpModelSpec\n}\n\nfunc NewResourceModelSpec(client *channel.Client) model.ResourceExpModelSpec {\n\tmodelSpec := &ResourceModelSpec{\n\t\tmodel.NewBaseResourceExpModelSpec(\"node\", client),\n\t}\n\tosModelSpecs := model.NewOSSubResourceModelSpec().ExpModels()\n\tspec.AddExecutorToModelSpec(&model.CommonExecutor{Client: client}, osModelSpecs...)\n\tselfModelSpec := NewSelfExpModelCommandSpec()\n\texpModelSpecs := append(osModelSpecs, selfModelSpec)\n\tspec.AddFlagsToModelSpec(getResourceFlags, expModelSpecs...)\n\tspec.AddFlagsToModelSpec(osModel.GetSSHExpFlags, expModelSpecs...)\n\tmodelSpec.RegisterExpModels(osModelSpecs...)\n\n\t// Register CNI fault injection model spec\n\tcniModelSpec := NewCniExpModelCommandSpec(client)\n\tspec.AddFlagsToModelSpec(getResourceFlags, cniModelSpec)\n\tmodelSpec.RegisterExpModels(cniModelSpec)\n\n\taddActionExamples(modelSpec)\n\treturn modelSpec\n}\n\nfunc addActionExamples(modelSpec *ResourceModelSpec) {\n\tfor _, expModelSpec := range modelSpec.ExpModelSpecs {\n\t\tfor _, action := range expModelSpec.Actions() {\n\t\t\tv := interface{}(action)\n\t\t\tswitch v.(type) {\n\t\t\tcase *cpu.FullLoadActionCommand:\n\t\t\t\taction.SetLongDesc(\"The CPU load experiment scenario for k8s node\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Create a CPU full load experiment in the node\n## using SSH channel \nblade create k8s node-cpu load --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-cpu load --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n#Specifies two random kernel's full load in the node\n## using SSH channel \nblade create k8s node-cpu load --cpu-percent 60 --cpu-count 2 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-cpu load --cpu-percent 60 --cpu-count 2 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Specifies that the kernel is full load with index 0, 3, and that the kernel's index starts at 0\n## using SSH channel \nblade create k8s node-cpu load --cpu-list 0,3 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-cpu load --cpu-list 0,3 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Specify the kernel full load of indexes 1-3\n## using SSH channel\nblade create k8s node-cpu load --cpu-list 1-3 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-cpu load --cpu-list 1-3 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Specified percentage load in the node\n## using SSH channel\nblade create k8s node-cpu load --cpu-percent 60 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-cpu load --cpu-percent 60 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *tc.DelayActionSpec:\n\t\t\t\taction.SetLongDesc(` The network delay experiment scenario for k8s node.\n!!! Using DaemonSet may result in failure to use the kubernetes API for destroy experiment.\n!!! Please use caution, add a timeout parameter for automatic destroy, or use the SSH channel.\n`)\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Access to native 8080 and 8081 ports is delayed by 3 seconds, and the delay time fluctuates by 1 second\n## using SSH channel\nblade create k8s node-network delay --time 3000 --offset 1000 --interface eth0 --local-port 8080,8081 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network delay --time 3000 --offset 1000 --interface eth0 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Local access to external 14.215.177.39 machine (ping www.baidu.com obtained IP) port 80 delay of 3 seconds\n## using SSH channel\nblade create k8s node-network delay --time 3000 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network delay --time 3000 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Do a 5 second delay for the entire network card eth0, excluding ports 22 and 8000 to 8080\n## using SSH channel\nblade create k8s node-network delay --time 5000 --interface eth0 --exclude-port 22,8000-8080 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network delay --time 5000 --interface eth0 --exclude-port 22,8000-8080 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *network.DropActionSpec:\n\t\t\t\taction.SetLongDesc(`!!! Using DaemonSet may result in failure to use the kubernetes API for destroy experiment.\n!!! Please use caution, add a timeout parameter for automatic destroy, or use the SSH channel.`)\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Experimental scenario of network shielding\n## using SSH channel\nblade create k8s node-network drop --source-port 80 --network-traffic in --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network drop --source-port 80 --network-traffic in --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *network.DnsActionSpec:\n\t\t\t\taction.SetLongDesc(`\n!!! Using DaemonSet may result in failure to use the kubernetes API for destroy experiment.\n!!! Please use caution, add a timeout parameter for automatic destroy, or use the SSH channel.`)\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The domain name www.baidu.com is not accessible\n## using SSH channel\nblade create k8s node-network dns --domain www.baidu.com --ip 10.0.0.0 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network dns --domain www.baidu.com --ip 10.0.0.0 --channel ssh --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *tc.LossActionSpec:\n\t\t\t\taction.SetLongDesc(`\n!!! Using DaemonSet may result in failure to use the kubernetes API for destroy experiment.\n!!! Please use caution, add a timeout parameter for automatic destroy, or use the SSH channel.`)\n\t\t\t\taction.SetExample(`# Access to native 8080 and 8081 ports lost 70% of packets\n## using SSH channel\nblade create k8s node-network loss --percent 70 --interface eth0 --local-port 8080,8081 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network loss --percent 70 --interface eth0 --local-port 8080,8081 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# The machine accesses external 14.215.177.39 machine (ping www.baidu.com) 80 port packet loss rate 100%\n## using SSH channel\nblade create k8s node-network loss --percent 100 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network loss --percent 100 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Do 60% packet loss for the entire network card Eth0, excluding ports 22 and 8000 to 8080\n## using SSH channel\nblade create k8s node-network loss --percent 60 --interface eth0 --exclude-port 22,8000-8080 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network loss --percent 60 --interface eth0 --exclude-port 22,8000-8080 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Realize the whole network card is not accessible, not accessible time 20 seconds. After executing the following command, the current network is disconnected and restored in 20 seconds. Remember!! Don't forget -timeout parameter\n## using SSH channel\nblade create k8s node-network loss --percent 100 --interface eth0 --timeout 20 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network loss --percent 100 --interface eth0 --timeout 20 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config`)\n\t\t\tcase *tc.DuplicateActionSpec:\n\t\t\t\taction.SetLongDesc(`\n!!! Using DaemonSet may result in failure to use the kubernetes API for destroy experiment.\n!!! Please use caution, add a timeout parameter for automatic destroy, or use the SSH channel.`)\n\t\t\t\taction.SetExample(`# Specify the network card eth0 and repeat the packet by 10%\n## using SSH channel\nblade create k8s node-network duplicate --percent=10 --interface=eth0 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network duplicate --percent=10 --interface=eth0 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`)\n\t\t\tcase *tc.CorruptActionSpec:\n\t\t\t\taction.SetLongDesc(`\n!!! Using DaemonSet may result in failure to use the kubernetes API for destroy experiment.\n!!! Please use caution, add a timeout parameter for automatic destroy, or use the SSH channel.`)\n\t\t\t\taction.SetExample(`# Access to the specified IP request packet is corrupted, 80% of the time\n## using SSH channel\nblade create k8s node-network corrupt --percent 80 --destination-ip 180.101.49.12 --interface eth0 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network corrupt --percent 80 --destination-ip 180.101.49.12 --interface eth0 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`)\n\t\t\tcase *tc.ReorderActionSpec:\n\t\t\t\taction.SetLongDesc(`\n!!! Using DaemonSet may result in failure to use the kubernetes API for destroy experiment.\n!!! Please use caution, add a timeout parameter for automatic destroy, or use the SSH channel.`)\n\t\t\t\taction.SetExample(`# Access the specified IP request packet disorder\n## using SSH channel\nblade create k8s node-network reorder --correlation 80 --percent 50 --gap 2 --time 500 --interface eth0 --destination-ip 180.101.49.12 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network reorder --correlation 80 --percent 50 --gap 2 --time 500 --interface eth0 --destination-ip 180.101.49.12 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`)\n\t\t\tcase *network.OccupyActionSpec:\n\t\t\t\taction.SetLongDesc(`\n!!! Using DaemonSet may result in failure to use the kubernetes API for destroy experiment.\n!!! Please use caution, add a timeout parameter for automatic destroy, or use the SSH channel.`)\n\t\t\t\taction.SetExample(`#Specify port 8080 occupancy\n## using SSH channel\nblade create k8s node-network occupy --port 8080 --force --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network occupy --port 8080 --force --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# The machine accesses external 14.215.177.39 machine (ping www.baidu.com) 80 port packet loss rate 100%\n## using SSH channel\nblade create k8s node-network loss --percent 100 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-network loss --percent 100 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`)\n\t\t\tcase *process.KillProcessActionCommandSpec:\n\t\t\t\taction.SetLongDesc(\"The process scenario in container is the same as the basic resource process scenario\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`\n# Kill the nginx process in the node\n## using SSH channel\nblade create k8s node-process kill --process nginx --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-process kill --process nginx --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Specifies the signal and local port to kill the process in the node\n## using SSH channel\nblade create k8s node-process kill --local-port 8080 --signal 15 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-process kill --local-port 8080 --signal 15 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\n\t\t\tcase *process.StopProcessActionCommandSpec:\n\t\t\t\taction.SetLongDesc(\"The process scenario in container is the same as the basic resource process scenario\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`\n# Pause the process that contains the \"nginx\" keyword in the node\n## using SSH channel\nblade create k8s node-process stop --process nginx --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-process stop --process nginx --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Pause the Java process in the node\n## using SSH channel\nblade create k8s node-process stop --process-cmd java --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-process stop --process-cmd java --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *disk.FillActionSpec:\n\t\t\t\taction.SetLongDesc(\"The disk fill scenario experiment in the node\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`\n# Fill the /home directory with 40G of disk space in the node\n## using SSH channel\nblade create k8s node-disk fill --path /home --size 40000 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-disk fill --path /home --size 40000 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Fill the /home directory with 80% of the disk space in the node and retains the file handle that populates the disk\n## using SSH channel\nblade create k8s node-disk fill --path /home --percent 80 --retain-handle --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-disk fill --path /home --percent 80 --retain-handle --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Perform a fixed-size experimental scenario in the node\n## using SSH channel\nblade c k8s node-disk fill --path /home --reserve 1024 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade c k8s node-disk fill --path /home --reserve 1024 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *disk.BurnActionSpec:\n\t\t\t\taction.SetLongDesc(\"Disk read and write IO load experiment in the node\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The data of rkB/s, wkB/s and % Util were mainly observed. Perform disk read IO high-load scenarios\n## using SSH channel\nblade create k8s node-disk burn --read --path /home --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-disk burn --read --path /home --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Perform disk write IO high-load scenarios\n## using SSH channel\nblade create k8s node-disk burn --write --path /home --channel ssh --ssh-host 192.168.1.100 --ssh-user root8\n## using DaemonSet\nblade create k8s node-disk burn --write --path /home --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Read and write IO load scenarios are performed at the same time. Path is not specified. The default is\n## using SSH channel\nblade create k8s node-disk burn --read --write --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-disk burn --read --write --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *disk.UnmountStuckActionSpec:\n\t\t\t\taction.SetLongDesc(\"Simulate volume unmount stuck by holding file handles in the node\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Simulate volume unmount stuck on /mnt/data in the node\n## using SSH channel\nblade create k8s node-disk unmount_stuck --path /mnt/data --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-disk unmount_stuck --path /mnt/data --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *mem.MemLoadActionCommand:\n\t\t\t\taction.SetLongDesc(\"The memory fill experiment scenario in container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The execution memory footprint is 50%\n## using SSH channel\nblade create k8s node-mem load --mode ram --mem-percent 50 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-mem load --mode ram --mem-percent 50 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# The execution memory footprint is 50%, cache model\n## using SSH channel\nblade create k8s node-mem load --mode cache --mem-percent 50 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-mem load --mode cache --mem-percent 50 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# The execution memory footprint is 50%, usage contains buffer/cache\n## using SSH channel\nblade create k8s node-mem load --mode ram --mem-percent 50 --include-buffer-cache --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-mem load --mode ram --mem-percent 50 --include-buffer-cache --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# The execution memory footprint is 50% for 200 seconds\n## using SSH channel\nblade create k8s node-mem load --mode ram --mem-percent 50 --timeout 200 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-mem load --mode ram --mem-percent 50 --timeout 200 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config\n\n# 200M memory is reserved\n## using SSH channel\nblade create k8s node-mem load --mode ram --reserve 200 --rate 100 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-mem load --mode ram --reserve 200 --rate 100 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *file.FileAppendActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file append experiment scenario in container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file\nblade create k8s node-file append --filepath=/home/logs/nginx.log --content=\"HELL WORLD\" --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file, interval 10 seconds\nblade create k8s node-file append --filepath=/home/logs/nginx.log --content=\"HELL WORLD\" --interval 10 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file, enable base64 encoding\nblade create k8s node-file append --filepath=/home/logs/nginx.log --content=SEVMTE8gV09STEQ= --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# mock interface timeout exception\nblade create k8s node-file append --filepath=/home/logs/nginx.log --content=\"@{DATE:+%Y-%m-%d %H:%M:%S} ERROR invoke getUser timeout [@{RANDOM:100-200}]ms abc  mock exception\" --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *file.FileAddActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file add experiment scenario in container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Create a file named nginx.log in the /home directory\nblade create k8s node-file add --filepath /home/nginx.log --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Create a file named nginx.log in the /home directory with the contents of HELLO WORLD\nblade create k8s node-file add --filepath /home/nginx.log --content \"HELLO WORLD\" --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Create a file named nginx.log in the /temp directory and automatically create directories that don't exist\nblade create k8s node-file add --filepath /temp/nginx.log --auto-create-dir --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Create a directory named /nginx in the /temp directory and automatically create directories that don't exist\nblade create k8s node-file add --directory --filepath /temp/nginx --auto-create-dir --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *file.FileChmodActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file permission modification scenario in container\")\n\t\t\t\taction.SetExample(`# Modify /home/logs/nginx.log file permissions to 777\nblade create k8s node-file chmod --filepath /home/logs/nginx.log --mark=777 --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`)\n\t\t\tcase *file.FileDeleteActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file delete scenario in container\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Delete the file /home/logs/nginx.log\nblade create k8s node-file delete --filepath /home/logs/nginx.log --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Force delete the file /home/logs/nginx.log unrecoverable\nblade create k8s node-file delete --filepath /home/logs/nginx.log --force --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *file.FileMoveActionSpec:\n\t\t\t\taction.SetExample(\"The file move scenario in container\")\n\t\t\t\taction.SetExample(`# Move the file /home/logs/nginx.log to /tmp\nblade create k8s node-file move --filepath /home/logs/nginx.log --target /tmp --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Force Move the file /home/logs/nginx.log to /temp\nblade create k8s node-file move --filepath /home/logs/nginx.log --target /tmp --force --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n\n# Move the file /home/logs/nginx.log to /temp/ and automatically create directories that don't exist\nblade create k8s node-file move --filepath /home/logs/nginx.log --target /temp --auto-create-dir --names nginx-app --container-ids f1de335b4eeaf --kubeconfig ~/.kube/config --namespace default\n`)\n\t\t\tcase *file.FileFdleakActionCommandSpec:\n\t\t\t\taction.SetLongDesc(\"The file descriptor leak experiment scenario in the node, which causes disk space usage to increase\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Occupy about 50% of disk space with a leaked unlinked file in the node\n## using SSH channel\nblade create k8s node-file fdleak --percent 50 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-file fdleak --percent 50 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30\n\n# Occupy about 80% of disk space in the /tmp directory\n## using SSH channel\nblade create k8s node-file fdleak --percent 80 --directory /tmp --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-file fdleak --percent 80 --directory /tmp --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`,\n\t\t\t\t)\n\t\t\tcase *script.ScriptDelayActionCommand:\n\t\t\t\taction.SetExample(`\n# Add commands to the script \"start0() { sleep 10.000000 ...}\"\n## using SSH channel\nblade create k8s node-script delay --time 10000 --file test.sh --function-name start0 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-script delay --time 10000 --file test.sh --function-name start0 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`)\n\t\t\tcase *script.ScriptExitActionCommand:\n\t\t\t\taction.SetExample(`\n# Add commands to the script \"start0() { echo this-is-error-message; exit 1; ... }\"\n## using SSH channel\nblade create k8s node-script exit --exit-code 1 --exit-message this-is-error-message --file test.sh --function-name start0 --channel ssh --ssh-host 192.168.1.100 --ssh-user root\n## using DaemonSet\nblade create k8s node-script exit --exit-code 1 --exit-message this-is-error-message --file test.sh --function-name start0 --names izbp1a4jchbdwkwi5hk7ekz --kubeconfig ~/.kube/config --timeout 30`)\n\t\t\tdefault:\n\t\t\t\taction.SetExample(strings.Replace(\n\t\t\t\t\taction.Example(),\n\t\t\t\t\tfmt.Sprintf(\"blade create %s %s\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\tfmt.Sprintf(\"blade create k8s node-%s %s --names nginx-app --channel ssh --ssh-host 192.168.1.100 --ssh-user root\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\t-1,\n\t\t\t\t))\n\t\t\t\taction.SetExample(strings.Replace(\n\t\t\t\t\taction.Example(),\n\t\t\t\t\tfmt.Sprintf(\"blade c %s %s\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\tfmt.Sprintf(\"blade c k8s node-%s %s --names nginx-app --channel ssh --ssh-host 192.168.1.100 --ssh-user root\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\t-1,\n\t\t\t\t))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc getResourceFlags() []spec.ExpFlagSpec {\n\tcoverageFlags := model.GetResourceCoverageFlags()\n\treturn append(coverageFlags, model.ResourceNamesFlag, model.ResourceLabelsFlag)\n}\n\nfunc NewSelfExpModelCommandSpec() spec.ExpModelCommandSpec {\n\treturn &SelfExpModelCommandSpec{\n\t\tspec.BaseExpModelCommandSpec{\n\t\t\tExpFlags:   []spec.ExpFlagSpec{},\n\t\t\tExpActions: []spec.ExpActionCommandSpec{\n\t\t\t\t// TODO\n\t\t\t\t// NewCordonActionCommandSpec(),\n\t\t\t},\n\t\t},\n\t}\n}\n\ntype SelfExpModelCommandSpec struct {\n\tspec.BaseExpModelCommandSpec\n}\n\nfunc (*SelfExpModelCommandSpec) Name() string {\n\treturn \"node\"\n}\n\nfunc (*SelfExpModelCommandSpec) ShortDesc() string {\n\treturn \"Node resource experiment for itself, for example cpu load\"\n}\n\nfunc (*SelfExpModelCommandSpec) LongDesc() string {\n\treturn \"Node resource experiment for itself, for example cpu load\"\n}\n\nfunc (*SelfExpModelCommandSpec) Example() string {\n\treturn \"blade c k8s node-cpu load --evict-count 1 --kubeconfig ~/.kube/config --names cn-hangzhou.192.168.0.205\"\n}\n"
  },
  {
    "path": "exec/pod/badresourcesize.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\tBadResourceSizeCPUFlag = \"cpu\"\n\tBadResourceSizeMemFlag = \"mem\"\n\n\tChaosBladeOriginalResourcesAnnotation = \"chaosblade.io/original-resources\"\n\tChaosBladeBadResourceSizeAction       = \"badresourcesize\"\n)\n\ntype BadResourceSizeActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n\tclient *channel.Client\n}\n\nfunc NewBadResourceSizeActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &BadResourceSizeActionSpec{\n\t\tBaseExpActionCommandSpec: spec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"workload-type\",\n\t\t\t\t\tDesc:     \"Workload type: deployment, daemonset, statefulset. Default: deployment\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t\tDefault:  \"deployment\",\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"workload-name\",\n\t\t\t\t\tDesc:     \"Workload name to modify resource size\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     BadResourceSizeCPUFlag,\n\t\t\t\t\tDesc:     \"CPU resource limit to set, e.g. 1m, 5m\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     BadResourceSizeMemFlag,\n\t\t\t\t\tDesc:     \"Memory resource limit to set, e.g. 128m, 256m\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &BadResourceSizeActionExecutor{client: client},\n\t\t\tActionExample: `# Set CPU resource limit for a deployment\nblade create k8s pod-pod badresourcesize --namespace default --workload-type deployment --workload-name nginx-app --cpu 1m --kubeconfig ~/.kube/config\n\n# Set memory resource limit for a deployment\nblade create k8s pod-pod badresourcesize --namespace default --workload-type deployment --workload-name nginx-app --mem 128m --kubeconfig ~/.kube/config\n\n# Set both CPU and memory resource limits for a deployment\nblade create k8s pod-pod badresourcesize --namespace default --workload-type deployment --workload-name nginx-app --cpu 1m --mem 128m --kubeconfig ~/.kube/config\n\n# Set resource limits for a statefulset\nblade create k8s pod-pod badresourcesize --namespace default --workload-type statefulset --workload-name redis-app --cpu 1m --mem 128m --kubeconfig ~/.kube/config\n\n# Set resource limits for a daemonset\nblade create k8s pod-pod badresourcesize --namespace default --workload-type daemonset --workload-name fluentd --cpu 1m --mem 128m --kubeconfig ~/.kube/config\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t\tclient: client,\n\t}\n}\n\nfunc (*BadResourceSizeActionSpec) Name() string {\n\treturn \"badresourcesize\"\n}\n\nfunc (*BadResourceSizeActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*BadResourceSizeActionSpec) ShortDesc() string {\n\treturn \"Modify workload pod resource limits to simulate bad resource sizing\"\n}\n\nfunc (*BadResourceSizeActionSpec) LongDesc() string {\n\treturn \"Modify the CPU/Memory resource limits of a workload (Deployment/DaemonSet/StatefulSet) \" +\n\t\t\"to simulate incorrect resource sizing. The original resource configuration is backed up \" +\n\t\t\"in an annotation and restored when the experiment is destroyed. \" +\n\t\t\"Existing container-level and pod-level resource settings are removed, \" +\n\t\t\"and new pod-level resource limits are applied.\"\n}\n\n// PreCreate implements model.ActionPreProcessor.\nfunc (a *BadResourceSizeActionSpec) PreCreate(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) (context.Context, *spec.Response) {\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\n\tcpuVal := expModel.ActionFlags[BadResourceSizeCPUFlag]\n\tmemVal := expModel.ActionFlags[BadResourceSizeMemFlag]\n\tif cpuVal == \"\" && memVal == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"cpu or mem (at least one is required)\")\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-brs-%s-%s\", workloadType, workloadName),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\n// PreDestroy implements model.ActionPreProcessor.\nfunc (a *BadResourceSizeActionSpec) PreDestroy(ctx context.Context, expModel *spec.ExpModel, client *channel.Client, oldExpStatus v1alpha1.ExperimentStatus) (context.Context, *spec.Response) {\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-brs-%s-%s\", workloadType, workloadName),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\ntype BadResourceSizeActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*BadResourceSizeActionExecutor) Name() string {\n\treturn \"badresourcesize\"\n}\n\nfunc (*BadResourceSizeActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *BadResourceSizeActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *BadResourceSizeActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\tcpuVal := expModel.ActionFlags[BadResourceSizeCPUFlag]\n\tmemVal := expModel.ActionFlags[BadResourceSizeMemFlag]\n\n\tif namespace == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"namespace is required\")\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"workload-name is required\")\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\tif cpuVal == \"\" && memVal == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"at least one of cpu or mem is required\")\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, \"cpu or mem\")\n\t}\n\n\tnewLimits, err := buildResourceLimits(cpuVal, memVal)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), fmt.Sprintf(\"parse resource values failed: %v\", err))\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterIllegal, \"cpu/mem\", err.Error())\n\t}\n\n\tstatus := v1alpha1.ResourceStatus{\n\t\tKind:       v1alpha1.PodKind,\n\t\tIdentifier: fmt.Sprintf(\"%s//%s//%s\", namespace, workloadType, workloadName),\n\t}\n\n\tswitch workloadType {\n\tcase \"deployment\":\n\t\tdeployment := &appsv1.Deployment{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, deployment)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"deployment %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"deployment not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get deployment %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get deployment failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\tif err := d.injectDeploymentBadResourceSize(ctx, deployment, newLimits, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject bad resource size to deployment %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject bad resource size failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected bad resource size to deployment %s/%s\", namespace, workloadName)\n\n\tcase \"daemonset\":\n\t\tdaemonset := &appsv1.DaemonSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, daemonset)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"daemonset %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"daemonset not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get daemonset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get daemonset failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\tif err := d.injectDaemonSetBadResourceSize(ctx, daemonset, newLimits, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject bad resource size to daemonset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject bad resource size failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected bad resource size to daemonset %s/%s\", namespace, workloadName)\n\n\tcase \"statefulset\":\n\t\tstatefulset := &appsv1.StatefulSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, statefulset)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"statefulset %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"statefulset not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get statefulset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get statefulset failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\tif err := d.injectStatefulSetBadResourceSize(ctx, statefulset, newLimits, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject bad resource size to statefulset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject bad resource size failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected bad resource size to statefulset %s/%s\", namespace, workloadName)\n\n\tdefault:\n\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"unsupported workload type: %s\", workloadType), spec.ParameterIllegal.Code)\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t}\n\n\tstatus = status.CreateSuccessResourceStatus()\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{status}))\n}\n\nfunc (d *BadResourceSizeActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tstatus := v1alpha1.ResourceStatus{\n\t\tKind:       v1alpha1.PodKind,\n\t\tIdentifier: fmt.Sprintf(\"%s//%s//%s\", namespace, workloadType, workloadName),\n\t}\n\n\tswitch workloadType {\n\tcase \"deployment\":\n\t\tdeployment := &appsv1.Deployment{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, deployment)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\t\tif err := d.restoreDeploymentResources(ctx, deployment, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore deployment %s/%s resources failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore deployment resources failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored deployment %s/%s resources\", namespace, workloadName)\n\n\tcase \"daemonset\":\n\t\tdaemonset := &appsv1.DaemonSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, daemonset)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\t\tif err := d.restoreDaemonSetResources(ctx, daemonset, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore daemonset %s/%s resources failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore daemonset resources failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored daemonset %s/%s resources\", namespace, workloadName)\n\n\tcase \"statefulset\":\n\t\tstatefulset := &appsv1.StatefulSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, statefulset)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\t\tif err := d.restoreStatefulSetResources(ctx, statefulset, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore statefulset %s/%s resources failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore statefulset resources failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored statefulset %s/%s resources\", namespace, workloadName)\n\n\tdefault:\n\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"unsupported workload type: %s\", workloadType), spec.ParameterIllegal.Code)\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t}\n\n\tstatus = status.CreateSuccessResourceStatus()\n\tstatus.State = v1alpha1.DestroyedState\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{status}))\n}\n\n// containerResourcesBackup holds the original resource settings for all containers in a pod template,\n// keyed by container name for resilience against container additions/removals during the experiment.\ntype containerResourcesBackup struct {\n\t// ResourcesByName maps container name to its original ResourceRequirements.\n\tResourcesByName map[string]v1.ResourceRequirements `json:\"resourcesByName\"`\n\t// InitResourcesByName maps init container name to its original ResourceRequirements.\n\tInitResourcesByName map[string]v1.ResourceRequirements `json:\"initResourcesByName,omitempty\"`\n}\n\n// buildResourceLimits parses the cpu/mem flag values into a v1.ResourceList for Limits.\nfunc buildResourceLimits(cpuVal, memVal string) (v1.ResourceList, error) {\n\tlimits := v1.ResourceList{}\n\tif cpuVal != \"\" {\n\t\tq, err := resource.ParseQuantity(cpuVal)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid cpu value %q: %v\", cpuVal, err)\n\t\t}\n\t\tlimits[v1.ResourceCPU] = q\n\t}\n\tif memVal != \"\" {\n\t\tmemQuantity := normalizeMemoryValue(memVal)\n\t\tq, err := resource.ParseQuantity(memQuantity)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid mem value %q: %v\", memVal, err)\n\t\t}\n\t\tlimits[v1.ResourceMemory] = q\n\t}\n\treturn limits, nil\n}\n\n// normalizeMemoryValue converts shorthand like \"128m\" to the Kubernetes-compatible \"128Mi\".\n// Kubernetes uses binary suffixes (Ki, Mi, Gi) for memory.\n// If the value already uses a standard suffix or is purely numeric, it is returned as-is.\nfunc normalizeMemoryValue(val string) string {\n\tif len(val) == 0 {\n\t\treturn val\n\t}\n\tlastChar := val[len(val)-1]\n\tif lastChar == 'm' || lastChar == 'M' {\n\t\tprefix := val[:len(val)-1]\n\t\tif _, err := resource.ParseQuantity(prefix); err == nil {\n\t\t\treturn prefix + \"Mi\"\n\t\t}\n\t}\n\tif lastChar == 'g' || lastChar == 'G' {\n\t\tprefix := val[:len(val)-1]\n\t\tif _, err := resource.ParseQuantity(prefix); err == nil {\n\t\t\treturn prefix + \"Gi\"\n\t\t}\n\t}\n\tif lastChar == 'k' || lastChar == 'K' {\n\t\tprefix := val[:len(val)-1]\n\t\tif _, err := resource.ParseQuantity(prefix); err == nil {\n\t\t\treturn prefix + \"Ki\"\n\t\t}\n\t}\n\treturn val\n}\n\n// backupAndInjectResources backs up original container resources (keyed by container name)\n// for both regular and init containers, then sets new resource limits on each container.\nfunc backupAndInjectResources(podSpec *v1.PodSpec, annotations map[string]string, newLimits v1.ResourceList) error {\n\tbackup := containerResourcesBackup{\n\t\tResourcesByName: make(map[string]v1.ResourceRequirements, len(podSpec.Containers)),\n\t}\n\tfor _, c := range podSpec.Containers {\n\t\tbackup.ResourcesByName[c.Name] = *c.Resources.DeepCopy()\n\t}\n\tif len(podSpec.InitContainers) > 0 {\n\t\tbackup.InitResourcesByName = make(map[string]v1.ResourceRequirements, len(podSpec.InitContainers))\n\t\tfor _, c := range podSpec.InitContainers {\n\t\t\tbackup.InitResourcesByName[c.Name] = *c.Resources.DeepCopy()\n\t\t}\n\t}\n\n\tbackupBytes, err := json.Marshal(backup)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal original resources failed: %v\", err)\n\t}\n\tannotations[ChaosBladeOriginalResourcesAnnotation] = string(backupBytes)\n\n\tfor i := range podSpec.Containers {\n\t\tpodSpec.Containers[i].Resources = v1.ResourceRequirements{\n\t\t\tLimits: newLimits.DeepCopy(),\n\t\t}\n\t}\n\tfor i := range podSpec.InitContainers {\n\t\tpodSpec.InitContainers[i].Resources = v1.ResourceRequirements{\n\t\t\tLimits: newLimits.DeepCopy(),\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// restoreResources restores original container resources from the backup annotation.\n// It uses best-effort matching by container name: containers that exist in both the\n// backup and the current spec are restored; new containers (not in backup) are left\n// untouched; removed containers (in backup but not in spec) are logged as warnings.\nfunc restoreResources(podSpec *v1.PodSpec, annotations map[string]string) error {\n\tbackupStr, ok := annotations[ChaosBladeOriginalResourcesAnnotation]\n\tif !ok || backupStr == \"\" {\n\t\treturn fmt.Errorf(\"original resources backup annotation not found\")\n\t}\n\n\tvar backup containerResourcesBackup\n\tif err := json.Unmarshal([]byte(backupStr), &backup); err != nil {\n\t\treturn fmt.Errorf(\"unmarshal original resources failed: %v\", err)\n\t}\n\n\tif len(backup.ResourcesByName) == 0 {\n\t\treturn fmt.Errorf(\"backup contains no container resources\")\n\t}\n\n\trestored := make(map[string]bool, len(backup.ResourcesByName))\n\tfor i := range podSpec.Containers {\n\t\tname := podSpec.Containers[i].Name\n\t\tif orig, found := backup.ResourcesByName[name]; found {\n\t\t\tpodSpec.Containers[i].Resources = orig\n\t\t\trestored[name] = true\n\t\t} else {\n\t\t\tlogrus.Warnf(\"container %q not found in backup, leaving its resources unchanged\", name)\n\t\t}\n\t}\n\tfor name := range backup.ResourcesByName {\n\t\tif !restored[name] {\n\t\t\tlogrus.Warnf(\"backed-up container %q no longer exists in pod spec, skipping restore\", name)\n\t\t}\n\t}\n\n\tif len(backup.InitResourcesByName) > 0 {\n\t\trestoredInit := make(map[string]bool, len(backup.InitResourcesByName))\n\t\tfor i := range podSpec.InitContainers {\n\t\t\tname := podSpec.InitContainers[i].Name\n\t\t\tif orig, found := backup.InitResourcesByName[name]; found {\n\t\t\t\tpodSpec.InitContainers[i].Resources = orig\n\t\t\t\trestoredInit[name] = true\n\t\t\t} else {\n\t\t\t\tlogrus.Warnf(\"init container %q not found in backup, leaving its resources unchanged\", name)\n\t\t\t}\n\t\t}\n\t\tfor name := range backup.InitResourcesByName {\n\t\t\tif !restoredInit[name] {\n\t\t\t\tlogrus.Warnf(\"backed-up init container %q no longer exists in pod spec, skipping restore\", name)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *BadResourceSizeActionExecutor) injectDeploymentBadResourceSize(ctx context.Context, deployment *appsv1.Deployment, newLimits v1.ResourceList, experimentId string) error {\n\tif deployment.Annotations == nil {\n\t\tdeployment.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(deployment.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\tif deployment.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tdeployment.Annotations[ChaosBladeDeploymentAnnotation] = ChaosBladeBadResourceSizeAction\n\tdeployment.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\tif err := backupAndInjectResources(&deployment.Spec.Template.Spec, deployment.Annotations, newLimits); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, deployment)\n}\n\nfunc (d *BadResourceSizeActionExecutor) injectDaemonSetBadResourceSize(ctx context.Context, daemonset *appsv1.DaemonSet, newLimits v1.ResourceList, experimentId string) error {\n\tif daemonset.Annotations == nil {\n\t\tdaemonset.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(daemonset.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\tif daemonset.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tdaemonset.Annotations[ChaosBladeDaemonSetAnnotation] = ChaosBladeBadResourceSizeAction\n\tdaemonset.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\tif err := backupAndInjectResources(&daemonset.Spec.Template.Spec, daemonset.Annotations, newLimits); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, daemonset)\n}\n\nfunc (d *BadResourceSizeActionExecutor) injectStatefulSetBadResourceSize(ctx context.Context, statefulset *appsv1.StatefulSet, newLimits v1.ResourceList, experimentId string) error {\n\tif statefulset.Annotations == nil {\n\t\tstatefulset.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(statefulset.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\tif statefulset.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tstatefulset.Annotations[ChaosBladeStatefulSetAnnotation] = ChaosBladeBadResourceSizeAction\n\tstatefulset.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\tif err := backupAndInjectResources(&statefulset.Spec.Template.Spec, statefulset.Annotations, newLimits); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, statefulset)\n}\n\nfunc (d *BadResourceSizeActionExecutor) restoreDeploymentResources(ctx context.Context, deployment *appsv1.Deployment, experimentId string) error {\n\tif deployment.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"deployment was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := restoreResources(&deployment.Spec.Template.Spec, deployment.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\tdelete(deployment.Annotations, ChaosBladeDeploymentAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeOriginalResourcesAnnotation)\n\n\treturn d.client.Update(ctx, deployment)\n}\n\nfunc (d *BadResourceSizeActionExecutor) restoreDaemonSetResources(ctx context.Context, daemonset *appsv1.DaemonSet, experimentId string) error {\n\tif daemonset.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"daemonset was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := restoreResources(&daemonset.Spec.Template.Spec, daemonset.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\tdelete(daemonset.Annotations, ChaosBladeDaemonSetAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeOriginalResourcesAnnotation)\n\n\treturn d.client.Update(ctx, daemonset)\n}\n\nfunc (d *BadResourceSizeActionExecutor) restoreStatefulSetResources(ctx context.Context, statefulset *appsv1.StatefulSet, experimentId string) error {\n\tif statefulset.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"statefulset was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := restoreResources(&statefulset.Spec.Template.Spec, statefulset.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\tdelete(statefulset.Annotations, ChaosBladeStatefulSetAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeOriginalResourcesAnnotation)\n\n\treturn d.client.Update(ctx, statefulset)\n}\n"
  },
  {
    "path": "exec/pod/configmapdeleteexp.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\tConfigMapNameFlag = \"configmap-name\"\n\n\tChaosBladeExperimentLabel      = \"chaosblade.io/experiment-id\"\n\tChaosBladeBackupLabel          = \"chaosblade.io/backup\"\n\tChaosBladeOriginalNameAnn      = \"chaosblade.io/original-name\"\n\tChaosBladeOriginalNamespaceAnn = \"chaosblade.io/original-namespace\"\n\tChaosBladeOriginalLabelsAnn    = \"chaosblade.io/original-labels\"\n\tChaosBladeOriginalAnnsAnn      = \"chaosblade.io/original-annotations\"\n)\n\ntype ConfigMapDeleteActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc NewConfigMapDeleteActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &ConfigMapDeleteActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: ConfigMapNameFlag,\n\t\t\t\t\tDesc: \"The ConfigMap name to delete. If not specified, the first non-optional ConfigMap from the Pod spec will be selected\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &ConfigMapDeleteActionExecutor{client: client},\n\t\t\tActionExample: `# Delete the auto-selected required ConfigMap for pods matching labels\nblade create k8s pod-pod configmapdelete --labels \"app=test\" --namespace default\n\n# Delete a specific ConfigMap\nblade create k8s pod-pod configmapdelete --labels \"app=test\" --namespace default --configmap-name my-config\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*ConfigMapDeleteActionSpec) Name() string {\n\treturn \"configmapdelete\"\n}\n\nfunc (*ConfigMapDeleteActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*ConfigMapDeleteActionSpec) ShortDesc() string {\n\treturn \"Delete ConfigMap to simulate Pod startup failure\"\n}\n\nfunc (*ConfigMapDeleteActionSpec) LongDesc() string {\n\treturn \"Delete a ConfigMap that a Pod depends on, then restart the Pod to simulate startup failure \" +\n\t\t\"caused by missing ConfigMap. The original ConfigMap is backed up and restored when the experiment is destroyed.\"\n}\n\ntype ConfigMapDeleteActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*ConfigMapDeleteActionExecutor) Name() string {\n\treturn \"configmapdelete\"\n}\n\nfunc (*ConfigMapDeleteActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *ConfigMapDeleteActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(ctx, expModel)\n\t}\n\treturn d.create(ctx, expModel)\n}\n\n// create backs up the target ConfigMap, deletes it, then deletes the Pod to trigger a restart failure.\nfunc (d *ConfigMapDeleteActionExecutor) create(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tconfigMapName := expModel.ActionFlags[ConfigMapNameFlag]\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerMatchedList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\tprocessedCMs := make(map[string]bool)\n\n\tfor _, c := range containerMatchedList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\n\t\t// Step 1: Fetch the Pod\n\t\tpod := &v1.Pod{}\n\t\tif err := d.client.Get(ctx, types.NamespacedName{Name: c.PodName, Namespace: c.Namespace}, pod); err != nil {\n\t\t\tlogrusField.Errorf(\"get pod %s/%s failed: %v\", c.Namespace, c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"get pod\", err), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Skip pods that are not in a healthy state\n\t\tif !isPodReady(pod) {\n\t\t\tlogrusField.Infof(\"pod %s/%s is not ready, skip\", c.Namespace, c.PodName)\n\t\t\tstatus = status.CreateFailResourceStatus(\n\t\t\t\tfmt.Sprintf(\"pod %s is not ready\", c.PodName), spec.K8sExecFailed.Code,\n\t\t\t)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Step 2: Resolve the target ConfigMap\n\t\tresolvedCMName, resolveErr := resolveTargetConfigMap(pod, configMapName)\n\t\tif resolveErr != nil {\n\t\t\tlogrusField.Errorf(\"resolve configmap for pod %s/%s failed: %v\", c.Namespace, c.PodName, resolveErr)\n\t\t\tstatus = status.CreateFailResourceStatus(resolveErr.Error(), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\tcmKey := fmt.Sprintf(\"%s/%s\", c.Namespace, resolvedCMName)\n\n\t\t// Step 3: Deduplicate — only backup & delete each ConfigMap once\n\t\tif !processedCMs[cmKey] {\n\t\t\t// Step 4: Fetch the original ConfigMap\n\t\t\toriginalCM := &v1.ConfigMap{}\n\t\t\tif err := d.client.Get(ctx, types.NamespacedName{Name: resolvedCMName, Namespace: c.Namespace}, originalCM); err != nil {\n\t\t\t\tlogrusField.Errorf(\"get configmap %s/%s failed: %v\", c.Namespace, resolvedCMName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(\n\t\t\t\t\tfmt.Sprintf(\"configmap %s not found in namespace %s\", resolvedCMName, c.Namespace), spec.K8sExecFailed.Code,\n\t\t\t\t)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Step 5: Create backup ConfigMap\n\t\t\t// (originalCM is declared inside the dedup block above)\n\t\t\tif err := d.createBackupConfigMap(ctx, experimentId, originalCM); err != nil {\n\t\t\t\tlogrusField.Errorf(\"create backup configmap for %s/%s failed: %v\", c.Namespace, resolvedCMName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(\n\t\t\t\t\tfmt.Sprintf(\"create backup configmap failed: %v\", err), spec.K8sExecFailed.Code,\n\t\t\t\t)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlogrusField.Infof(\"created backup configmap for %s/%s\", c.Namespace, resolvedCMName)\n\n\t\t\t// Step 6: Delete the original ConfigMap\n\t\t\tif err := d.client.Delete(ctx, originalCM); err != nil && !apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Errorf(\"delete configmap %s/%s failed: %v\", c.Namespace, resolvedCMName, err)\n\t\t\t\t// Rollback: delete the backup\n\t\t\t\tbackupName := getBackupConfigMapName(experimentId, c.Namespace, resolvedCMName)\n\t\t\t\tif rbErr := d.client.Delete(ctx, &v1.ConfigMap{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: backupName, Namespace: c.Namespace},\n\t\t\t\t}); rbErr != nil && !apierrors.IsNotFound(rbErr) {\n\t\t\t\t\tlogrusField.Warningf(\"rollback: delete backup configmap %s failed: %v\", backupName, rbErr)\n\t\t\t\t}\n\t\t\t\tstatus = status.CreateFailResourceStatus(\n\t\t\t\t\tfmt.Sprintf(\"delete configmap %s failed: %v\", resolvedCMName, err), spec.K8sExecFailed.Code,\n\t\t\t\t)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlogrusField.Infof(\"deleted configmap %s/%s\", c.Namespace, resolvedCMName)\n\n\t\t\t// Mark as processed only after both backup and delete succeed\n\t\t\tprocessedCMs[cmKey] = true\n\t\t}\n\n\t\t// Step 7: Delete the Pod to trigger rebuild\n\t\tif err := d.client.Delete(ctx, pod); err != nil && !apierrors.IsNotFound(err) {\n\t\t\tlogrusField.Errorf(\"delete pod %s/%s failed: %v\", c.Namespace, c.PodName, err)\n\t\t\t// The ConfigMap is already deleted. Attempt to restore it from backup.\n\t\t\tbackupName := getBackupConfigMapName(experimentId, c.Namespace, resolvedCMName)\n\t\t\tif restoreErr := d.restoreAndCleanupBackup(ctx, c.Namespace, backupName); restoreErr != nil {\n\t\t\t\tlogrusField.Errorf(\"rollback: restore configmap from backup %s failed: %v, manual intervention required\", backupName, restoreErr)\n\t\t\t\tstatus = status.CreateFailResourceStatus(\n\t\t\t\t\tfmt.Sprintf(\"configmap %s has been deleted but restore failed: %v, manual intervention required\", resolvedCMName, restoreErr),\n\t\t\t\t\tspec.K8sExecFailed.Code,\n\t\t\t\t)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tstatus = status.CreateFailResourceStatus(\n\t\t\t\tfmt.Sprintf(\"delete pod %s failed: %v\", c.PodName, err), spec.K8sExecFailed.Code,\n\t\t\t)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\t\tlogrusField.Infof(\"deleted pod %s/%s to trigger rebuild\", c.Namespace, c.PodName)\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatuses = append(statuses, status)\n\t\tsuccess = true\n\t}\n\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\n// destroy restores the backed-up ConfigMap and deletes the Pod to trigger a healthy restart.\nfunc (d *ConfigMapDeleteActionExecutor) destroy(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerMatchedList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\t// Find all backup ConfigMaps for this experiment across all relevant namespaces\n\tnamespaces := make(map[string]bool)\n\tfor _, c := range containerMatchedList {\n\t\tnamespaces[c.Namespace] = true\n\t}\n\n\t// Collect backup CMs by namespace\n\tallSuccess := true\n\tbackupCMs := make(map[string][]*v1.ConfigMap) // namespace -> list of backup CMs\n\tfor ns := range namespaces {\n\t\tcmList := &v1.ConfigMapList{}\n\t\tlabelSelector := labels.SelectorFromSet(labels.Set{\n\t\t\tChaosBladeExperimentLabel: experimentId,\n\t\t\tChaosBladeBackupLabel:     \"configmap\",\n\t\t})\n\t\tif err := d.client.List(ctx, cmList, &client.ListOptions{\n\t\t\tNamespace:     ns,\n\t\t\tLabelSelector: labelSelector,\n\t\t}); err != nil {\n\t\t\tlogrusField.Errorf(\"list backup configmaps in namespace %s failed: %v\", ns, err)\n\t\t\tallSuccess = false\n\t\t\tcontinue\n\t\t}\n\t\tfor i := range cmList.Items {\n\t\t\tbackupCMs[ns] = append(backupCMs[ns], &cmList.Items[i])\n\t\t}\n\t}\n\n\t// Restore all backup ConfigMaps\n\trestoredCMs := make(map[string]bool) // key: namespace/backupName\n\tfor ns, cms := range backupCMs {\n\t\tfor _, backupCM := range cms {\n\t\t\tbackupKey := fmt.Sprintf(\"%s/%s\", ns, backupCM.Name)\n\t\t\tif restoredCMs[backupKey] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trestoredCMs[backupKey] = true\n\n\t\t\tif err := d.restoreConfigMapFromBackup(ctx, backupCM); err != nil {\n\t\t\t\tlogrusField.Errorf(\"restore configmap from backup %s/%s failed: %v\", ns, backupCM.Name, err)\n\t\t\t\tallSuccess = false\n\t\t\t\t// Do NOT delete the backup when restore fails — preserve it for retry\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err := d.deleteBackupConfigMap(ctx, backupCM); err != nil {\n\t\t\t\tlogrusField.Warningf(\"delete backup configmap %s/%s failed: %v\", ns, backupCM.Name, err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Delete all matched Pods to trigger healthy rebuilds\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tfor _, c := range containerMatchedList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tId:         c.Id,\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\n\t\tpod := &v1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      c.PodName,\n\t\t\t\tNamespace: c.Namespace,\n\t\t\t},\n\t\t}\n\t\tif err := d.client.Delete(ctx, pod); err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Infof(\"pod %s/%s already deleted\", c.Namespace, c.PodName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Errorf(\"delete pod %s/%s failed: %v\", c.Namespace, c.PodName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(\n\t\t\t\t\tfmt.Sprintf(\"delete pod %s failed: %v\", c.PodName, err), spec.K8sExecFailed.Code,\n\t\t\t\t)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tallSuccess = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"deleted pod %s/%s to trigger healthy rebuild\", c.Namespace, c.PodName)\n\t\t}\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatus.State = v1alpha1.DestroyedState\n\t\tstatuses = append(statuses, status)\n\t}\n\n\tif allSuccess {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus(statuses))\n\t}\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses))\n}\n\n// createBackupConfigMap creates a backup copy of the original ConfigMap.\nfunc (d *ConfigMapDeleteActionExecutor) createBackupConfigMap(ctx context.Context, experimentId string, originalCM *v1.ConfigMap) error {\n\tbackupName := getBackupConfigMapName(experimentId, originalCM.Namespace, originalCM.Name)\n\n\tannotations := map[string]string{\n\t\tChaosBladeOriginalNameAnn:      originalCM.Name,\n\t\tChaosBladeOriginalNamespaceAnn: originalCM.Namespace,\n\t\tChaosBladeExperimentAnnotation: experimentId,\n\t}\n\n\t// Preserve original labels and annotations as JSON\n\tif len(originalCM.Labels) > 0 {\n\t\tif labelsJSON, err := json.Marshal(originalCM.Labels); err != nil {\n\t\t\tlogrus.Warningf(\"failed to marshal original labels for configmap %s/%s: %v\", originalCM.Namespace, originalCM.Name, err)\n\t\t} else {\n\t\t\tannotations[ChaosBladeOriginalLabelsAnn] = string(labelsJSON)\n\t\t}\n\t}\n\tif len(originalCM.Annotations) > 0 {\n\t\tif annsJSON, err := json.Marshal(originalCM.Annotations); err != nil {\n\t\t\tlogrus.Warningf(\"failed to marshal original annotations for configmap %s/%s: %v\", originalCM.Namespace, originalCM.Name, err)\n\t\t} else {\n\t\t\tannotations[ChaosBladeOriginalAnnsAnn] = string(annsJSON)\n\t\t}\n\t}\n\n\tbackupCM := &v1.ConfigMap{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      backupName,\n\t\t\tNamespace: originalCM.Namespace,\n\t\t\tLabels: map[string]string{\n\t\t\t\tChaosBladeExperimentLabel: experimentId,\n\t\t\t\tChaosBladeBackupLabel:     \"configmap\",\n\t\t\t},\n\t\t\tAnnotations: annotations,\n\t\t},\n\t\tData:       copyStringMap(originalCM.Data),\n\t\tBinaryData: copyByteMap(originalCM.BinaryData),\n\t}\n\n\tif err := d.client.Create(ctx, backupCM); err != nil {\n\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).Infof(\"backup configmap %s already exists, skip creation\", backupName)\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// restoreConfigMapFromBackup recreates the original ConfigMap from a backup ConfigMap object.\n// It reads the original name, namespace, labels, and annotations from the backup's annotations.\n// Returns nil if the original ConfigMap already exists (AlreadyExists is treated as success).\nfunc (d *ConfigMapDeleteActionExecutor) restoreConfigMapFromBackup(ctx context.Context, backupCM *v1.ConfigMap) error {\n\toriginalName := backupCM.Annotations[ChaosBladeOriginalNameAnn]\n\toriginalNamespace := backupCM.Annotations[ChaosBladeOriginalNamespaceAnn]\n\tif originalNamespace == \"\" {\n\t\toriginalNamespace = backupCM.Namespace\n\t}\n\n\t// Restore original labels\n\tvar originalLabels map[string]string\n\tif labelsJSON := backupCM.Annotations[ChaosBladeOriginalLabelsAnn]; labelsJSON != \"\" {\n\t\tif err := json.Unmarshal([]byte(labelsJSON), &originalLabels); err != nil {\n\t\t\tlogrus.Warningf(\"failed to unmarshal original labels from backup %s/%s: %v\", backupCM.Namespace, backupCM.Name, err)\n\t\t}\n\t}\n\t// Restore original annotations\n\tvar originalAnnotations map[string]string\n\tif annsJSON := backupCM.Annotations[ChaosBladeOriginalAnnsAnn]; annsJSON != \"\" {\n\t\tif err := json.Unmarshal([]byte(annsJSON), &originalAnnotations); err != nil {\n\t\t\tlogrus.Warningf(\"failed to unmarshal original annotations from backup %s/%s: %v\", backupCM.Namespace, backupCM.Name, err)\n\t\t}\n\t}\n\n\trestoredCM := &v1.ConfigMap{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        originalName,\n\t\t\tNamespace:   originalNamespace,\n\t\t\tLabels:      originalLabels,\n\t\t\tAnnotations: originalAnnotations,\n\t\t},\n\t\tData:       backupCM.Data,\n\t\tBinaryData: backupCM.BinaryData,\n\t}\n\tif err := d.client.Create(ctx, restoredCM); err != nil {\n\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\tlogrus.Infof(\"configmap %s/%s already exists, skip restore\", originalNamespace, originalName)\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"restore configmap %s/%s: %w\", originalNamespace, originalName, err)\n\t}\n\treturn nil\n}\n\n// deleteBackupConfigMap removes a backup ConfigMap. NotFound is treated as success.\nfunc (d *ConfigMapDeleteActionExecutor) deleteBackupConfigMap(ctx context.Context, backupCM *v1.ConfigMap) error {\n\tif err := d.client.Delete(ctx, backupCM); err != nil && !apierrors.IsNotFound(err) {\n\t\treturn fmt.Errorf(\"delete backup configmap %s/%s: %w\", backupCM.Namespace, backupCM.Name, err)\n\t}\n\treturn nil\n}\n\n// restoreAndCleanupBackup fetches the backup ConfigMap by name, restores the original, and deletes the backup.\n// Used during create-phase rollback when we need to look up the backup by name.\nfunc (d *ConfigMapDeleteActionExecutor) restoreAndCleanupBackup(ctx context.Context, namespace, backupName string) error {\n\tbackupCM := &v1.ConfigMap{}\n\tif err := d.client.Get(ctx, types.NamespacedName{Name: backupName, Namespace: namespace}, backupCM); err != nil {\n\t\treturn fmt.Errorf(\"get backup configmap %s/%s: %w\", namespace, backupName, err)\n\t}\n\n\tif err := d.restoreConfigMapFromBackup(ctx, backupCM); err != nil {\n\t\treturn err\n\t}\n\treturn d.deleteBackupConfigMap(ctx, backupCM)\n}\n\n// ConfigMapRef describes a ConfigMap reference found in a Pod spec.\ntype ConfigMapRef struct {\n\tName     string\n\tOptional bool\n\tSource   string // \"volume\", \"envFrom\", \"envValueFrom\"\n}\n\n// collectConfigMapReferences scans a Pod spec and returns all ConfigMap references.\n// It scans Volumes, EnvFrom and Env ValueFrom across all containers and init containers.\n// Duplicate names are merged: if any reference is required, the result is required.\nfunc collectConfigMapReferences(pod *v1.Pod) []ConfigMapRef {\n\tseen := make(map[string]int) // name -> index in result\n\tvar refs []ConfigMapRef\n\n\taddRef := func(name string, optional bool, source string) {\n\t\tif idx, exists := seen[name]; exists {\n\t\t\t// If any reference is required, mark as required\n\t\t\tif !optional {\n\t\t\t\trefs[idx].Optional = false\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tseen[name] = len(refs)\n\t\trefs = append(refs, ConfigMapRef{Name: name, Optional: optional, Source: source})\n\t}\n\n\t// Scan volumes first (highest priority for auto-selection)\n\tfor _, vol := range pod.Spec.Volumes {\n\t\tif vol.ConfigMap != nil {\n\t\t\taddRef(vol.ConfigMap.LocalObjectReference.Name, isOptional(vol.ConfigMap.Optional), \"volume\")\n\t\t}\n\t}\n\n\t// Scan all containers (regular + init)\n\tallContainers := make([]v1.Container, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))\n\tallContainers = append(allContainers, pod.Spec.Containers...)\n\tallContainers = append(allContainers, pod.Spec.InitContainers...)\n\tfor _, ctr := range allContainers {\n\t\tfor _, envFrom := range ctr.EnvFrom {\n\t\t\tif envFrom.ConfigMapRef != nil {\n\t\t\t\taddRef(envFrom.ConfigMapRef.LocalObjectReference.Name, isOptional(envFrom.ConfigMapRef.Optional), \"envFrom\")\n\t\t\t}\n\t\t}\n\t\tfor _, env := range ctr.Env {\n\t\t\tif env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil {\n\t\t\t\taddRef(env.ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name, isOptional(env.ValueFrom.ConfigMapKeyRef.Optional), \"envValueFrom\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn refs\n}\n\n// resolveTargetConfigMap determines which ConfigMap to target for chaos injection.\n// If userSpecifiedName is provided, it validates that the ConfigMap is referenced and non-optional.\n// Otherwise, it returns the first non-optional ConfigMap.\nfunc resolveTargetConfigMap(pod *v1.Pod, userSpecifiedName string) (string, error) {\n\trefs := collectConfigMapReferences(pod)\n\n\tif len(refs) == 0 {\n\t\treturn \"\", fmt.Errorf(\"pod %s has no ConfigMap dependency\", pod.Name)\n\t}\n\n\tif userSpecifiedName != \"\" {\n\t\tfor _, ref := range refs {\n\t\t\tif ref.Name == userSpecifiedName {\n\t\t\t\tif ref.Optional {\n\t\t\t\t\treturn \"\", fmt.Errorf(\"configmap %s is optional in pod %s, only required ConfigMaps can be deleted for chaos\", userSpecifiedName, pod.Name)\n\t\t\t\t}\n\t\t\t\treturn userSpecifiedName, nil\n\t\t\t}\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"configmap %s is not referenced by pod %s\", userSpecifiedName, pod.Name)\n\t}\n\n\t// Auto-select the first non-optional ConfigMap\n\tfor _, ref := range refs {\n\t\tif !ref.Optional {\n\t\t\treturn ref.Name, nil\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"pod %s has no required (non-optional) ConfigMap dependency\", pod.Name)\n}\n\n// isOptional returns true only when the *bool pointer is non-nil and true.\nfunc isOptional(opt *bool) bool {\n\treturn opt != nil && *opt\n}\n\n// getBackupConfigMapName generates a deterministic backup name from experiment ID and ConfigMap identity.\n// The experimentId is truncated to 8 chars to keep names short; the full ID is stored in labels for querying.\nfunc getBackupConfigMapName(experimentId, namespace, cmName string) string {\n\thash := sha256.Sum256([]byte(fmt.Sprintf(\"%s/%s/%s\", experimentId, namespace, cmName)))\n\thashStr := fmt.Sprintf(\"%x\", hash[:4])\n\n\texpIdPrefix := experimentId\n\tif len(expIdPrefix) > 8 {\n\t\texpIdPrefix = expIdPrefix[:8]\n\t}\n\n\treturn fmt.Sprintf(\"chaosblade-backup-%s-%s\", expIdPrefix, hashStr)\n}\n\nfunc copyStringMap(src map[string]string) map[string]string {\n\tif src == nil {\n\t\treturn nil\n\t}\n\tdst := make(map[string]string, len(src))\n\tfor k, v := range src {\n\t\tdst[k] = v\n\t}\n\treturn dst\n}\n\nfunc copyByteMap(src map[string][]byte) map[string][]byte {\n\tif src == nil {\n\t\treturn nil\n\t}\n\tdst := make(map[string][]byte, len(src))\n\tfor k, v := range src {\n\t\tcp := make([]byte, len(v))\n\t\tcopy(cp, v)\n\t\tdst[k] = cp\n\t}\n\treturn dst\n}\n"
  },
  {
    "path": "exec/pod/configmapdeleteexp_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc boolPtr(b bool) *bool {\n\treturn &b\n}\n\nfunc TestCollectConfigMapReferences(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tpod      *v1.Pod\n\t\twantLen  int\n\t\twantRefs []ConfigMapRef\n\t}{\n\t\t{\n\t\t\tname: \"volume configmap only\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"config-vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"app-config\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLen: 1,\n\t\t\twantRefs: []ConfigMapRef{\n\t\t\t\t{Name: \"app-config\", Optional: false, Source: \"volume\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"envFrom configmap optional\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"main\",\n\t\t\t\t\t\t\tEnvFrom: []v1.EnvFromSource{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tConfigMapRef: &v1.ConfigMapEnvSource{\n\t\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"env-config\"},\n\t\t\t\t\t\t\t\t\t\tOptional:             boolPtr(true),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLen: 1,\n\t\t\twantRefs: []ConfigMapRef{\n\t\t\t\t{Name: \"env-config\", Optional: true, Source: \"envFrom\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"env valueFrom configmap\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"main\",\n\t\t\t\t\t\t\tEnv: []v1.EnvVar{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"MY_VAR\",\n\t\t\t\t\t\t\t\t\tValueFrom: &v1.EnvVarSource{\n\t\t\t\t\t\t\t\t\t\tConfigMapKeyRef: &v1.ConfigMapKeySelector{\n\t\t\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"key-config\"},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLen: 1,\n\t\t\twantRefs: []ConfigMapRef{\n\t\t\t\t{Name: \"key-config\", Optional: false, Source: \"envValueFrom\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed sources with deduplication - required wins\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"shared-config\"},\n\t\t\t\t\t\t\t\t\tOptional:             boolPtr(true),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"main\",\n\t\t\t\t\t\t\tEnvFrom: []v1.EnvFromSource{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tConfigMapRef: &v1.ConfigMapEnvSource{\n\t\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"shared-config\"},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLen: 1,\n\t\t\twantRefs: []ConfigMapRef{\n\t\t\t\t{Name: \"shared-config\", Optional: false, Source: \"volume\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no configmap references\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{Name: \"main\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLen:  0,\n\t\t\twantRefs: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"init container configmap\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tInitContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"init\",\n\t\t\t\t\t\t\tEnvFrom: []v1.EnvFromSource{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tConfigMapRef: &v1.ConfigMapEnvSource{\n\t\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"init-config\"},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLen: 1,\n\t\t\twantRefs: []ConfigMapRef{\n\t\t\t\t{Name: \"init-config\", Optional: false, Source: \"envFrom\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple distinct configmaps ordered volume > envFrom > envValueFrom\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"vol-cm\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"main\",\n\t\t\t\t\t\t\tEnvFrom: []v1.EnvFromSource{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tConfigMapRef: &v1.ConfigMapEnvSource{\n\t\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"env-cm\"},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tEnv: []v1.EnvVar{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName: \"VAR\",\n\t\t\t\t\t\t\t\t\tValueFrom: &v1.EnvVarSource{\n\t\t\t\t\t\t\t\t\t\tConfigMapKeyRef: &v1.ConfigMapKeySelector{\n\t\t\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"key-cm\"},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLen: 3,\n\t\t\twantRefs: []ConfigMapRef{\n\t\t\t\t{Name: \"vol-cm\", Optional: false, Source: \"volume\"},\n\t\t\t\t{Name: \"env-cm\", Optional: false, Source: \"envFrom\"},\n\t\t\t\t{Name: \"key-cm\", Optional: false, Source: \"envValueFrom\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"volume with explicit optional false is required\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"explicit-required\"},\n\t\t\t\t\t\t\t\t\tOptional:             boolPtr(false),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantLen: 1,\n\t\t\twantRefs: []ConfigMapRef{\n\t\t\t\t{Name: \"explicit-required\", Optional: false, Source: \"volume\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := collectConfigMapReferences(tt.pod)\n\t\t\tif len(got) != tt.wantLen {\n\t\t\t\tt.Fatalf(\"collectConfigMapReferences() returned %d refs, want %d\", len(got), tt.wantLen)\n\t\t\t}\n\t\t\tfor i, want := range tt.wantRefs {\n\t\t\t\tif got[i].Name != want.Name {\n\t\t\t\t\tt.Errorf(\"ref[%d].Name = %q, want %q\", i, got[i].Name, want.Name)\n\t\t\t\t}\n\t\t\t\tif got[i].Optional != want.Optional {\n\t\t\t\t\tt.Errorf(\"ref[%d].Optional = %v, want %v\", i, got[i].Optional, want.Optional)\n\t\t\t\t}\n\t\t\t\tif got[i].Source != want.Source {\n\t\t\t\t\tt.Errorf(\"ref[%d].Source = %q, want %q\", i, got[i].Source, want.Source)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResolveTargetConfigMap(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\tpod               *v1.Pod\n\t\tuserSpecifiedName string\n\t\twantCM            string\n\t\twantErr           string\n\t}{\n\t\t{\n\t\t\tname: \"user specifies valid required configmap\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"test-pod\"},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"my-config\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tuserSpecifiedName: \"my-config\",\n\t\t\twantCM:            \"my-config\",\n\t\t},\n\t\t{\n\t\t\tname: \"user specifies optional configmap - error\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"test-pod\"},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"opt-config\"},\n\t\t\t\t\t\t\t\t\tOptional:             boolPtr(true),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tuserSpecifiedName: \"opt-config\",\n\t\t\twantErr:           \"optional\",\n\t\t},\n\t\t{\n\t\t\tname: \"user specifies non-existent configmap - error\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"test-pod\"},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"real-config\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tuserSpecifiedName: \"not-exist\",\n\t\t\twantErr:           \"not referenced\",\n\t\t},\n\t\t{\n\t\t\tname: \"auto select first required configmap\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"test-pod\"},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol1\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"first-config\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol2\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"second-config\"},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tuserSpecifiedName: \"\",\n\t\t\twantCM:            \"first-config\",\n\t\t},\n\t\t{\n\t\t\tname: \"auto select skips optional - picks required\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"test-pod\"},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"optional-config\"},\n\t\t\t\t\t\t\t\t\tOptional:             boolPtr(true),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"main\",\n\t\t\t\t\t\t\tEnvFrom: []v1.EnvFromSource{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tConfigMapRef: &v1.ConfigMapEnvSource{\n\t\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"required-config\"},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tuserSpecifiedName: \"\",\n\t\t\twantCM:            \"required-config\",\n\t\t},\n\t\t{\n\t\t\tname: \"all optional - error\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"test-pod\"},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tVolumes: []v1.Volume{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"vol\",\n\t\t\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: \"opt-config\"},\n\t\t\t\t\t\t\t\t\tOptional:             boolPtr(true),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tuserSpecifiedName: \"\",\n\t\t\twantErr:           \"no required\",\n\t\t},\n\t\t{\n\t\t\tname: \"no configmap at all - error\",\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: \"test-pod\"},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{{Name: \"main\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\tuserSpecifiedName: \"\",\n\t\t\twantErr:           \"no ConfigMap dependency\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := resolveTargetConfigMap(tt.pod, tt.userSpecifiedName)\n\t\t\tif tt.wantErr != \"\" {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatalf(\"resolveTargetConfigMap() returned nil error, want error containing %q\", tt.wantErr)\n\t\t\t\t}\n\t\t\t\tif !strings.Contains(err.Error(), tt.wantErr) {\n\t\t\t\t\tt.Errorf(\"resolveTargetConfigMap() error = %q, want to contain %q\", err.Error(), tt.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"resolveTargetConfigMap() returned unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif got != tt.wantCM {\n\t\t\t\tt.Errorf(\"resolveTargetConfigMap() = %q, want %q\", got, tt.wantCM)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetBackupConfigMapName(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\texperimentId string\n\t\tnamespace    string\n\t\tcmName       string\n\t}{\n\t\t{\n\t\t\tname:         \"basic\",\n\t\t\texperimentId: \"exp-12345\",\n\t\t\tnamespace:    \"default\",\n\t\t\tcmName:       \"my-config\",\n\t\t},\n\t\t{\n\t\t\tname:         \"with uuid\",\n\t\t\texperimentId: \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n\t\t\tnamespace:    \"production\",\n\t\t\tcmName:       \"app-settings\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult1 := getBackupConfigMapName(tt.experimentId, tt.namespace, tt.cmName)\n\t\t\tresult2 := getBackupConfigMapName(tt.experimentId, tt.namespace, tt.cmName)\n\n\t\t\t// Deterministic\n\t\t\tif result1 != result2 {\n\t\t\t\tt.Errorf(\"getBackupConfigMapName() not deterministic: %q != %q\", result1, result2)\n\t\t\t}\n\n\t\t\t// Valid DNS subdomain name (lowercase alphanumeric and hyphens)\n\t\t\tvalidDNS := regexp.MustCompile(`^[a-z0-9]([a-z0-9\\-]*[a-z0-9])?$`)\n\t\t\tif !validDNS.MatchString(result1) {\n\t\t\t\tt.Errorf(\"getBackupConfigMapName() result %q is not a valid DNS subdomain\", result1)\n\t\t\t}\n\n\t\t\t// Within K8s name limit\n\t\t\tif len(result1) > 253 {\n\t\t\t\tt.Errorf(\"getBackupConfigMapName() result length %d exceeds 253\", len(result1))\n\t\t\t}\n\t\t})\n\t}\n\n\t// Different inputs produce different outputs\n\tr1 := getBackupConfigMapName(\"exp1\", \"ns1\", \"cm1\")\n\tr2 := getBackupConfigMapName(\"exp1\", \"ns1\", \"cm2\")\n\tr3 := getBackupConfigMapName(\"exp1\", \"ns2\", \"cm1\")\n\tr4 := getBackupConfigMapName(\"exp2\", \"ns1\", \"cm1\")\n\n\tif r1 == r2 {\n\t\tt.Errorf(\"different cmName should produce different results: %q == %q\", r1, r2)\n\t}\n\tif r1 == r3 {\n\t\tt.Errorf(\"different namespace should produce different results: %q == %q\", r1, r3)\n\t}\n\tif r1 == r4 {\n\t\tt.Errorf(\"different experimentId should produce different results: %q == %q\", r1, r4)\n\t}\n\n\t// ExperimentId truncation: short IDs should not be truncated\n\tshortResult := getBackupConfigMapName(\"abc\", \"ns\", \"cm\")\n\tif !strings.Contains(shortResult, \"abc\") {\n\t\tt.Errorf(\"short experimentId should appear in name, got %q\", shortResult)\n\t}\n\n\t// ExperimentId truncation: long IDs should be truncated to 8 chars\n\tlongId := \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\n\tlongResult := getBackupConfigMapName(longId, \"ns\", \"cm\")\n\tif strings.Contains(longResult, longId) {\n\t\tt.Errorf(\"long experimentId should be truncated, but full ID found in %q\", longResult)\n\t}\n\tif !strings.Contains(longResult, longId[:8]) {\n\t\tt.Errorf(\"long experimentId should keep first 8 chars %q, got %q\", longId[:8], longResult)\n\t}\n\n\t// Name length is bounded regardless of input lengths\n\tveryLongId := strings.Repeat(\"x\", 200)\n\tboundedResult := getBackupConfigMapName(veryLongId, \"ns\", \"cm\")\n\t// \"chaosblade-backup-\" (18) + 8 (expId) + \"-\" (1) + 8 (hash) = 35\n\tif len(boundedResult) > 35 {\n\t\tt.Errorf(\"name should be bounded, got length %d: %q\", len(boundedResult), boundedResult)\n\t}\n}\n\nfunc TestIsOptional(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\topt  *bool\n\t\twant bool\n\t}{\n\t\t{\"nil is required\", nil, false},\n\t\t{\"true is optional\", boolPtr(true), true},\n\t\t{\"false is required\", boolPtr(false), false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := isOptional(tt.opt)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"isOptional(%v) = %v, want %v\", tt.opt, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCopyStringMap(t *testing.T) {\n\tt.Run(\"nil input returns nil\", func(t *testing.T) {\n\t\tgot := copyStringMap(nil)\n\t\tif got != nil {\n\t\t\tt.Errorf(\"copyStringMap(nil) = %v, want nil\", got)\n\t\t}\n\t})\n\n\tt.Run(\"empty map returns empty map\", func(t *testing.T) {\n\t\tgot := copyStringMap(map[string]string{})\n\t\tif got == nil || len(got) != 0 {\n\t\t\tt.Errorf(\"copyStringMap(empty) = %v, want empty non-nil map\", got)\n\t\t}\n\t})\n\n\tt.Run(\"copies all entries\", func(t *testing.T) {\n\t\tsrc := map[string]string{\"a\": \"1\", \"b\": \"2\"}\n\t\tgot := copyStringMap(src)\n\t\tif len(got) != 2 || got[\"a\"] != \"1\" || got[\"b\"] != \"2\" {\n\t\t\tt.Errorf(\"copyStringMap() = %v, want %v\", got, src)\n\t\t}\n\t})\n\n\tt.Run(\"modifying copy does not affect original\", func(t *testing.T) {\n\t\tsrc := map[string]string{\"key\": \"val\"}\n\t\tdst := copyStringMap(src)\n\t\tdst[\"key\"] = \"changed\"\n\t\tdst[\"new\"] = \"added\"\n\t\tif src[\"key\"] != \"val\" {\n\t\t\tt.Errorf(\"original was modified: src[key] = %q, want %q\", src[\"key\"], \"val\")\n\t\t}\n\t\tif _, exists := src[\"new\"]; exists {\n\t\t\tt.Error(\"original gained new key from copy modification\")\n\t\t}\n\t})\n}\n\nfunc TestCopyByteMap(t *testing.T) {\n\tt.Run(\"nil input returns nil\", func(t *testing.T) {\n\t\tgot := copyByteMap(nil)\n\t\tif got != nil {\n\t\t\tt.Errorf(\"copyByteMap(nil) = %v, want nil\", got)\n\t\t}\n\t})\n\n\tt.Run(\"empty map returns empty map\", func(t *testing.T) {\n\t\tgot := copyByteMap(map[string][]byte{})\n\t\tif got == nil || len(got) != 0 {\n\t\t\tt.Errorf(\"copyByteMap(empty) = %v, want empty non-nil map\", got)\n\t\t}\n\t})\n\n\tt.Run(\"copies all entries\", func(t *testing.T) {\n\t\tsrc := map[string][]byte{\"a\": {1, 2, 3}, \"b\": {4, 5}}\n\t\tgot := copyByteMap(src)\n\t\tif len(got) != 2 {\n\t\t\tt.Fatalf(\"copyByteMap() returned %d entries, want 2\", len(got))\n\t\t}\n\t\tif len(got[\"a\"]) != 3 || got[\"a\"][0] != 1 || got[\"a\"][2] != 3 {\n\t\t\tt.Errorf(\"copyByteMap()[a] = %v, want [1 2 3]\", got[\"a\"])\n\t\t}\n\t})\n\n\tt.Run(\"deep copy - modifying byte slice does not affect original\", func(t *testing.T) {\n\t\toriginal := []byte{10, 20, 30}\n\t\tsrc := map[string][]byte{\"data\": original}\n\t\tdst := copyByteMap(src)\n\n\t\t// Modify the copy\n\t\tdst[\"data\"][0] = 99\n\n\t\t// Original should be unchanged\n\t\tif src[\"data\"][0] != 10 {\n\t\t\tt.Errorf(\"original byte slice was modified: src[data][0] = %d, want 10\", src[\"data\"][0])\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "exec/pod/containercreating.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\t// ChaosBladePVAnnotation is the annotation for PV resources created by containercreating action\n\tChaosBladePVAnnotation = \"chaosblade.io/pv\"\n\t// ChaosBladePVCAnnotation is the annotation for PVC resources created by containercreating action\n\tChaosBladePVCAnnotation = \"chaosblade.io/pvc\"\n\t// ChaosBladePodAnnotation is the annotation for Pod resources created by containercreating action\n\tChaosBladePodAnnotation = \"chaosblade.io/pod\"\n\t// ChaosBladeExperimentAnnotation is the annotation key for experiment ID\n\tChaosBladeExperimentAnnotation = \"chaosblade.io/experiment\"\n\t// ChaosBladeActionCreate is the annotation value for create action\n\tChaosBladeActionCreate = \"create\"\n)\n\ntype PodContainerCreatingActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n\tclient *channel.Client\n}\n\nfunc NewPodContainerCreatingActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &PodContainerCreatingActionSpec{\n\t\tBaseExpActionCommandSpec: spec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"volume-mount-path\",\n\t\t\t\t\tDesc: \"Volume mount path in the container. Default: /mnt/data\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &PodContainerCreatingActionExecutor{client: client},\n\t\t\tActionExample: `# Create a pod stuck in ContainerCreating state in the default namespace\nblade create k8s pod-pod containercreating --namespace default --kubeconfig ~/.kube/config\n\n# Create a pod stuck in ContainerCreating state with custom volume mount path\nblade create k8s pod-pod containercreating --namespace default --volume-mount-path /data --kubeconfig ~/.kube/config\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t\tclient: client,\n\t}\n}\n\nfunc (*PodContainerCreatingActionSpec) Name() string {\n\treturn \"containercreating\"\n}\n\nfunc (*PodContainerCreatingActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*PodContainerCreatingActionSpec) ShortDesc() string {\n\treturn \"Make pod stuck in ContainerCreating state by PVC mount failure\"\n}\n\nfunc (*PodContainerCreatingActionSpec) LongDesc() string {\n\treturn \"Simulate the scenario where a Pod is stuck in ContainerCreating state due to storage volume mount failure. \" +\n\t\t\"This fault is injected by creating a PV with an unreachable NFS server and a PVC bound to it, \" +\n\t\t\"then creating a Pod that mounts this PVC. Since the NFS server is unreachable, the volume mount fails \" +\n\t\t\"and the Pod remains stuck in ContainerCreating state. \" +\n\t\t\"When the experiment is destroyed, the created Pod, PVC, and PV will be cleaned up.\"\n}\n\ntype PodContainerCreatingActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*PodContainerCreatingActionExecutor) Name() string {\n\treturn \"containercreating\"\n}\n\nfunc (*PodContainerCreatingActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *PodContainerCreatingActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *PodContainerCreatingActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\t// Parse flags with defaults\n\tvolumeMountPath := expModel.ActionFlags[\"volume-mount-path\"]\n\tif volumeMountPath == \"\" {\n\t\tvolumeMountPath = \"/mnt/data\"\n\t}\n\n\t// Deduplicate by namespace - create one faulty PV+PVC+Pod per unique namespace\n\tseenNamespaces := make(map[string]bool)\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\n\tfor _, meta := range containerObjectMetaList {\n\t\tif seenNamespaces[meta.Namespace] {\n\t\t\tcontinue\n\t\t}\n\t\tseenNamespaces[meta.Namespace] = true\n\n\t\tpvName := fmt.Sprintf(\"chaosblade-cc-%s-pv\", experimentId)\n\t\tpvcName := fmt.Sprintf(\"chaosblade-cc-%s-pvc\", experimentId)\n\t\tpodName := fmt.Sprintf(\"chaosblade-cc-%s-pod\", experimentId)\n\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: fmt.Sprintf(\"%s//%s\", meta.Namespace, podName),\n\t\t}\n\n\t\t// Step 1: Create PV with unreachable NFS server\n\t\tif err := d.createPV(ctx, pvName, experimentId); err != nil {\n\t\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\t\tlogrusField.Infof(\"PV %s already exists, skip creation\", pvName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"create PV %s failed: %v\", pvName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"create PV failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"created PV %s with unreachable NFS server\", pvName)\n\t\t}\n\n\t\t// Step 2: Create PVC bound to the PV (PVC will be Bound, but mount will fail)\n\t\tif err := d.createPVC(ctx, meta.Namespace, pvcName, pvName, experimentId); err != nil {\n\t\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\t\tlogrusField.Infof(\"PVC %s/%s already exists, skip creation\", meta.Namespace, pvcName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"create PVC %s/%s failed: %v\", meta.Namespace, pvcName, err)\n\t\t\t\t// Best-effort rollback: delete the PV we just created.\n\t\t\t\t// If rollback fails, the PV will be leaked because we record\n\t\t\t\t// a failed status and Destroy only processes successful ones.\n\t\t\t\t// To prevent leaks, record success so Destroy will retry cleanup.\n\t\t\t\tpvDeleted := true\n\t\t\t\tif delErr := d.deletePV(ctx, pvName); delErr != nil {\n\t\t\t\t\tlogrusField.Warningf(\"rollback PV %s failed: %v\", pvName, delErr)\n\t\t\t\t\tpvDeleted = false\n\t\t\t\t}\n\t\t\t\tif pvDeleted {\n\t\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"create PVC failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\t} else {\n\t\t\t\t\tlogrusField.Warningf(\"rollback incomplete, recording success status to ensure Destroy can clean up\")\n\t\t\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\t\tsuccess = true\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"created PVC %s/%s bound to PV %s\", meta.Namespace, pvcName, pvName)\n\t\t}\n\n\t\t// Step 3: Wait for PVC to be Bound before creating Pod\n\t\tif err := d.waitForPVCBound(ctx, meta.Namespace, pvcName, 30*time.Second); err != nil {\n\t\t\tlogrusField.Warningf(\"PVC %s/%s is not bound yet: %v\", meta.Namespace, pvcName, err)\n\t\t}\n\n\t\t// Step 4: Create Pod that mounts the PVC (will be stuck in ContainerCreating)\n\t\tif err := d.createPod(ctx, meta.Namespace, podName, pvcName, volumeMountPath, experimentId); err != nil {\n\t\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\t\tlogrusField.Infof(\"Pod %s/%s already exists, skip creation\", meta.Namespace, podName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"create Pod %s/%s failed: %v\", meta.Namespace, podName, err)\n\t\t\t\t// Best-effort rollback: delete PVC and PV.\n\t\t\t\t// If rollback fails, resources will be leaked because we record\n\t\t\t\t// a failed status and Destroy only processes successful ones.\n\t\t\t\t// To prevent leaks, we still record success so Destroy will\n\t\t\t\t// attempt cleanup (destroy is idempotent and handles NotFound).\n\t\t\t\tpvcDeleted := false\n\t\t\t\tif delErr := d.deletePVC(ctx, meta.Namespace, pvcName); delErr != nil {\n\t\t\t\t\tlogrusField.Warningf(\"rollback PVC %s/%s failed: %v\", meta.Namespace, pvcName, delErr)\n\t\t\t\t} else {\n\t\t\t\t\tpvcDeleted = true\n\t\t\t\t}\n\t\t\t\tpvDeleted := true\n\t\t\t\tif delErr := d.deletePV(ctx, pvName); delErr != nil {\n\t\t\t\t\tlogrusField.Warningf(\"rollback PV %s failed: %v\", pvName, delErr)\n\t\t\t\t\tpvDeleted = false\n\t\t\t\t}\n\t\t\t\t// If rollback fully succeeded, record failure (no leaked resources).\n\t\t\t\t// If any rollback step failed, record success so Destroy will retry cleanup.\n\t\t\t\tif pvcDeleted && pvDeleted {\n\t\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"create Pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\t} else {\n\t\t\t\t\tlogrusField.Warningf(\"rollback incomplete, recording success status to ensure Destroy can clean up\")\n\t\t\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\t\tsuccess = true\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"created Pod %s/%s which will be stuck in ContainerCreating state\", meta.Namespace, podName)\n\t\t}\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatuses = append(statuses, status)\n\t\tsuccess = true\n\t}\n\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *PodContainerCreatingActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tallSuccess := true\n\tseenNamespaces := make(map[string]bool)\n\n\tfor _, meta := range containerObjectMetaList {\n\t\tif seenNamespaces[meta.Namespace] {\n\t\t\tcontinue\n\t\t}\n\t\tseenNamespaces[meta.Namespace] = true\n\n\t\tpvName := fmt.Sprintf(\"chaosblade-cc-%s-pv\", experimentId)\n\t\tpvcName := fmt.Sprintf(\"chaosblade-cc-%s-pvc\", experimentId)\n\t\tpodName := fmt.Sprintf(\"chaosblade-cc-%s-pod\", experimentId)\n\t\tnamespace := meta.Namespace\n\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: fmt.Sprintf(\"%s//%s\", namespace, podName),\n\t\t}\n\n\t\t// Step 1: Delete Pod\n\t\tif err := d.deletePod(ctx, namespace, podName); err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Infof(\"Pod %s/%s already deleted\", namespace, podName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"delete Pod %s/%s failed: %v\", namespace, podName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"delete Pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tallSuccess = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"deleted Pod %s/%s\", namespace, podName)\n\t\t}\n\n\t\t// Step 2: Delete PVC\n\t\tif err := d.deletePVC(ctx, namespace, pvcName); err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Infof(\"PVC %s/%s already deleted\", namespace, pvcName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"delete PVC %s/%s failed: %v\", namespace, pvcName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"delete PVC failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tallSuccess = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"deleted PVC %s/%s\", namespace, pvcName)\n\t\t}\n\n\t\t// Step 3: Delete PV\n\t\tif err := d.deletePV(ctx, pvName); err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Infof(\"PV %s already deleted\", pvName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"delete PV %s failed: %v\", pvName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"delete PV failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tallSuccess = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"deleted PV %s\", pvName)\n\t\t}\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatus.State = v1alpha1.DestroyedState\n\t\tstatuses = append(statuses, status)\n\t}\n\n\tif allSuccess {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus(statuses))\n\t}\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses))\n}\n\n// createPV creates a PersistentVolume with an unreachable NFS server.\n// The PV will be Available, allowing PVC binding, but the NFS mount will fail\n// when a Pod tries to use it, causing the Pod to be stuck in ContainerCreating.\nfunc (d *PodContainerCreatingActionExecutor) createPV(ctx context.Context, pvName, experimentId string) error {\n\tpv := &v1.PersistentVolume{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: pvName,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladePVAnnotation:         ChaosBladeActionCreate,\n\t\t\t\tChaosBladeExperimentAnnotation: experimentId,\n\t\t\t},\n\t\t},\n\t\tSpec: v1.PersistentVolumeSpec{\n\t\t\tCapacity: v1.ResourceList{\n\t\t\t\tv1.ResourceStorage: resource.MustParse(\"1Gi\"),\n\t\t\t},\n\t\t\tAccessModes: []v1.PersistentVolumeAccessMode{\n\t\t\t\tv1.ReadWriteOnce,\n\t\t\t},\n\t\t\tPersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,\n\t\t\tPersistentVolumeSource: v1.PersistentVolumeSource{\n\t\t\t\tNFS: &v1.NFSVolumeSource{\n\t\t\t\t\t// Use a non-routable IP address to simulate unreachable NFS server\n\t\t\t\t\tServer:   \"10.255.255.1\",\n\t\t\t\t\tPath:     \"/chaosblade-fake-nfs\",\n\t\t\t\t\tReadOnly: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\treturn d.client.Create(ctx, pv)\n}\n\n// createPVC creates a PersistentVolumeClaim that binds to the specified PV.\n// The PVC will be Bound to the PV, but the actual volume mount will fail\n// because the NFS server is unreachable.\nfunc (d *PodContainerCreatingActionExecutor) createPVC(ctx context.Context, namespace, pvcName, pvName, experimentId string) error {\n\temptyStr := \"\"\n\tpvc := &v1.PersistentVolumeClaim{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      pvcName,\n\t\t\tNamespace: namespace,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladePVCAnnotation:        ChaosBladeActionCreate,\n\t\t\t\tChaosBladeExperimentAnnotation: experimentId,\n\t\t\t},\n\t\t},\n\t\tSpec: v1.PersistentVolumeClaimSpec{\n\t\t\tStorageClassName: &emptyStr,\n\t\t\tAccessModes: []v1.PersistentVolumeAccessMode{\n\t\t\t\tv1.ReadWriteOnce,\n\t\t\t},\n\t\t\tResources: v1.VolumeResourceRequirements{\n\t\t\t\tRequests: v1.ResourceList{\n\t\t\t\t\tv1.ResourceStorage: resource.MustParse(\"1Gi\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tVolumeName: pvName,\n\t\t},\n\t}\n\treturn d.client.Create(ctx, pvc)\n}\n\n// createPod creates a Pod that mounts the given PVC, which will cause it to be\n// stuck in ContainerCreating state because the NFS mount fails.\nfunc (d *PodContainerCreatingActionExecutor) createPod(ctx context.Context, namespace, podName, pvcName, volumeMountPath, experimentId string) error {\n\tpod := &v1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      podName,\n\t\t\tNamespace: namespace,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladePodAnnotation:        ChaosBladeActionCreate,\n\t\t\t\tChaosBladeExperimentAnnotation: experimentId,\n\t\t\t},\n\t\t},\n\t\tSpec: v1.PodSpec{\n\t\t\tContainers: []v1.Container{\n\t\t\t\t{\n\t\t\t\t\tName:  \"chaosblade-cc\",\n\t\t\t\t\tImage: \"busybox\",\n\t\t\t\t\tCommand: []string{\n\t\t\t\t\t\t\"sleep\",\n\t\t\t\t\t\t\"infinity\",\n\t\t\t\t\t},\n\t\t\t\t\tVolumeMounts: []v1.VolumeMount{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:      \"chaosblade-cc-volume\",\n\t\t\t\t\t\t\tMountPath: volumeMountPath,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Tolerate all taints so the Pod can be scheduled on any node\n\t\t\tTolerations: []v1.Toleration{\n\t\t\t\t{\n\t\t\t\t\tOperator: v1.TolerationOpExists,\n\t\t\t\t},\n\t\t\t},\n\t\t\tVolumes: []v1.Volume{\n\t\t\t\t{\n\t\t\t\t\tName: \"chaosblade-cc-volume\",\n\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\tPersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{\n\t\t\t\t\t\t\tClaimName: pvcName,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\treturn d.client.Create(ctx, pod)\n}\n\n// deletePod deletes a Pod by namespace and name\nfunc (d *PodContainerCreatingActionExecutor) deletePod(ctx context.Context, namespace, podName string) error {\n\tpod := &v1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      podName,\n\t\t\tNamespace: namespace,\n\t\t},\n\t}\n\treturn d.client.Delete(ctx, pod)\n}\n\n// deletePVC deletes a PersistentVolumeClaim by namespace and name\nfunc (d *PodContainerCreatingActionExecutor) deletePVC(ctx context.Context, namespace, pvcName string) error {\n\tpvc := &v1.PersistentVolumeClaim{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      pvcName,\n\t\t\tNamespace: namespace,\n\t\t},\n\t}\n\treturn d.client.Delete(ctx, pvc)\n}\n\n// waitForPVCBound polls until the PVC is in Bound state or timeout is reached\nfunc (d *PodContainerCreatingActionExecutor) waitForPVCBound(ctx context.Context, namespace, pvcName string, timeout time.Duration) error {\n\tdeadline := time.Now().Add(timeout)\n\tfor time.Now().Before(deadline) {\n\t\tpvc := &v1.PersistentVolumeClaim{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: pvcName}, pvc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif pvc.Status.Phase == v1.ClaimBound {\n\t\t\treturn nil\n\t\t}\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\treturn fmt.Errorf(\"PVC %s/%s is not bound after %v\", namespace, pvcName, timeout)\n}\n\n// deletePV deletes a PersistentVolume by name\nfunc (d *PodContainerCreatingActionExecutor) deletePV(ctx context.Context, pvName string) error {\n\tpv := &v1.PersistentVolume{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: pvName,\n\t\t},\n\t}\n\treturn d.client.Delete(ctx, pv)\n}\n\n// PreCreate implements model.ActionPreProcessor interface.\n// It validates the namespace and prepares the context for containercreating action.\nfunc (a *PodContainerCreatingActionSpec) PreCreate(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) (context.Context, *spec.Response) {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\n\t// Validate namespace: must be specified and only one value\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-cc-%s-pod\", experimentId),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\n// PreDestroy implements model.ActionPreProcessor interface.\n// It prepares the context for containercreating destroy flow.\nfunc (a *PodContainerCreatingActionSpec) PreDestroy(ctx context.Context, expModel *spec.ExpModel, client *channel.Client, oldExpStatus v1alpha1.ExperimentStatus) (context.Context, *spec.Response) {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-cc-%s-pod\", experimentId),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n"
  },
  {
    "path": "exec/pod/containercreatingdisk.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\n// PodContainerCreatingDiskActionSpec defines the action spec for containercreating-disk.\n// It creates a PVC with a specified StorageClass (triggering cloud disk provisioning)\n// and a Pod that mounts this PVC. When the cloud disk provisioner fails (due to zone\n// mismatch, disk type not supported, or quota exceeded), the PVC remains Pending and\n// the Pod is stuck in ContainerCreating.\ntype PodContainerCreatingDiskActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n\tclient *channel.Client\n}\n\nfunc NewPodContainerCreatingDiskActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &PodContainerCreatingDiskActionSpec{\n\t\tBaseExpActionCommandSpec: spec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"storage-class\",\n\t\t\t\t\tDesc:     \"StorageClass name for PVC creation\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"pv-capacity\",\n\t\t\t\t\tDesc: \"PVC storage capacity, default: 20Gi\",\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"volume-mount-path\",\n\t\t\t\t\tDesc: \"Volume mount path in the container, default: /mnt/data\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &PodContainerCreatingDiskActionExecutor{client: client},\n\t\t\tActionExample: `# Create a pod stuck in ContainerCreating state by cloud disk PVC failure\nblade create k8s pod-pod containercreating-disk --namespace default --storage-class alicloud-disk-ssd --kubeconfig ~/.kube/config\n\n# Specify custom PV capacity\nblade create k8s pod-pod containercreating-disk --namespace default --storage-class alicloud-disk-ssd --pv-capacity 50Gi --kubeconfig ~/.kube/config\n\n# Specify custom volume mount path\nblade create k8s pod-pod containercreating-disk --namespace default --storage-class alicloud-disk-ssd --volume-mount-path /data --kubeconfig ~/.kube/config`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t\tclient: client,\n\t}\n}\n\nfunc (*PodContainerCreatingDiskActionSpec) Name() string {\n\treturn \"containercreating-disk\"\n}\n\nfunc (*PodContainerCreatingDiskActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*PodContainerCreatingDiskActionSpec) ShortDesc() string {\n\treturn \"Make pod stuck in ContainerCreating state by cloud disk PVC creation failure\"\n}\n\nfunc (*PodContainerCreatingDiskActionSpec) LongDesc() string {\n\treturn \"Simulate the scenario where a Pod is stuck in ContainerCreating state due to cloud disk PVC creation failure. \" +\n\t\t\"This fault is injected by creating a PVC with the specified StorageClass (which triggers cloud disk provisioning), \" +\n\t\t\"then creating a Pod that mounts this PVC. When the cloud disk provisioner fails (due to zone mismatch, \" +\n\t\t\"disk type not supported, or quota exceeded), the PVC remains Pending and the Pod is stuck in ContainerCreating. \" +\n\t\t\"When the experiment is destroyed, the created Pod and PVC will be cleaned up.\"\n}\n\n// PodContainerCreatingDiskActionExecutor implements the create and destroy logic\n// for the containercreating-disk action.\ntype PodContainerCreatingDiskActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*PodContainerCreatingDiskActionExecutor) Name() string {\n\treturn \"containercreating-disk\"\n}\n\nfunc (*PodContainerCreatingDiskActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *PodContainerCreatingDiskActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *PodContainerCreatingDiskActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\t// Parse flags with defaults\n\tstorageClass := expModel.ActionFlags[\"storage-class\"]\n\tpvCapacity := expModel.ActionFlags[\"pv-capacity\"]\n\tif pvCapacity == \"\" {\n\t\tpvCapacity = \"20Gi\"\n\t}\n\tvolumeMountPath := expModel.ActionFlags[\"volume-mount-path\"]\n\tif volumeMountPath == \"\" {\n\t\tvolumeMountPath = \"/mnt/data\"\n\t}\n\n\t// Deduplicate by namespace - create one PVC+Pod per unique namespace\n\tseenNamespaces := make(map[string]bool)\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\n\tfor _, meta := range containerObjectMetaList {\n\t\tif seenNamespaces[meta.Namespace] {\n\t\t\tcontinue\n\t\t}\n\t\tseenNamespaces[meta.Namespace] = true\n\n\t\tpvcName := fmt.Sprintf(\"chaosblade-ccd-%s-pvc\", experimentId)\n\t\tpodName := fmt.Sprintf(\"chaosblade-ccd-%s-pod\", experimentId)\n\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: fmt.Sprintf(\"%s//%s\", meta.Namespace, podName),\n\t\t}\n\n\t\t// Step 1: Create PVC with the specified StorageClass.\n\t\t// In clusters without a cloud disk provisioner, the PVC will remain Pending.\n\t\tif err := d.createPVC(ctx, meta.Namespace, pvcName, storageClass, pvCapacity, experimentId); err != nil {\n\t\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\t\tlogrusField.Infof(\"PVC %s/%s already exists, skip creation\", meta.Namespace, pvcName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"create PVC %s/%s failed: %v\", meta.Namespace, pvcName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"create PVC failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"created PVC %s/%s with StorageClass %s\", meta.Namespace, pvcName, storageClass)\n\t\t}\n\n\t\t// Step 2: Create Pod that mounts the PVC.\n\t\t// The Pod will be stuck in ContainerCreating since the PVC is not bound.\n\t\tif err := d.createPod(ctx, meta.Namespace, podName, pvcName, volumeMountPath, experimentId); err != nil {\n\t\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\t\tlogrusField.Infof(\"Pod %s/%s already exists, skip creation\", meta.Namespace, podName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"create Pod %s/%s failed: %v\", meta.Namespace, podName, err)\n\t\t\t\t// Best-effort rollback: delete the PVC we just created.\n\t\t\t\t// If rollback fails, resources will be leaked because we record\n\t\t\t\t// a failed status and Destroy only processes successful ones.\n\t\t\t\t// To prevent leaks, we still record success so Destroy will\n\t\t\t\t// attempt cleanup (destroy is idempotent and handles NotFound).\n\t\t\t\tpvcDeleted := true\n\t\t\t\tif delErr := d.deletePVC(ctx, meta.Namespace, pvcName); delErr != nil {\n\t\t\t\t\tlogrusField.Warningf(\"rollback PVC %s/%s failed: %v\", meta.Namespace, pvcName, delErr)\n\t\t\t\t\tpvcDeleted = false\n\t\t\t\t}\n\t\t\t\tif pvcDeleted {\n\t\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"create Pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\t} else {\n\t\t\t\t\tlogrusField.Warningf(\"rollback incomplete, recording success status to ensure Destroy can clean up\")\n\t\t\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\t\tsuccess = true\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"created Pod %s/%s which will be stuck in ContainerCreating state\", meta.Namespace, podName)\n\t\t}\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatuses = append(statuses, status)\n\t\tsuccess = true\n\t}\n\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *PodContainerCreatingDiskActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tallSuccess := true\n\tseenNamespaces := make(map[string]bool)\n\n\tfor _, meta := range containerObjectMetaList {\n\t\tif seenNamespaces[meta.Namespace] {\n\t\t\tcontinue\n\t\t}\n\t\tseenNamespaces[meta.Namespace] = true\n\n\t\tpvcName := fmt.Sprintf(\"chaosblade-ccd-%s-pvc\", experimentId)\n\t\tpodName := fmt.Sprintf(\"chaosblade-ccd-%s-pod\", experimentId)\n\t\tnamespace := meta.Namespace\n\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: fmt.Sprintf(\"%s//%s\", namespace, podName),\n\t\t}\n\n\t\t// Step 1: Delete Pod\n\t\tif err := d.deletePod(ctx, namespace, podName); err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Infof(\"Pod %s/%s already deleted\", namespace, podName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"delete Pod %s/%s failed: %v\", namespace, podName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"delete Pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tallSuccess = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"deleted Pod %s/%s\", namespace, podName)\n\t\t}\n\n\t\t// Step 2: Delete PVC\n\t\tif err := d.deletePVC(ctx, namespace, pvcName); err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Infof(\"PVC %s/%s already deleted\", namespace, pvcName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"delete PVC %s/%s failed: %v\", namespace, pvcName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"delete PVC failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tallSuccess = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"deleted PVC %s/%s\", namespace, pvcName)\n\t\t}\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatus.State = v1alpha1.DestroyedState\n\t\tstatuses = append(statuses, status)\n\t}\n\n\tif allSuccess {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus(statuses))\n\t}\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses))\n}\n\n// createPVC creates a PersistentVolumeClaim with the specified StorageClass.\n// The PVC will remain Pending if the cloud disk provisioner is unavailable or\n// misconfigured (zone mismatch, disk type not supported, quota exceeded).\nfunc (d *PodContainerCreatingDiskActionExecutor) createPVC(ctx context.Context, namespace, pvcName, storageClass, pvCapacity, experimentId string) error {\n\tpvc := &v1.PersistentVolumeClaim{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      pvcName,\n\t\t\tNamespace: namespace,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladePVCAnnotation:        ChaosBladeActionCreate,\n\t\t\t\tChaosBladeExperimentAnnotation: experimentId,\n\t\t\t},\n\t\t},\n\t\tSpec: v1.PersistentVolumeClaimSpec{\n\t\t\tStorageClassName: &storageClass,\n\t\t\tAccessModes: []v1.PersistentVolumeAccessMode{\n\t\t\t\tv1.ReadWriteOnce,\n\t\t\t},\n\t\t\tResources: v1.VolumeResourceRequirements{\n\t\t\t\tRequests: v1.ResourceList{\n\t\t\t\t\tv1.ResourceStorage: resource.MustParse(pvCapacity),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\treturn d.client.Create(ctx, pvc)\n}\n\n// createPod creates a Pod that mounts the given PVC, which will cause it to be\n// stuck in ContainerCreating state because the PVC is not bound (cloud disk\n// provisioner failure).\nfunc (d *PodContainerCreatingDiskActionExecutor) createPod(ctx context.Context, namespace, podName, pvcName, volumeMountPath, experimentId string) error {\n\tpod := &v1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      podName,\n\t\t\tNamespace: namespace,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladePodAnnotation:        ChaosBladeActionCreate,\n\t\t\t\tChaosBladeExperimentAnnotation: experimentId,\n\t\t\t},\n\t\t},\n\t\tSpec: v1.PodSpec{\n\t\t\tContainers: []v1.Container{\n\t\t\t\t{\n\t\t\t\t\tName:    \"chaosblade-ccd-container\",\n\t\t\t\t\tImage:   \"busybox\",\n\t\t\t\t\tCommand: []string{\"sleep\", \"infinity\"},\n\t\t\t\t\tVolumeMounts: []v1.VolumeMount{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:      \"chaosblade-ccd-volume\",\n\t\t\t\t\t\t\tMountPath: volumeMountPath,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tTolerations: []v1.Toleration{\n\t\t\t\t{\n\t\t\t\t\tOperator: v1.TolerationOpExists,\n\t\t\t\t},\n\t\t\t},\n\t\t\tVolumes: []v1.Volume{\n\t\t\t\t{\n\t\t\t\t\tName: \"chaosblade-ccd-volume\",\n\t\t\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\t\t\tPersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{\n\t\t\t\t\t\t\tClaimName: pvcName,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\treturn d.client.Create(ctx, pod)\n}\n\n// deletePod deletes a Pod by namespace and name.\nfunc (d *PodContainerCreatingDiskActionExecutor) deletePod(ctx context.Context, namespace, podName string) error {\n\tpod := &v1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      podName,\n\t\t\tNamespace: namespace,\n\t\t},\n\t}\n\treturn d.client.Delete(ctx, pod)\n}\n\n// deletePVC deletes a PersistentVolumeClaim by namespace and name.\nfunc (d *PodContainerCreatingDiskActionExecutor) deletePVC(ctx context.Context, namespace, pvcName string) error {\n\tpvc := &v1.PersistentVolumeClaim{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      pvcName,\n\t\t\tNamespace: namespace,\n\t\t},\n\t}\n\treturn d.client.Delete(ctx, pvc)\n}\n\n// PreCreate implements model.ActionPreProcessor interface.\n// It validates namespace and storage-class, and prepares the context for\n// containercreating-disk action.\nfunc (a *PodContainerCreatingDiskActionSpec) PreCreate(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) (context.Context, *spec.Response) {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\n\t// Validate namespace: must be specified and only one value\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\n\t// Validate storage-class: must be specified\n\tstorageClass := expModel.ActionFlags[\"storage-class\"]\n\tif storageClass == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"storage-class\")\n\t}\n\n\t// Validate pv-capacity format if specified\n\tpvCapacity := expModel.ActionFlags[\"pv-capacity\"]\n\tif pvCapacity != \"\" {\n\t\tif _, err := resource.ParseQuantity(pvCapacity); err != nil {\n\t\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterIllegal, \"pv-capacity\", pvCapacity, err.Error())\n\t\t}\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-ccd-%s-pod\", experimentId),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\n// PreDestroy implements model.ActionPreProcessor interface.\n// It prepares the context for containercreating-disk destroy flow.\nfunc (a *PodContainerCreatingDiskActionSpec) PreDestroy(ctx context.Context, expModel *spec.ExpModel, client *channel.Client, oldExpStatus v1alpha1.ExperimentStatus) (context.Context, *spec.Response) {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-ccd-%s-pod\", experimentId),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n"
  },
  {
    "path": "exec/pod/containercreatingdisk_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nfunc TestPodContainerCreatingDiskActionSpec_Name(t *testing.T) {\n\ts := NewPodContainerCreatingDiskActionSpec(nil)\n\tif s.Name() != \"containercreating-disk\" {\n\t\tt.Errorf(\"expected name 'containercreating-disk', got '%s'\", s.Name())\n\t}\n}\n\nfunc TestPodContainerCreatingDiskActionSpec_ShortDesc(t *testing.T) {\n\ts := NewPodContainerCreatingDiskActionSpec(nil)\n\tif s.ShortDesc() == \"\" {\n\t\tt.Error(\"ShortDesc should not be empty\")\n\t}\n}\n\nfunc TestPodContainerCreatingDiskActionSpec_LongDesc(t *testing.T) {\n\ts := NewPodContainerCreatingDiskActionSpec(nil)\n\tif s.LongDesc() == \"\" {\n\t\tt.Error(\"LongDesc should not be empty\")\n\t}\n}\n\nfunc TestPodContainerCreatingDiskActionSpec_Aliases(t *testing.T) {\n\ts := NewPodContainerCreatingDiskActionSpec(nil)\n\tif len(s.Aliases()) != 0 {\n\t\tt.Errorf(\"expected no aliases, got %v\", s.Aliases())\n\t}\n}\n\nfunc TestPodContainerCreatingDiskActionSpec_ActionFlags(t *testing.T) {\n\ts := NewPodContainerCreatingDiskActionSpec(nil)\n\tflags := s.Flags()\n\tif flags == nil {\n\t\tt.Fatal(\"Flags should not be nil\")\n\t}\n\n\tflagNames := make(map[string]bool)\n\tfor _, f := range flags {\n\t\tflagNames[f.FlagName()] = true\n\t}\n\tif !flagNames[\"storage-class\"] {\n\t\tt.Error(\"storage-class flag should exist\")\n\t}\n\tif !flagNames[\"pv-capacity\"] {\n\t\tt.Error(\"pv-capacity flag should exist\")\n\t}\n\tif !flagNames[\"volume-mount-path\"] {\n\t\tt.Error(\"volume-mount-path flag should exist\")\n\t}\n}\n\nfunc TestPodContainerCreatingDiskActionSpec_ActionCategories(t *testing.T) {\n\ts := NewPodContainerCreatingDiskActionSpec(nil)\n\tcategories := s.Categories()\n\tfound := false\n\tfor _, c := range categories {\n\t\tif c == model.CategorySystemContainer {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !found {\n\t\tt.Error(\"CategorySystemContainer should be present\")\n\t}\n}\n\nfunc TestPodContainerCreatingDiskActionSpec_ActionExample(t *testing.T) {\n\ts := NewPodContainerCreatingDiskActionSpec(nil)\n\texample := s.Example()\n\tif example == \"\" {\n\t\tt.Error(\"Example should not be empty\")\n\t}\n}\n\nfunc TestPodContainerCreatingDiskActionExecutor_Name(t *testing.T) {\n\texecutor := &PodContainerCreatingDiskActionExecutor{}\n\tif executor.Name() != \"containercreating-disk\" {\n\t\tt.Errorf(\"expected name 'containercreating-disk', got '%s'\", executor.Name())\n\t}\n}\n\nfunc TestPreCreate_NamespaceEmpty(t *testing.T) {\n\tactionSpec := NewPodContainerCreatingDiskActionSpec(nil).(*PodContainerCreatingDiskActionSpec)\n\n\texpModel := &spec.ExpModel{\n\t\tActionFlags: map[string]string{\n\t\t\tmodel.ResourceNamespaceFlag.Name: \"\",\n\t\t\t\"storage-class\":                  \"alicloud-disk-ssd\",\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tctx = model.SetExperimentIdToContext(ctx, \"test-exp-001\")\n\n\t_, resp := actionSpec.PreCreate(ctx, expModel, nil)\n\tif resp == nil {\n\t\tt.Fatal(\"expected error response for empty namespace\")\n\t}\n\tif resp.Success {\n\t\tt.Error(\"expected PreCreate to fail for empty namespace\")\n\t}\n}\n\nfunc TestPreCreate_NamespaceWithComma(t *testing.T) {\n\tactionSpec := NewPodContainerCreatingDiskActionSpec(nil).(*PodContainerCreatingDiskActionSpec)\n\n\texpModel := &spec.ExpModel{\n\t\tActionFlags: map[string]string{\n\t\t\tmodel.ResourceNamespaceFlag.Name: \"ns1,ns2\",\n\t\t\t\"storage-class\":                  \"alicloud-disk-ssd\",\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tctx = model.SetExperimentIdToContext(ctx, \"test-exp-002\")\n\n\t_, resp := actionSpec.PreCreate(ctx, expModel, nil)\n\tif resp == nil {\n\t\tt.Fatal(\"expected error response for multi-value namespace\")\n\t}\n\tif resp.Success {\n\t\tt.Error(\"expected PreCreate to fail for namespace with comma\")\n\t}\n}\n\nfunc TestPreCreate_StorageClassEmpty(t *testing.T) {\n\tactionSpec := NewPodContainerCreatingDiskActionSpec(nil).(*PodContainerCreatingDiskActionSpec)\n\n\texpModel := &spec.ExpModel{\n\t\tActionFlags: map[string]string{\n\t\t\tmodel.ResourceNamespaceFlag.Name: \"default\",\n\t\t\t\"storage-class\":                  \"\",\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tctx = model.SetExperimentIdToContext(ctx, \"test-exp-003\")\n\n\t_, resp := actionSpec.PreCreate(ctx, expModel, nil)\n\tif resp == nil {\n\t\tt.Fatal(\"expected error response for empty storage-class\")\n\t}\n\tif resp.Success {\n\t\tt.Error(\"expected PreCreate to fail for empty storage-class\")\n\t}\n}\n\nfunc TestPreCreate_Success(t *testing.T) {\n\tactionSpec := NewPodContainerCreatingDiskActionSpec(nil).(*PodContainerCreatingDiskActionSpec)\n\n\texpModel := &spec.ExpModel{\n\t\tActionFlags: map[string]string{\n\t\t\tmodel.ResourceNamespaceFlag.Name: \"default\",\n\t\t\t\"storage-class\":                  \"alicloud-disk-ssd\",\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tctx = model.SetExperimentIdToContext(ctx, \"test-exp-004\")\n\n\tnewCtx, resp := actionSpec.PreCreate(ctx, expModel, nil)\n\tif resp != nil {\n\t\tt.Fatalf(\"expected no error, got response: %+v\", resp)\n\t}\n\n\tlist, err := model.GetContainerObjectMetaListFromContext(newCtx)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get containerObjectMetaList: %v\", err)\n\t}\n\tif len(list) != 1 {\n\t\tt.Fatalf(\"expected 1 container meta, got %d\", len(list))\n\t}\n\tif list[0].Namespace != \"default\" {\n\t\tt.Errorf(\"expected namespace 'default', got '%s'\", list[0].Namespace)\n\t}\n}\n\nfunc TestPreDestroy_Success(t *testing.T) {\n\tactionSpec := NewPodContainerCreatingDiskActionSpec(nil).(*PodContainerCreatingDiskActionSpec)\n\n\texpModel := &spec.ExpModel{\n\t\tActionFlags: map[string]string{\n\t\t\tmodel.ResourceNamespaceFlag.Name: \"default\",\n\t\t\t\"storage-class\":                  \"alicloud-disk-ssd\",\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tctx = model.SetExperimentIdToContext(ctx, \"test-exp-005\")\n\n\tnewCtx, resp := actionSpec.PreDestroy(ctx, expModel, nil, v1alpha1.ExperimentStatus{})\n\tif resp != nil {\n\t\tt.Fatalf(\"expected no error, got response: %+v\", resp)\n\t}\n\n\tlist, err := model.GetContainerObjectMetaListFromContext(newCtx)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get containerObjectMetaList: %v\", err)\n\t}\n\tif len(list) != 1 {\n\t\tt.Fatalf(\"expected 1 container meta, got %d\", len(list))\n\t}\n\tif list[0].Namespace != \"default\" {\n\t\tt.Errorf(\"expected namespace 'default', got '%s'\", list[0].Namespace)\n\t}\n}\n\nfunc TestPreDestroy_NamespaceEmpty(t *testing.T) {\n\tactionSpec := NewPodContainerCreatingDiskActionSpec(nil).(*PodContainerCreatingDiskActionSpec)\n\n\texpModel := &spec.ExpModel{\n\t\tActionFlags: map[string]string{\n\t\t\tmodel.ResourceNamespaceFlag.Name: \"\",\n\t\t\t\"storage-class\":                  \"alicloud-disk-ssd\",\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tctx = model.SetExperimentIdToContext(ctx, \"test-exp-006\")\n\n\t_, resp := actionSpec.PreDestroy(ctx, expModel, nil, v1alpha1.ExperimentStatus{})\n\tif resp == nil {\n\t\tt.Fatal(\"expected error response for empty namespace\")\n\t}\n\tif resp.Success {\n\t\tt.Error(\"expected PreDestroy to fail for empty namespace\")\n\t}\n}\n\nfunc TestPreCreate_PVCapacityInvalid(t *testing.T) {\n\tactionSpec := NewPodContainerCreatingDiskActionSpec(nil).(*PodContainerCreatingDiskActionSpec)\n\n\texpModel := &spec.ExpModel{\n\t\tActionFlags: map[string]string{\n\t\t\tmodel.ResourceNamespaceFlag.Name: \"default\",\n\t\t\t\"storage-class\":                  \"alicloud-disk-ssd\",\n\t\t\t\"pv-capacity\":                    \"invalid-capacity\",\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tctx = model.SetExperimentIdToContext(ctx, \"test-exp-007\")\n\n\t_, resp := actionSpec.PreCreate(ctx, expModel, nil)\n\tif resp == nil {\n\t\tt.Fatal(\"expected error response for invalid pv-capacity\")\n\t}\n\tif resp.Success {\n\t\tt.Error(\"expected PreCreate to fail for invalid pv-capacity\")\n\t}\n}\n\nfunc TestPreCreate_PVCapacityValid(t *testing.T) {\n\tactionSpec := NewPodContainerCreatingDiskActionSpec(nil).(*PodContainerCreatingDiskActionSpec)\n\n\texpModel := &spec.ExpModel{\n\t\tActionFlags: map[string]string{\n\t\t\tmodel.ResourceNamespaceFlag.Name: \"default\",\n\t\t\t\"storage-class\":                  \"alicloud-disk-ssd\",\n\t\t\t\"pv-capacity\":                    \"50Gi\",\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tctx = model.SetExperimentIdToContext(ctx, \"test-exp-008\")\n\n\tnewCtx, resp := actionSpec.PreCreate(ctx, expModel, nil)\n\tif resp != nil {\n\t\tt.Fatalf(\"expected no error, got response: %+v\", resp)\n\t}\n\n\tlist, err := model.GetContainerObjectMetaListFromContext(newCtx)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get containerObjectMetaList: %v\", err)\n\t}\n\tif len(list) != 1 {\n\t\tt.Fatalf(\"expected 1 container meta, got %d\", len(list))\n\t}\n\tif list[0].Namespace != \"default\" {\n\t\tt.Errorf(\"expected namespace 'default', got '%s'\", list[0].Namespace)\n\t}\n}\n"
  },
  {
    "path": "exec/pod/controller.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\n\t\"github.com/chaosblade-io/chaosblade-exec-cri/exec/container\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\ntype ExpController struct {\n\tmodel.BaseExperimentController\n}\n\nfunc NewExpController(client *channel.Client) model.ExperimentController {\n\treturn &ExpController{\n\t\tmodel.BaseExperimentController{\n\t\t\tClient:            client,\n\t\t\tResourceModelSpec: NewResourceModelSpec(client),\n\t\t},\n\t}\n}\n\nfunc (*ExpController) Name() string {\n\treturn \"pod\"\n}\n\n// Create pod resource experiments\nfunc (e *ExpController) Create(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response {\n\texpModel := model.ExtractExpModelFromExperimentSpec(expSpec)\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\t// Get action spec to check if it implements ActionPreProcessor\n\tactionSpec := e.ResourceModelSpec.GetExpActionModelSpec(expModel.Target, expModel.ActionName)\n\tif actionSpec != nil {\n\t\tif preProcessor, ok := actionSpec.(model.ActionPreProcessor); ok {\n\t\t\tnewCtx, resp := preProcessor.PreCreate(ctx, expModel, e.Client)\n\t\t\tif resp != nil {\n\t\t\t\treturn resp\n\t\t\t}\n\t\t\tctx = newCtx\n\t\t\tlogrusField.Infof(\"creating %s experiment with pre-processing\", expModel.ActionName)\n\t\t\treturn e.Exec(ctx, expModel)\n\t\t}\n\t}\n\n\t// Default flow: find matched pods and execute\n\tpods, resp := e.GetMatchedPodResources(ctx, *expModel)\n\tif !resp.Success {\n\t\tlogrusField.Errorf(\"uid: %s, get matched pod experiment failed, %v\", experimentId, resp.Err)\n\t\tresp.Result = v1alpha1.CreateFailExperimentStatus(resp.Err, []v1alpha1.ResourceStatus{})\n\t}\n\tlogrusField.Infof(\"creating pod experiment, pod count is %d\", len(pods))\n\tcontainerObjectMetaList := getContainerMatchedList(experimentId, pods)\n\tif len(containerObjectMetaList) == 0 {\n\t\tlogrusField.Errorf(\"uid: %s, get container from context failed\", experimentId)\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn e.Exec(ctx, expModel)\n}\n\nfunc (e *ExpController) Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) *spec.Response {\n\texpModel := model.ExtractExpModelFromExperimentSpec(expSpec)\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrus.WithField(\"experiment\", experimentId).Infoln(\"start to destroy\")\n\n\t// Check if action implements ActionPreProcessor - use the same path as Create\n\tactionSpec := e.ResourceModelSpec.GetExpActionModelSpec(expModel.Target, expModel.ActionName)\n\tif actionSpec != nil {\n\t\tif preProcessor, ok := actionSpec.(model.ActionPreProcessor); ok {\n\t\t\tnewCtx, resp := preProcessor.PreDestroy(ctx, expModel, e.Client, oldExpStatus)\n\t\t\tif resp != nil {\n\t\t\t\treturn resp\n\t\t\t}\n\t\t\tctx = newCtx\n\t\t\treturn e.Exec(ctx, expModel)\n\t\t}\n\t}\n\n\t// Default flow: find matched containers and destroy\n\tstatuses := oldExpStatus.ResStatuses\n\tif len(statuses) == 0 {\n\t\treturn spec.ReturnSuccess(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{}))\n\t}\n\tcontainerObjectMetaList := model.ContainerMatchedList{}\n\tfor _, status := range statuses {\n\t\tif !status.Success {\n\t\t\tcontinue\n\t\t}\n\t\tcontainerObjectMeta := model.ParseIdentifier(status.Identifier)\n\t\tcontainerObjectMeta.Id = status.Id\n\t\tcontainerObjectMetaList = append(containerObjectMetaList, containerObjectMeta)\n\t}\n\tif len(containerObjectMetaList) == 0 {\n\t\treturn spec.ReturnSuccess(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{}))\n\t}\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn e.Exec(ctx, expModel)\n}\n\n// getContainerMatchedList transports selected pods\nfunc getContainerMatchedList(experimentId string, pods []v1.Pod) model.ContainerMatchedList {\n\tcontainerObjectMetaList := model.ContainerMatchedList{}\n\tfor _, p := range pods {\n\t\tcontainerId, containerName, runtime, err := model.GetOneAvailableContainerIdFromPod(p)\n\t\tif runtime == container.DockerRuntime {\n\t\t\tcontainerId = containerId[:12]\n\t\t}\n\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", experimentId).WithField(\"pod\", p.Name).\n\t\t\t\tErrorf(\"get an available container failed, %v\", err)\n\t\t\tcontinue\n\t\t}\n\t\tcontainerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{\n\t\t\tContainerId:      containerId,\n\t\t\tContainerName:    containerName,\n\t\t\tContainerRuntime: runtime,\n\t\t\tPodName:          p.Name,\n\t\t\tNodeName:         p.Spec.NodeName,\n\t\t\tNamespace:        p.Namespace,\n\t\t})\n\t}\n\treturn containerObjectMetaList\n}\n"
  },
  {
    "path": "exec/pod/delete.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\ntype DeletePodActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc NewDeletePodActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &DeletePodActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:   \"random\",\n\t\t\t\t\tDesc:   \"Randomly select pod\",\n\t\t\t\t\tNoArgs: true,\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &DeletePodActionExecutor{client: client},\n\t\t\tActionExample: `# Deletes the POD under the specified default namespace that is app=guestbook\nblade create k8s pod-pod delete --labels app=guestbook --namespace default --evict-count 2 --kubeconfig ~/.kube/config`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*DeletePodActionSpec) Name() string {\n\treturn \"delete\"\n}\n\nfunc (*DeletePodActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*DeletePodActionSpec) ShortDesc() string {\n\treturn \"Delete pods\"\n}\n\nfunc (*DeletePodActionSpec) LongDesc() string {\n\treturn \"Delete pods\"\n}\n\ntype DeletePodActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*DeletePodActionExecutor) Name() string {\n\treturn \"delete\"\n}\n\nfunc (*DeletePodActionExecutor) SetChannel(channel spec.Channel) {\n}\n\nfunc (d *DeletePodActionExecutor) Exec(uid string, ctx context.Context, model *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, model)\n\t} else {\n\t\treturn d.create(uid, ctx, model)\n\t}\n}\n\nfunc (d *DeletePodActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\tfor _, meta := range containerObjectMetaList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: fmt.Sprintf(\"%s/%s/%s\", meta.Namespace, meta.NodeName, meta.PodName),\n\t\t}\n\t\tobjectMeta := metav1.ObjectMeta{Name: meta.PodName, Namespace: meta.Namespace}\n\t\terr := d.client.Delete(context.TODO(), &v1.Pod{ObjectMeta: objectMeta})\n\t\tif err != nil {\n\t\t\tlogrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx)).\n\t\t\t\tWarningf(\"delete pod %s err, %v\", meta.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t} else {\n\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t\tsuccess = true\n\t\t}\n\t\tstatuses = append(statuses, status)\n\t}\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *DeletePodActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\texperimentStatus := v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{})\n\tstatuses := experimentStatus.ResStatuses\n\tfor _, c := range containerObjectMetaList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tId:         c.Id,\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tState:      v1alpha1.DestroyedState,\n\t\t\tSuccess:    true,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\t\tstatuses = append(statuses, status)\n\t}\n\texperimentStatus.ResStatuses = statuses\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n"
  },
  {
    "path": "exec/pod/failedmount.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\tChaosBladeFailedMountAction           = \"failedmount\"\n\tChaosBladeOriginalVolumesAnnotation   = \"chaosblade.io/original-volumes\"\n\tChaosBladeFailedMountVolumeNamePrefix = \"chaosblade-fm-\"\n\tChaosBladeFailedMountVolumeMountPath  = \"/chaosblade-fm-nonexistent\"\n\n\tFailedMountVolumeTypeConfigMap = \"configmap\"\n\tFailedMountVolumeTypeSecret    = \"secret\"\n\tFailedMountVolumeTypePVC       = \"pvc\"\n)\n\ntype FailedMountActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n\tclient *channel.Client\n}\n\nfunc NewFailedMountActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &FailedMountActionSpec{\n\t\tBaseExpActionCommandSpec: spec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"workload-type\",\n\t\t\t\t\tDesc:     \"Workload type: deployment, daemonset, statefulset. Default: deployment\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t\tDefault:  \"deployment\",\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"workload-name\",\n\t\t\t\t\tDesc:     \"Workload name to inject failed mount volume\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"volume-type\",\n\t\t\t\t\tDesc:     \"Volume type to inject: configmap, secret, pvc\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"with-initcontainer\",\n\t\t\t\t\tDesc:     \"Mount the non-existent volume to init containers first. Default: false\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t\tDefault:  \"false\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &FailedMountActionExecutor{client: client},\n\t\t\tActionExample: `# Mount a non-existent configmap volume to a deployment\nblade create k8s pod-pod failedmount --namespace default --workload-type deployment --workload-name nginx-app --volume-type configmap --kubeconfig ~/.kube/config\n\n# Mount a non-existent secret volume to a deployment with init container\nblade create k8s pod-pod failedmount --namespace default --workload-type deployment --workload-name nginx-app --volume-type secret --with-initcontainer true --kubeconfig ~/.kube/config\n\n# Mount a non-existent pvc volume to a statefulset\nblade create k8s pod-pod failedmount --namespace default --workload-type statefulset --workload-name redis-app --volume-type pvc --kubeconfig ~/.kube/config\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t\tclient: client,\n\t}\n}\n\nfunc (*FailedMountActionSpec) Name() string {\n\treturn \"failedmount\"\n}\n\nfunc (*FailedMountActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*FailedMountActionSpec) ShortDesc() string {\n\treturn \"Mount a non-existent configmap/secret/pvc volume to simulate volume mount failure\"\n}\n\nfunc (*FailedMountActionSpec) LongDesc() string {\n\treturn \"Inject a fault by adding a volume referencing a non-existent ConfigMap, Secret, or PVC \" +\n\t\t\"to the target workload (Deployment/DaemonSet/StatefulSet). The volume name is randomly generated. \" +\n\t\t\"When --with-initcontainer is true, the volume mount is added to init containers first. \" +\n\t\t\"The original volume configuration is backed up in an annotation and restored when the experiment is destroyed.\"\n}\n\n// PreCreate implements model.ActionPreProcessor.\nfunc (a *FailedMountActionSpec) PreCreate(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) (context.Context, *spec.Response) {\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\n\tvolumeType := expModel.ActionFlags[\"volume-type\"]\n\tif volumeType == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"volume-type\")\n\t}\n\tif volumeType != FailedMountVolumeTypeConfigMap && volumeType != FailedMountVolumeTypeSecret && volumeType != FailedMountVolumeTypePVC {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterIllegal, \"volume-type\",\n\t\t\tvolumeType, \"must be one of: configmap, secret, pvc\")\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-fm-%s-%s\", workloadType, workloadName),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\n// PreDestroy implements model.ActionPreProcessor.\nfunc (a *FailedMountActionSpec) PreDestroy(ctx context.Context, expModel *spec.ExpModel, client *channel.Client, oldExpStatus v1alpha1.ExperimentStatus) (context.Context, *spec.Response) {\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-fm-%s-%s\", workloadType, workloadName),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\ntype FailedMountActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*FailedMountActionExecutor) Name() string {\n\treturn \"failedmount\"\n}\n\nfunc (*FailedMountActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *FailedMountActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *FailedMountActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\tvolumeType := expModel.ActionFlags[\"volume-type\"]\n\twithInitContainer := strings.EqualFold(expModel.ActionFlags[\"with-initcontainer\"], \"true\")\n\n\tif namespace == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"namespace is required\")\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"workload-name is required\")\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\tif volumeType == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"volume-type is required\")\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, \"volume-type\")\n\t}\n\tif volumeType != FailedMountVolumeTypeConfigMap && volumeType != FailedMountVolumeTypeSecret && volumeType != FailedMountVolumeTypePVC {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), fmt.Sprintf(\"invalid volume-type: %s\", volumeType))\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterIllegal, \"volume-type\",\n\t\t\tvolumeType, \"must be one of: configmap, secret, pvc\")\n\t}\n\n\tstatus := v1alpha1.ResourceStatus{\n\t\tKind:       v1alpha1.PodKind,\n\t\tIdentifier: fmt.Sprintf(\"%s//%s//%s\", namespace, workloadType, workloadName),\n\t}\n\n\tswitch workloadType {\n\tcase \"deployment\":\n\t\tdeployment := &appsv1.Deployment{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, deployment)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"deployment %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"deployment not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get deployment %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get deployment failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\tif err := d.injectDeploymentFailedMount(ctx, deployment, volumeType, withInitContainer, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject failed mount to deployment %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject failed mount failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected failed mount to deployment %s/%s with volume-type=%s\", namespace, workloadName, volumeType)\n\n\tcase \"daemonset\":\n\t\tdaemonset := &appsv1.DaemonSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, daemonset)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"daemonset %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"daemonset not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get daemonset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get daemonset failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\tif err := d.injectDaemonSetFailedMount(ctx, daemonset, volumeType, withInitContainer, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject failed mount to daemonset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject failed mount failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected failed mount to daemonset %s/%s with volume-type=%s\", namespace, workloadName, volumeType)\n\n\tcase \"statefulset\":\n\t\tstatefulset := &appsv1.StatefulSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, statefulset)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"statefulset %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"statefulset not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get statefulset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get statefulset failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\tif err := d.injectStatefulSetFailedMount(ctx, statefulset, volumeType, withInitContainer, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject failed mount to statefulset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject failed mount failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected failed mount to statefulset %s/%s with volume-type=%s\", namespace, workloadName, volumeType)\n\n\tdefault:\n\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"unsupported workload type: %s\", workloadType), spec.ParameterIllegal.Code)\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t}\n\n\tstatus = status.CreateSuccessResourceStatus()\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{status}))\n}\n\nfunc (d *FailedMountActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tstatus := v1alpha1.ResourceStatus{\n\t\tKind:       v1alpha1.PodKind,\n\t\tIdentifier: fmt.Sprintf(\"%s//%s//%s\", namespace, workloadType, workloadName),\n\t}\n\n\tswitch workloadType {\n\tcase \"deployment\":\n\t\tdeployment := &appsv1.Deployment{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, deployment)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\t\tif err := d.restoreDeploymentVolumes(ctx, deployment, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore deployment %s/%s volumes failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore deployment volumes failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored deployment %s/%s volumes\", namespace, workloadName)\n\n\tcase \"daemonset\":\n\t\tdaemonset := &appsv1.DaemonSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, daemonset)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\t\tif err := d.restoreDaemonSetVolumes(ctx, daemonset, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore daemonset %s/%s volumes failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore daemonset volumes failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored daemonset %s/%s volumes\", namespace, workloadName)\n\n\tcase \"statefulset\":\n\t\tstatefulset := &appsv1.StatefulSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, statefulset)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\t\tif err := d.restoreStatefulSetVolumes(ctx, statefulset, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore statefulset %s/%s volumes failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore statefulset volumes failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored statefulset %s/%s volumes\", namespace, workloadName)\n\n\tdefault:\n\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"unsupported workload type: %s\", workloadType), spec.ParameterIllegal.Code)\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t}\n\n\tstatus = status.CreateSuccessResourceStatus()\n\tstatus.State = v1alpha1.DestroyedState\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{status}))\n}\n\n// volumeBackup stores the injected volume name so destroy knows exactly what to remove.\ntype volumeBackup struct {\n\tVolumeName string `json:\"volumeName\"`\n\tVolumeType string `json:\"volumeType\"`\n\tMountedTo  string `json:\"mountedTo\"` // \"initContainers\" or \"containers\"\n}\n\n// generateRandomHash generates a 12-character hex string.\nfunc generateRandomHash() string {\n\tb := make([]byte, 6)\n\tif _, err := rand.Read(b); err != nil {\n\t\treturn \"a1b2c3d4e5f6\"\n\t}\n\treturn hex.EncodeToString(b)\n}\n\n// buildFailedMountVolume creates a Volume with a non-existent configmap/secret/pvc reference.\nfunc buildFailedMountVolume(volumeName, volumeType string) v1.Volume {\n\tfakeRef := \"chaosblade-nonexistent-\" + generateRandomHash()\n\tvol := v1.Volume{Name: volumeName}\n\n\tswitch volumeType {\n\tcase FailedMountVolumeTypeConfigMap:\n\t\tvol.VolumeSource = v1.VolumeSource{\n\t\t\tConfigMap: &v1.ConfigMapVolumeSource{\n\t\t\t\tLocalObjectReference: v1.LocalObjectReference{Name: fakeRef},\n\t\t\t},\n\t\t}\n\tcase FailedMountVolumeTypeSecret:\n\t\tvol.VolumeSource = v1.VolumeSource{\n\t\t\tSecret: &v1.SecretVolumeSource{\n\t\t\t\tSecretName: fakeRef,\n\t\t\t},\n\t\t}\n\tcase FailedMountVolumeTypePVC:\n\t\tvol.VolumeSource = v1.VolumeSource{\n\t\t\tPersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{\n\t\t\t\tClaimName: fakeRef,\n\t\t\t},\n\t\t}\n\t}\n\treturn vol\n}\n\n// injectFailedMountVolume adds a non-existent volume and mounts it to the target containers.\n// If withInitContainer is true, mounts to init containers; otherwise mounts to regular containers.\nfunc injectFailedMountVolume(podSpec *v1.PodSpec, annotations map[string]string, volumeType string, withInitContainer bool) (*volumeBackup, error) {\n\tvolumeName := ChaosBladeFailedMountVolumeNamePrefix + generateRandomHash()\n\n\tmountedTo := \"containers\"\n\tif withInitContainer {\n\t\tif len(podSpec.InitContainers) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"the specified pod has no initContainers\")\n\t\t}\n\t\tmountedTo = \"initContainers\"\n\t}\n\n\tvol := buildFailedMountVolume(volumeName, volumeType)\n\tpodSpec.Volumes = append(podSpec.Volumes, vol)\n\n\tvolumeMount := v1.VolumeMount{\n\t\tName:      volumeName,\n\t\tMountPath: ChaosBladeFailedMountVolumeMountPath + \"/\" + volumeName,\n\t}\n\n\tif withInitContainer {\n\t\tfor i := range podSpec.InitContainers {\n\t\t\tpodSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, volumeMount)\n\t\t}\n\t} else {\n\t\tfor i := range podSpec.Containers {\n\t\t\tpodSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, volumeMount)\n\t\t}\n\t}\n\n\tbackup := &volumeBackup{\n\t\tVolumeName: volumeName,\n\t\tVolumeType: volumeType,\n\t\tMountedTo:  mountedTo,\n\t}\n\tbackupBytes, err := json.Marshal(backup)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshal volume backup failed: %v\", err)\n\t}\n\tannotations[ChaosBladeOriginalVolumesAnnotation] = string(backupBytes)\n\n\treturn backup, nil\n}\n\n// removeInjectedVolume removes the injected volume and its mounts from the pod spec.\n// It also validates that the volume being removed matches the expected type from backup.\nfunc removeInjectedVolume(podSpec *v1.PodSpec, backup *volumeBackup) error {\n\tfound := false\n\tnewVolumes := make([]v1.Volume, 0, len(podSpec.Volumes))\n\tfor _, vol := range podSpec.Volumes {\n\t\tif vol.Name == backup.VolumeName {\n\t\t\tfound = true\n\t\t\tif err := validateVolumeType(&vol, backup.VolumeType); err != nil {\n\t\t\t\treturn fmt.Errorf(\"sanity check failed for volume %q: %w\", backup.VolumeName, err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tnewVolumes = append(newVolumes, vol)\n\t}\n\tif !found {\n\t\tlogrus.Warnf(\"injected volume %q not found in pod spec, it may have been removed externally\", backup.VolumeName)\n\t}\n\tpodSpec.Volumes = newVolumes\n\n\tif backup.MountedTo == \"initContainers\" {\n\t\tfor i := range podSpec.InitContainers {\n\t\t\tmounts := make([]v1.VolumeMount, 0, len(podSpec.InitContainers[i].VolumeMounts))\n\t\t\tfor _, m := range podSpec.InitContainers[i].VolumeMounts {\n\t\t\t\tif m.Name != backup.VolumeName {\n\t\t\t\t\tmounts = append(mounts, m)\n\t\t\t\t}\n\t\t\t}\n\t\t\tpodSpec.InitContainers[i].VolumeMounts = mounts\n\t\t}\n\t} else {\n\t\tfor i := range podSpec.Containers {\n\t\t\tmounts := make([]v1.VolumeMount, 0, len(podSpec.Containers[i].VolumeMounts))\n\t\t\tfor _, m := range podSpec.Containers[i].VolumeMounts {\n\t\t\t\tif m.Name != backup.VolumeName {\n\t\t\t\t\tmounts = append(mounts, m)\n\t\t\t\t}\n\t\t\t}\n\t\t\tpodSpec.Containers[i].VolumeMounts = mounts\n\t\t}\n\t}\n\treturn nil\n}\n\n// validateVolumeType checks that the volume's actual source type matches the expected type from backup.\nfunc validateVolumeType(vol *v1.Volume, expectedType string) error {\n\tswitch expectedType {\n\tcase FailedMountVolumeTypeConfigMap:\n\t\tif vol.ConfigMap == nil {\n\t\t\treturn fmt.Errorf(\"expected configmap volume but found different type\")\n\t\t}\n\tcase FailedMountVolumeTypeSecret:\n\t\tif vol.Secret == nil {\n\t\t\treturn fmt.Errorf(\"expected secret volume but found different type\")\n\t\t}\n\tcase FailedMountVolumeTypePVC:\n\t\tif vol.PersistentVolumeClaim == nil {\n\t\t\treturn fmt.Errorf(\"expected pvc volume but found different type\")\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown volume type %q in backup annotation\", expectedType)\n\t}\n\treturn nil\n}\n\n// restoreVolumeFromAnnotation parses the backup annotation and removes the injected volume.\nfunc restoreVolumeFromAnnotation(podSpec *v1.PodSpec, annotations map[string]string) error {\n\tbackupStr, ok := annotations[ChaosBladeOriginalVolumesAnnotation]\n\tif !ok || backupStr == \"\" {\n\t\treturn fmt.Errorf(\"volume backup annotation not found\")\n\t}\n\n\tvar backup volumeBackup\n\tif err := json.Unmarshal([]byte(backupStr), &backup); err != nil {\n\t\treturn fmt.Errorf(\"unmarshal volume backup failed: %v\", err)\n\t}\n\n\treturn removeInjectedVolume(podSpec, &backup)\n}\n\n// --- Deployment ---\n\nfunc (d *FailedMountActionExecutor) injectDeploymentFailedMount(ctx context.Context, deployment *appsv1.Deployment, volumeType string, withInitContainer bool, experimentId string) error {\n\tif deployment.Annotations == nil {\n\t\tdeployment.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(deployment.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\tif deployment.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tdeployment.Annotations[ChaosBladeDeploymentAnnotation] = ChaosBladeFailedMountAction\n\tdeployment.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\tif _, err := injectFailedMountVolume(&deployment.Spec.Template.Spec, deployment.Annotations, volumeType, withInitContainer); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, deployment)\n}\n\nfunc (d *FailedMountActionExecutor) restoreDeploymentVolumes(ctx context.Context, deployment *appsv1.Deployment, experimentId string) error {\n\tif deployment.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"deployment was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := restoreVolumeFromAnnotation(&deployment.Spec.Template.Spec, deployment.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\tdelete(deployment.Annotations, ChaosBladeDeploymentAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeOriginalVolumesAnnotation)\n\n\treturn d.client.Update(ctx, deployment)\n}\n\n// --- DaemonSet ---\n\nfunc (d *FailedMountActionExecutor) injectDaemonSetFailedMount(ctx context.Context, daemonset *appsv1.DaemonSet, volumeType string, withInitContainer bool, experimentId string) error {\n\tif daemonset.Annotations == nil {\n\t\tdaemonset.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(daemonset.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\tif daemonset.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tdaemonset.Annotations[ChaosBladeDaemonSetAnnotation] = ChaosBladeFailedMountAction\n\tdaemonset.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\tif _, err := injectFailedMountVolume(&daemonset.Spec.Template.Spec, daemonset.Annotations, volumeType, withInitContainer); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, daemonset)\n}\n\nfunc (d *FailedMountActionExecutor) restoreDaemonSetVolumes(ctx context.Context, daemonset *appsv1.DaemonSet, experimentId string) error {\n\tif daemonset.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"daemonset was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := restoreVolumeFromAnnotation(&daemonset.Spec.Template.Spec, daemonset.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\tdelete(daemonset.Annotations, ChaosBladeDaemonSetAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeOriginalVolumesAnnotation)\n\n\treturn d.client.Update(ctx, daemonset)\n}\n\n// --- StatefulSet ---\n\nfunc (d *FailedMountActionExecutor) injectStatefulSetFailedMount(ctx context.Context, statefulset *appsv1.StatefulSet, volumeType string, withInitContainer bool, experimentId string) error {\n\tif statefulset.Annotations == nil {\n\t\tstatefulset.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(statefulset.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\tif statefulset.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tstatefulset.Annotations[ChaosBladeStatefulSetAnnotation] = ChaosBladeFailedMountAction\n\tstatefulset.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\tif _, err := injectFailedMountVolume(&statefulset.Spec.Template.Spec, statefulset.Annotations, volumeType, withInitContainer); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, statefulset)\n}\n\nfunc (d *FailedMountActionExecutor) restoreStatefulSetVolumes(ctx context.Context, statefulset *appsv1.StatefulSet, experimentId string) error {\n\tif statefulset.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"statefulset was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := restoreVolumeFromAnnotation(&statefulset.Spec.Template.Spec, statefulset.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\tdelete(statefulset.Annotations, ChaosBladeStatefulSetAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeOriginalVolumesAnnotation)\n\n\treturn d.client.Update(ctx, statefulset)\n}\n"
  },
  {
    "path": "exec/pod/failexp.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\ntype FailPodActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc NewFailPodActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &FailPodActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{},\n\t\t\t},\n\t\t\tActionExecutor: &FailPodActionExecutor{client: client},\n\t\t\tActionExample: `# Specify POD exception\nblade create k8s pod-pod fail --labels \"app=test\" --namespace default\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*FailPodActionSpec) Name() string {\n\treturn \"fail\"\n}\n\nfunc (*FailPodActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*FailPodActionSpec) ShortDesc() string {\n\treturn \"Fail pods\"\n}\n\nfunc (*FailPodActionSpec) LongDesc() string {\n\treturn \"Fail pods\"\n}\n\ntype FailPodActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*FailPodActionExecutor) Name() string {\n\treturn \"fail\"\n}\n\nfunc (*FailPodActionExecutor) SetChannel(channel spec.Channel) {\n}\n\nfunc (d *FailPodActionExecutor) Exec(uid string, ctx context.Context, model *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(ctx, model)\n\t} else {\n\t\treturn d.create(ctx, model)\n\t}\n}\n\nfunc (d *FailPodActionExecutor) create(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\tcontainerMatchedList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\tfor _, c := range containerMatchedList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\t\tobjectMeta := types.NamespacedName{Name: c.PodName, Namespace: c.Namespace}\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(context.TODO(), objectMeta, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"get pod %s err, %v\", c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"get\", err), spec.K8sExecFailed.Code)\n\t\t}\n\n\t\tif !isPodReady(pod) {\n\t\t\tlogrusField.Infof(\"pod %s is not ready\", c.PodName)\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(spec.PodNotReady.Sprintf(c.PodName),\n\t\t\t\tspec.PodNotReady.Code))\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := d.failPod(ctx, pod); err != nil {\n\t\t\tlogrusField.Warningf(\"fail pod %s err, %v\", c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"update\", err), spec.K8sExecFailed.Code)\n\t\t} else {\n\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t\tsuccess = true\n\t\t}\n\t\tstatuses = append(statuses, status)\n\t}\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *FailPodActionExecutor) destroy(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tcontainerMatchedList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\texperimentStatus := v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{})\n\tstatuses := experimentStatus.ResStatuses\n\tfor _, c := range containerMatchedList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\t\tobjectMeta := types.NamespacedName{Name: c.PodName, Namespace: c.Namespace}\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(context.TODO(), objectMeta, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"get pod %s err, %v\", c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"get\", err), spec.K8sExecFailed.Code)\n\t\t\tcontinue\n\t\t}\n\n\t\terr = d.client.Delete(context.TODO(), pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"delete pod %s err, %v\", c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"delete\", err), spec.K8sExecFailed.Code)\n\t\t\tcontinue\n\t\t}\n\t}\n\texperimentStatus.ResStatuses = statuses\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\n// failPod will exec failPod experiment\nfunc (d *FailPodActionExecutor) failPod(ctx context.Context, pod *v1.Pod) error {\n\tfor i, container := range pod.Spec.Containers {\n\t\tkey := fmt.Sprintf(\"%s-%s\", \"failPod\", container.Name)\n\t\tif pod.Annotations == nil {\n\t\t\tpod.Annotations = make(map[string]string)\n\t\t}\n\t\tif isAnnotationExist(pod.Annotations, key) {\n\t\t\tcontinue\n\t\t}\n\t\tpod.Annotations[key] = container.Image\n\t\tpod.Spec.Containers[i].Image = fmt.Sprintf(\"%s-fault-injection\", container.Image)\n\t}\n\tif err := d.client.Update(ctx, pod); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// isAnnotationExist will check this pod has been tested\nfunc isAnnotationExist(annotation map[string]string, key string) bool {\n\t_, ok := annotation[key]\n\tif !ok {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "exec/pod/fsexp.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n\tchaosfs \"github.com/chaosblade-io/chaosblade-operator/pkg/hookfs\"\n\twebhook \"github.com/chaosblade-io/chaosblade-operator/pkg/webhook/pod\"\n)\n\ntype PodIOActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc NewPodIOActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &PodIOActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"method\",\n\t\t\t\t\tDesc: \"inject methods, only support read and write\",\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"delay\",\n\t\t\t\t\tDesc: \"file io delay time, ms\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"path\",\n\t\t\t\t\tDesc: \"I/O exception path or file\",\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:   \"random\",\n\t\t\t\t\tDesc:   \"random inject I/O code\",\n\t\t\t\t\tNoArgs: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"percent\",\n\t\t\t\t\tDesc: \"I/O error percent [0-100],\",\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"errno\",\n\t\t\t\t\tDesc: \"I/O error code\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &PodIOActionExecutor{client: client},\n\t\t\tActionExample: `# Two types of exceptions were injected for the READ operation, with an exception rate of 60 percent\nblade create k8s pod-pod IO --method read --delay 1000 --path /home --percent 60 --errno 28 --labels \"app=test\" --namespace default`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*PodIOActionSpec) Name() string {\n\treturn \"IO\"\n}\n\nfunc (*PodIOActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*PodIOActionSpec) ShortDesc() string {\n\treturn \"Pod File System IO Exception\"\n}\n\nfunc (*PodIOActionSpec) LongDesc() string {\n\treturn \"Pod File System IO Exception\"\n}\n\ntype PodIOActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*PodIOActionExecutor) Name() string {\n\treturn \"IO\"\n}\n\nfunc (*PodIOActionExecutor) SetChannel(channel spec.Channel) {\n}\n\nfunc (d *PodIOActionExecutor) Exec(uid string, ctx context.Context, model *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(ctx, model)\n\t} else {\n\t\treturn d.create(ctx, model)\n\t}\n}\n\nfunc (d *PodIOActionExecutor) create(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tcontainerMatchedList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithFlags(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\tfor _, c := range containerMatchedList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(context.TODO(), client.ObjectKey{Namespace: c.Namespace, Name: c.PodName}, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"get pod %s err, %v\", c.PodName, err)\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\tspec.K8sExecFailed.Sprintf(\"get\", err), spec.K8sExecFailed.Code,\n\t\t\t))\n\t\t\tcontinue\n\t\t}\n\t\tif !isPodReady(pod) {\n\t\t\tlogrusField.Infof(\"pod %s is not ready\", c.PodName)\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(spec.PodNotReady.Msg, spec.PodNotReady.Code))\n\t\t\tcontinue\n\t\t}\n\t\tmethods, ok := expModel.ActionFlags[\"method\"]\n\t\tif !ok && len(methods) != 0 {\n\t\t\tlogrusField.Error(\"method cannot be empty\")\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\tspec.ParameterLess.Sprintf(\"method\"), spec.ParameterLess.Code,\n\t\t\t))\n\t\t\tcontinue\n\t\t}\n\n\t\tvar delay, percent, errno int\n\t\tdelayStr, ok := expModel.ActionFlags[\"delay\"]\n\t\tif ok && len(delayStr) != 0 {\n\t\t\tdelay, err = strconv.Atoi(delayStr)\n\t\t\tif err != nil {\n\t\t\t\tlogrusField.Error(\"delay must be integer\")\n\t\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\t\tspec.ParameterIllegal.Sprintf(\"delay\", delayStr, err), spec.ParameterIllegal.Code,\n\t\t\t\t))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tpercentStr, ok := expModel.ActionFlags[\"percent\"]\n\t\tif ok && len(percentStr) != 0 {\n\t\t\tif percent, err = strconv.Atoi(percentStr); err != nil {\n\t\t\t\tlogrusField.Error(\"percent must be integer\")\n\t\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\t\tspec.ParameterIllegal.Sprintf(\"percent\", percentStr, err), spec.ParameterIllegal.Code,\n\t\t\t\t))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\terrnoStr, ok := expModel.ActionFlags[\"errno\"]\n\t\tif ok && len(errnoStr) != 0 {\n\t\t\tif errno, err = strconv.Atoi(errnoStr); err != nil {\n\t\t\t\tlogrusField.Error(\"errno must be integer\")\n\t\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\t\tspec.ParameterIllegal.Sprintf(\"errno\", errnoStr, err), spec.ParameterIllegal.Code,\n\t\t\t\t))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\trandom := false\n\t\trandomStr, ok := expModel.ActionFlags[\"random\"]\n\t\tif ok && randomStr == \"true\" {\n\t\t\trandom = true\n\t\t}\n\n\t\trequest := &chaosfs.InjectMessage{\n\t\t\tMethods: strings.Split(methods, \",\"),\n\t\t\tPath:    expModel.ActionFlags[\"path\"],\n\t\t\tDelay:   uint32(delay),\n\t\t\tPercent: uint32(percent),\n\t\t\tRandom:  random,\n\t\t\tErrno:   uint32(errno),\n\t\t}\n\n\t\tchaosfsClient, err := getChaosfsClient(pod)\n\t\tif err != nil {\n\t\t\tlogrusField.WithField(\"pod\", c.PodName).WithField(\"request\", request).\n\t\t\t\tErrorf(\"init chaosfs client failed: %v\", err)\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\tspec.ChaosfsClientFailed.Sprintf(pod.Name, err), spec.ChaosfsClientFailed.Code,\n\t\t\t))\n\t\t\tcontinue\n\t\t}\n\t\terr = chaosfsClient.InjectFault(ctx, request)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"inject io exception in pod %s failed, request %v, err: %v\", c.PodName, request, err)\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\tspec.ChaosfsInjectFailed.Sprintf(pod.Name, request, err), spec.ChaosfsInjectFailed.Code,\n\t\t\t))\n\t\t\tcontinue\n\t\t}\n\t\tstatuses = append(statuses, status.CreateSuccessResourceStatus())\n\t\tsuccess = true\n\t}\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *PodIOActionExecutor) destroy(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tcontainerMatchedList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\texperimentStatus := v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{})\n\tstatuses := experimentStatus.ResStatuses\n\tfor _, c := range containerMatchedList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(context.TODO(), client.ObjectKey{Namespace: c.Namespace, Name: c.PodName}, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"get pod %s err, %v\", c.PodName, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !isPodReady(pod) {\n\t\t\tlogrusField.Errorf(\"pod %s is not ready\", c.PodName)\n\t\t\tcontinue\n\t\t}\n\n\t\tchaosfsClient, err := getChaosfsClient(pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"init chaosfs client failed in pod %v, err: %v\", pod.Name, err)\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\tspec.ChaosfsClientFailed.Sprintf(pod.Name, err), spec.ChaosfsClientFailed.Code,\n\t\t\t))\n\t\t\tcontinue\n\t\t}\n\t\terr = chaosfsClient.Revoke(ctx)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"recover io exception failed in pod  %v, err: %v\", c.PodName, err)\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(\n\t\t\t\tspec.ChaosfsRecoverFailed.Sprintf(pod.Name, err), spec.ChaosfsRecoverFailed.Code,\n\t\t\t))\n\t\t\tcontinue\n\t\t}\n\t}\n\texperimentStatus.ResStatuses = statuses\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc isPodReady(pod *v1.Pod) bool {\n\tif pod.ObjectMeta.DeletionTimestamp != nil {\n\t\treturn false\n\t}\n\tfor _, condition := range pod.Status.Conditions {\n\t\tif condition.Type == v1.PodReady &&\n\t\t\tcondition.Status == v1.ConditionTrue {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc getChaosfsClient(pod *v1.Pod) (*chaosfs.ChaosBladeHookClient, error) {\n\tport, err := getContainerPort(webhook.FuseServerPortName, pod)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taddr := fmt.Sprintf(\"%s:%d\", pod.Status.PodIP, port)\n\treturn chaosfs.NewChabladeHookClient(addr), nil\n}\n\nfunc getContainerPort(portName string, pod *v1.Pod) (int32, error) {\n\tfor _, container := range pod.Spec.Containers {\n\t\tfor _, port := range container.Ports {\n\t\t\tif port.Name == portName {\n\t\t\t\treturn port.ContainerPort, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn 0, fmt.Errorf(\"can not found fuse-server container port \")\n}\n"
  },
  {
    "path": "exec/pod/imageconfigexp.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\ntype ImageConfigActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nconst (\n\tImageNameFlag = \"image-name\"\n\tImageTagFlag  = \"image-tag\"\n)\n\nfunc NewImageConfigActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &ImageConfigActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: ImageNameFlag,\n\t\t\t\t\tDesc: \"The image name to replace the original image name, e.g. nginx-not-exist\",\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: ImageTagFlag,\n\t\t\t\t\tDesc: \"The image tag to replace the original image tag, e.g. non-existent-tag\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &ImageConfigActionExecutor{client: client},\n\t\t\tActionExample: `# Inject image config error to pods with default behavior\nblade create k8s pod-pod imageconfig --labels \"app=test\" --namespace default\n\n# Inject image config error with custom image name\nblade create k8s pod-pod imageconfig --labels \"app=test\" --namespace default --image-name nginx-not-exist\n\n# Inject image config error with custom image tag\nblade create k8s pod-pod imageconfig --labels \"app=test\" --namespace default --image-tag non-existent-tag\n\n# Inject image config error with both custom image name and tag\nblade create k8s pod-pod imageconfig --labels \"app=test\" --namespace default --image-name nginx-not-exist --image-tag non-existent-tag\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*ImageConfigActionSpec) Name() string {\n\treturn \"imageconfig\"\n}\n\nfunc (*ImageConfigActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*ImageConfigActionSpec) ShortDesc() string {\n\treturn \"Inject image config error to pods\"\n}\n\nfunc (*ImageConfigActionSpec) LongDesc() string {\n\treturn \"Modify pod container image to a non-existent image to simulate image config error\"\n}\n\ntype ImageConfigActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*ImageConfigActionExecutor) Name() string {\n\treturn \"imageconfig\"\n}\n\nfunc (*ImageConfigActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *ImageConfigActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(ctx, expModel)\n\t}\n\treturn d.create(ctx, expModel)\n}\n\nfunc (d *ImageConfigActionExecutor) create(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\timageName := expModel.ActionFlags[ImageNameFlag]\n\timageTag := expModel.ActionFlags[ImageTagFlag]\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\tcontainerMatchedList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\tfor _, c := range containerMatchedList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\t\tobjectMeta := types.NamespacedName{Name: c.PodName, Namespace: c.Namespace}\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(ctx, objectMeta, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"get pod %s err, %v\", c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"get\", err), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\tif !isImageConfigPodReady(pod) {\n\t\t\tlogrusField.Infof(\"pod %s is not ready\", c.PodName)\n\t\t\tstatuses = append(statuses, status.CreateFailResourceStatus(spec.PodNotReady.Sprintf(c.PodName),\n\t\t\t\tspec.PodNotReady.Code))\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := d.modifyPodImage(ctx, pod, imageName, imageTag); err != nil {\n\t\t\tlogrusField.Warningf(\"modify pod %s image err, %v\", c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"update\", err), spec.K8sExecFailed.Code)\n\t\t} else {\n\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t\tsuccess = true\n\t\t}\n\t\tstatuses = append(statuses, status)\n\t}\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *ImageConfigActionExecutor) destroy(ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tcontainerMatchedList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(experimentId, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\texperimentStatus := v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{})\n\tstatuses := experimentStatus.ResStatuses\n\tfor _, c := range containerMatchedList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: c.GetIdentifier(),\n\t\t}\n\t\tobjectMeta := types.NamespacedName{Name: c.PodName, Namespace: c.Namespace}\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(ctx, objectMeta, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"get pod %s err, %v\", c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"get\", err), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\terr = d.client.Delete(ctx, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Errorf(\"delete pod %s err, %v\", c.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(spec.K8sExecFailed.Sprintf(\"delete\", err), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatuses = append(statuses, status)\n\t}\n\texperimentStatus.ResStatuses = statuses\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\n// modifyPodImage modifies the pod container images to simulate image config error.\n// If imageName and imageTag are both empty, it appends \"-image-config-error\" to the original image (default behavior).\n// If imageName is provided, the image name portion is replaced.\n// If imageTag is provided, the image tag portion is replaced.\nfunc (d *ImageConfigActionExecutor) modifyPodImage(ctx context.Context, pod *v1.Pod, imageName, imageTag string) error {\n\tmodified := false\n\tfor i, container := range pod.Spec.Containers {\n\t\tkey := fmt.Sprintf(\"%s-%s\", \"chaosblade.io/imageconfig\", container.Name)\n\t\tif pod.Annotations == nil {\n\t\t\tpod.Annotations = make(map[string]string)\n\t\t}\n\t\tif isImageConfigAnnotationExist(pod.Annotations, key) {\n\t\t\tcontinue\n\t\t}\n\t\tpod.Annotations[key] = container.Image\n\t\tpod.Spec.Containers[i].Image = buildNewImage(container.Image, imageName, imageTag)\n\t\tmodified = true\n\t}\n\tif !modified {\n\t\treturn nil\n\t}\n\treturn d.client.Update(ctx, pod)\n}\n\n// buildNewImage constructs the new image string based on provided imageName and imageTag.\n// If both are empty, returns \"{original}-image-config-error\" for backward compatibility.\n// The original image format can be: \"name\", \"name:tag\", \"registry/name\", \"registry/name:tag\".\nfunc buildNewImage(originalImage, imageName, imageTag string) string {\n\tif imageName == \"\" && imageTag == \"\" {\n\t\treturn fmt.Sprintf(\"%s-image-config-error\", originalImage)\n\t}\n\n\t// Parse the original image into name and tag parts\n\torigName, origTag := parseImage(originalImage)\n\n\tif imageName != \"\" {\n\t\torigName = imageName\n\t}\n\tif imageTag != \"\" {\n\t\torigTag = imageTag\n\t}\n\n\tif origTag == \"\" {\n\t\treturn origName\n\t}\n\treturn fmt.Sprintf(\"%s:%s\", origName, origTag)\n}\n\n// parseImage splits an image reference into name and tag.\n// Handles formats like \"nginx\", \"nginx:latest\", \"registry.example.com/nginx:v1.0\".\nfunc parseImage(image string) (name, tag string) {\n\t// Find the last colon that is not part of a registry port (after the last /)\n\tslashIdx := strings.LastIndex(image, \"/\")\n\tcolonIdx := strings.LastIndex(image, \":\")\n\n\t// If colon exists and is after the last slash, it's a tag separator\n\tif colonIdx > slashIdx {\n\t\treturn image[:colonIdx], image[colonIdx+1:]\n\t}\n\treturn image, \"\"\n}\n\n// isImageConfigAnnotationExist checks if the annotation already exists\nfunc isImageConfigAnnotationExist(annotation map[string]string, key string) bool {\n\t_, ok := annotation[key]\n\tif !ok {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// isImageConfigPodReady checks if the pod is ready\nfunc isImageConfigPodReady(pod *v1.Pod) bool {\n\tif pod.ObjectMeta.DeletionTimestamp != nil {\n\t\treturn false\n\t}\n\tfor _, condition := range pod.Status.Conditions {\n\t\tif condition.Type == v1.PodReady &&\n\t\t\tcondition.Status == v1.ConditionTrue {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "exec/pod/imageconfigexp_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"testing\"\n)\n\nfunc TestParseImage(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\twantName string\n\t\twantTag  string\n\t}{\n\t\t{\"nginx\", \"nginx\", \"\"},\n\t\t{\"nginx:latest\", \"nginx\", \"latest\"},\n\t\t{\"nginx:1.19.0\", \"nginx\", \"1.19.0\"},\n\t\t{\"registry.example.com/nginx\", \"registry.example.com/nginx\", \"\"},\n\t\t{\"registry.example.com/nginx:v1.0\", \"registry.example.com/nginx\", \"v1.0\"},\n\t\t{\"registry.example.com:5000/nginx\", \"registry.example.com:5000/nginx\", \"\"},\n\t\t{\"registry.example.com:5000/nginx:v1.0\", \"registry.example.com:5000/nginx\", \"v1.0\"},\n\t\t{\"my-registry.io:5000/my-org/my-image:sha-abc123\", \"my-registry.io:5000/my-org/my-image\", \"sha-abc123\"},\n\t\t{\"localhost:5000/test\", \"localhost:5000/test\", \"\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tgotName, gotTag := parseImage(tt.input)\n\t\t\tif gotName != tt.wantName {\n\t\t\t\tt.Errorf(\"parseImage(%q) name = %q, want %q\", tt.input, gotName, tt.wantName)\n\t\t\t}\n\t\t\tif gotTag != tt.wantTag {\n\t\t\t\tt.Errorf(\"parseImage(%q) tag = %q, want %q\", tt.input, gotTag, tt.wantTag)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBuildNewImage(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\toriginalImage string\n\t\timageName     string\n\t\timageTag      string\n\t\twant          string\n\t}{\n\t\t// Default behavior: both params empty -> append \"-image-config-error\"\n\t\t{\n\t\t\tname:          \"default behavior - simple image\",\n\t\t\toriginalImage: \"nginx\",\n\t\t\timageName:     \"\",\n\t\t\timageTag:      \"\",\n\t\t\twant:          \"nginx-image-config-error\",\n\t\t},\n\t\t{\n\t\t\tname:          \"default behavior - image with tag\",\n\t\t\toriginalImage: \"nginx:latest\",\n\t\t\timageName:     \"\",\n\t\t\timageTag:      \"\",\n\t\t\twant:          \"nginx:latest-image-config-error\",\n\t\t},\n\t\t{\n\t\t\tname:          \"default behavior - full registry path\",\n\t\t\toriginalImage: \"registry.example.com/nginx:v1.0\",\n\t\t\timageName:     \"\",\n\t\t\timageTag:      \"\",\n\t\t\twant:          \"registry.example.com/nginx:v1.0-image-config-error\",\n\t\t},\n\n\t\t// Only imageName provided\n\t\t{\n\t\t\tname:          \"replace image name only - simple image\",\n\t\t\toriginalImage: \"nginx\",\n\t\t\timageName:     \"nginx-not-exist\",\n\t\t\timageTag:      \"\",\n\t\t\twant:          \"nginx-not-exist\",\n\t\t},\n\t\t{\n\t\t\tname:          \"replace image name only - image with tag\",\n\t\t\toriginalImage: \"nginx:latest\",\n\t\t\timageName:     \"nginx-not-exist\",\n\t\t\timageTag:      \"\",\n\t\t\twant:          \"nginx-not-exist:latest\",\n\t\t},\n\t\t{\n\t\t\tname:          \"replace image name only - full registry path\",\n\t\t\toriginalImage: \"registry.example.com/nginx:v1.0\",\n\t\t\timageName:     \"my-bad-image\",\n\t\t\timageTag:      \"\",\n\t\t\twant:          \"my-bad-image:v1.0\",\n\t\t},\n\n\t\t// Only imageTag provided\n\t\t{\n\t\t\tname:          \"replace tag only - simple image without tag\",\n\t\t\toriginalImage: \"nginx\",\n\t\t\timageName:     \"\",\n\t\t\timageTag:      \"non-existent-tag\",\n\t\t\twant:          \"nginx:non-existent-tag\",\n\t\t},\n\t\t{\n\t\t\tname:          \"replace tag only - image with tag\",\n\t\t\toriginalImage: \"nginx:latest\",\n\t\t\timageName:     \"\",\n\t\t\timageTag:      \"non-existent-tag\",\n\t\t\twant:          \"nginx:non-existent-tag\",\n\t\t},\n\t\t{\n\t\t\tname:          \"replace tag only - full registry path\",\n\t\t\toriginalImage: \"registry.example.com/nginx:v1.0\",\n\t\t\timageName:     \"\",\n\t\t\timageTag:      \"broken-tag\",\n\t\t\twant:          \"registry.example.com/nginx:broken-tag\",\n\t\t},\n\n\t\t// Both imageName and imageTag provided\n\t\t{\n\t\t\tname:          \"replace both name and tag\",\n\t\t\toriginalImage: \"nginx:latest\",\n\t\t\timageName:     \"bad-image\",\n\t\t\timageTag:      \"bad-tag\",\n\t\t\twant:          \"bad-image:bad-tag\",\n\t\t},\n\t\t{\n\t\t\tname:          \"replace both - full registry path\",\n\t\t\toriginalImage: \"registry.example.com/nginx:v1.0\",\n\t\t\timageName:     \"totally-wrong\",\n\t\t\timageTag:      \"no-such-tag\",\n\t\t\twant:          \"totally-wrong:no-such-tag\",\n\t\t},\n\n\t\t// Edge cases with registry port\n\t\t{\n\t\t\tname:          \"registry with port - replace tag only\",\n\t\t\toriginalImage: \"registry.example.com:5000/nginx:v1.0\",\n\t\t\timageName:     \"\",\n\t\t\timageTag:      \"broken\",\n\t\t\twant:          \"registry.example.com:5000/nginx:broken\",\n\t\t},\n\t\t{\n\t\t\tname:          \"registry with port no tag - replace tag\",\n\t\t\toriginalImage: \"registry.example.com:5000/nginx\",\n\t\t\timageName:     \"\",\n\t\t\timageTag:      \"broken\",\n\t\t\twant:          \"registry.example.com:5000/nginx:broken\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := buildNewImage(tt.originalImage, tt.imageName, tt.imageTag)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"buildNewImage(%q, %q, %q) = %q, want %q\",\n\t\t\t\t\ttt.originalImage, tt.imageName, tt.imageTag, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsImageConfigAnnotationExist(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tannotation map[string]string\n\t\tkey        string\n\t\twant       bool\n\t}{\n\t\t{\n\t\t\tname:       \"annotation exists\",\n\t\t\tannotation: map[string]string{\"imageConfig-nginx\": \"nginx:latest\"},\n\t\t\tkey:        \"imageConfig-nginx\",\n\t\t\twant:       true,\n\t\t},\n\t\t{\n\t\t\tname:       \"annotation does not exist\",\n\t\t\tannotation: map[string]string{\"imageConfig-nginx\": \"nginx:latest\"},\n\t\t\tkey:        \"imageConfig-redis\",\n\t\t\twant:       false,\n\t\t},\n\t\t{\n\t\t\tname:       \"empty annotations\",\n\t\t\tannotation: map[string]string{},\n\t\t\tkey:        \"imageConfig-nginx\",\n\t\t\twant:       false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := isImageConfigAnnotationExist(tt.annotation, tt.key)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"isImageConfigAnnotationExist() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "exec/pod/imagepullsecretserror.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\t// ChaosBladeIPSBackupAnnotation marks a Secret as a backup created by this action\n\tChaosBladeIPSBackupAnnotation = \"chaosblade.io/ips-backup\"\n\t// ChaosBladeIPSOriginalNameAnnotation stores the original Secret name\n\tChaosBladeIPSOriginalNameAnnotation = \"chaosblade.io/ips-original-name\"\n\t// ChaosBladeIPSOriginalNamespaceAnnotation stores the original Secret namespace\n\tChaosBladeIPSOriginalNamespaceAnnotation = \"chaosblade.io/ips-original-namespace\"\n\t// ChaosBladeIPSExperimentLabel is the label key for experiment ID on backup Secrets\n\tChaosBladeIPSExperimentLabel = \"chaosblade.io/experiment\"\n)\n\ntype ImagePullSecretsErrorActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc NewImagePullSecretsErrorActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &ImagePullSecretsErrorActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName: \"secret-name\",\n\t\t\t\t\tDesc: \"The name of the imagePullSecret to corrupt. If not specified, all imagePullSecrets of the target Pod will be corrupted\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &ImagePullSecretsErrorActionExecutor{client: client},\n\t\t\tActionExample: `# Simulate image pull authentication failure for a specific pod\nblade create k8s pod-pod imagepullsecretserror --names my-app-pod --namespace default --kubeconfig ~/.kube/config\n\n# Simulate image pull authentication failure for pods selected by labels\nblade create k8s pod-pod imagepullsecretserror --labels app=nginx --namespace default --kubeconfig ~/.kube/config\n\n# Corrupt only a specific imagePullSecret\nblade create k8s pod-pod imagepullsecretserror --names my-app-pod --namespace default --secret-name my-registry-secret --kubeconfig ~/.kube/config\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*ImagePullSecretsErrorActionSpec) Name() string {\n\treturn \"imagepullsecretserror\"\n}\n\nfunc (*ImagePullSecretsErrorActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*ImagePullSecretsErrorActionSpec) ShortDesc() string {\n\treturn \"Simulate image pull authentication failure by corrupting imagePullSecrets\"\n}\n\nfunc (*ImagePullSecretsErrorActionSpec) LongDesc() string {\n\treturn \"Simulate the scenario where a Pod fails to pull images from a private registry due to \" +\n\t\t\"authentication failure. This fault is injected by corrupting the credentials in the Secret \" +\n\t\t\"referenced by the Pod's imagePullSecrets field. The original Secret data is backed up to a \" +\n\t\t\"separate Secret for recovery. After corruption, the Pod is deleted so the controller recreates \" +\n\t\t\"it, and the new Pod will fail to pull images with ErrImagePull/ImagePullBackOff status. \" +\n\t\t\"When the experiment is destroyed, the original Secret is restored and the Pod is deleted again \" +\n\t\t\"to trigger a successful image pull.\"\n}\n\ntype ImagePullSecretsErrorActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*ImagePullSecretsErrorActionExecutor) Name() string {\n\treturn \"imagepullsecretserror\"\n}\n\nfunc (*ImagePullSecretsErrorActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *ImagePullSecretsErrorActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *ImagePullSecretsErrorActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\tsecretNameFilter := expModel.ActionFlags[\"secret-name\"]\n\n\t// Track processed Secrets to avoid corrupting the same Secret multiple times\n\t// when multiple Pods reference the same Secret\n\tprocessedSecrets := make(map[string]bool)\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\n\tfor _, meta := range containerObjectMetaList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: meta.GetIdentifier(),\n\t\t}\n\n\t\t// Get the Pod\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Name: meta.PodName, Namespace: meta.Namespace}, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Warningf(\"get pod %s/%s failed: %v\", meta.Namespace, meta.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check imagePullSecrets\n\t\tif len(pod.Spec.ImagePullSecrets) == 0 {\n\t\t\tlogrusField.Warningf(\"pod %s/%s has no imagePullSecrets\", meta.Namespace, meta.PodName)\n\t\t\tstatus = status.CreateFailResourceStatus(\"pod has no imagePullSecrets\", spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Filter by --secret-name if specified\n\t\ttargetSecretRefs := pod.Spec.ImagePullSecrets\n\t\tif secretNameFilter != \"\" {\n\t\t\ttargetSecretRefs = filterSecretRefs(targetSecretRefs, secretNameFilter)\n\t\t\tif len(targetSecretRefs) == 0 {\n\t\t\t\tlogrusField.Warningf(\"pod %s/%s does not have imagePullSecret %s\", meta.Namespace, meta.PodName, secretNameFilter)\n\t\t\t\tstatus = status.CreateFailResourceStatus(\n\t\t\t\t\tfmt.Sprintf(\"pod does not have imagePullSecret %s\", secretNameFilter), spec.K8sExecFailed.Code,\n\t\t\t\t)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Process each target Secret for this Pod.\n\t\t// All Secrets must be corrupted successfully before deleting the Pod.\n\t\t// If any corruption fails, roll back the ones that succeeded for this Pod\n\t\t// to avoid partial corruption leading to unpredictable behavior.\n\t\tcorruptedInThisRound := make([]string, 0, len(targetSecretRefs))\n\t\tallSecretsOk := true\n\t\tfor _, secretRef := range targetSecretRefs {\n\t\t\tsecretKey := fmt.Sprintf(\"%s/%s\", meta.Namespace, secretRef.Name)\n\t\t\tif processedSecrets[secretKey] {\n\t\t\t\t// Already corrupted by a previous Pod in this experiment\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err := d.corruptSecret(ctx, logrusField, experimentId, meta.Namespace, secretRef.Name); err != nil {\n\t\t\t\tlogrusField.Warningf(\"corrupt secret %s failed: %v\", secretKey, err)\n\t\t\t\tallSecretsOk = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcorruptedInThisRound = append(corruptedInThisRound, secretKey)\n\t\t}\n\n\t\tif !allSecretsOk {\n\t\t\t// Roll back Secrets corrupted in this round to avoid partial corruption.\n\t\t\t// Secrets corrupted by previous Pods (already in processedSecrets) are not\n\t\t\t// rolled back here because those Pods have already been deleted successfully.\n\t\t\tfor _, secretKey := range corruptedInThisRound {\n\t\t\t\tif err := d.rollbackSecret(ctx, logrusField, experimentId, secretKey); err != nil {\n\t\t\t\t\tlogrusField.Warningf(\"rollback secret %s failed: %v\", secretKey, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tstatus = status.CreateFailResourceStatus(\"failed to corrupt all imagePullSecrets, rolled back\", spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Mark all newly corrupted Secrets as processed\n\t\tfor _, secretKey := range corruptedInThisRound {\n\t\t\tprocessedSecrets[secretKey] = true\n\t\t}\n\n\t\t// Delete the Pod to trigger recreation with corrupted credentials\n\t\tif err := d.client.Delete(ctx, pod); err != nil {\n\t\t\tif !apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"delete pod %s/%s failed: %v, rolling back corrupted secrets\", meta.Namespace, meta.PodName, err)\n\t\t\t\t// Roll back Secrets corrupted in this round since Pod won't be recreated\n\t\t\t\tfor _, secretKey := range corruptedInThisRound {\n\t\t\t\t\tif rbErr := d.rollbackSecret(ctx, logrusField, experimentId, secretKey); rbErr != nil {\n\t\t\t\t\t\tlogrusField.Warningf(\"rollback secret %s after pod delete failure: %v\", secretKey, rbErr)\n\t\t\t\t\t}\n\t\t\t\t\tdelete(processedSecrets, secretKey)\n\t\t\t\t}\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"delete pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tlogrusField.Infof(\"corrupted imagePullSecrets and deleted pod %s/%s\", meta.Namespace, meta.PodName)\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatuses = append(statuses, status)\n\t\tsuccess = true\n\t}\n\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *ImagePullSecretsErrorActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\t// Collect all unique namespaces involved\n\tnamespaces := make(map[string]bool)\n\tfor _, meta := range containerObjectMetaList {\n\t\tnamespaces[meta.Namespace] = true\n\t}\n\n\t// Find and restore all backup Secrets for this experiment\n\tallSuccess := true\n\tfor ns := range namespaces {\n\t\tif err := d.restoreSecretsInNamespace(ctx, logrusField, experimentId, ns); err != nil {\n\t\t\tlogrusField.Warningf(\"restore secrets in namespace %s failed: %v\", ns, err)\n\t\t\tallSuccess = false\n\t\t}\n\t}\n\n\t// Delete Pods to trigger recreation with restored credentials\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tfor _, meta := range containerObjectMetaList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: meta.GetIdentifier(),\n\t\t}\n\n\t\tpod := &v1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      meta.PodName,\n\t\t\t\tNamespace: meta.Namespace,\n\t\t\t},\n\t\t}\n\t\tif err := d.client.Delete(ctx, pod); err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Infof(\"pod %s/%s already deleted\", meta.Namespace, meta.PodName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"delete pod %s/%s failed: %v\", meta.Namespace, meta.PodName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"delete pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tallSuccess = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatus.State = v1alpha1.DestroyedState\n\t\tstatuses = append(statuses, status)\n\t}\n\n\tif allSuccess {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus(statuses))\n\t}\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses))\n}\n\n// corruptSecret backs up the original Secret data, then corrupts the credentials\nfunc (d *ImagePullSecretsErrorActionExecutor) corruptSecret(ctx context.Context, logrusField *logrus.Entry, experimentId, namespace, secretName string) error {\n\t// Get the Secret\n\tsecret := &v1.Secret{}\n\tif err := d.client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, secret); err != nil {\n\t\treturn fmt.Errorf(\"get secret %s/%s failed: %v\", namespace, secretName, err)\n\t}\n\n\t// Validate Secret type\n\tif secret.Type != v1.SecretTypeDockerConfigJson && secret.Type != v1.SecretTypeDockercfg {\n\t\treturn fmt.Errorf(\"secret %s/%s type is %s, expected %s or %s\",\n\t\t\tnamespace, secretName, secret.Type, v1.SecretTypeDockerConfigJson, v1.SecretTypeDockercfg)\n\t}\n\n\t// Create backup Secret\n\tbackupName := generateBackupSecretName(experimentId, namespace, secretName)\n\tbackupSecret := &v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      backupName,\n\t\t\tNamespace: namespace,\n\t\t\tLabels: map[string]string{\n\t\t\t\tChaosBladeIPSExperimentLabel: experimentId,\n\t\t\t},\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladeIPSBackupAnnotation:            \"true\",\n\t\t\t\tChaosBladeIPSOriginalNameAnnotation:      secretName,\n\t\t\t\tChaosBladeIPSOriginalNamespaceAnnotation: namespace,\n\t\t\t},\n\t\t},\n\t\tType: secret.Type,\n\t\tData: copySecretData(secret.Data),\n\t}\n\n\tcreatedBackup := false\n\tif err := d.client.Create(ctx, backupSecret); err != nil {\n\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\t// Check if the existing backup belongs to this experiment\n\t\t\texistingBackup := &v1.Secret{}\n\t\t\tif getErr := d.client.Get(ctx, types.NamespacedName{Name: backupName, Namespace: namespace}, existingBackup); getErr != nil {\n\t\t\t\treturn fmt.Errorf(\"backup secret %s/%s already exists and failed to verify owner: %v\", namespace, backupName, getErr)\n\t\t\t}\n\t\t\tif existingBackup.Labels[ChaosBladeIPSExperimentLabel] != experimentId {\n\t\t\t\treturn fmt.Errorf(\"secret %s/%s is already being used by another experiment %s\",\n\t\t\t\t\tnamespace, secretName, existingBackup.Labels[ChaosBladeIPSExperimentLabel])\n\t\t\t}\n\t\t\tlogrusField.Infof(\"backup secret %s/%s already exists for this experiment, skip creation\", namespace, backupName)\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"create backup secret %s/%s failed: %v\", namespace, backupName, err)\n\t\t}\n\t} else {\n\t\tcreatedBackup = true\n\t\tlogrusField.Infof(\"created backup secret %s/%s for original %s\", namespace, backupName, secretName)\n\t}\n\n\t// Corrupt the credentials\n\tvar corruptedData []byte\n\tvar corruptErr error\n\tif secret.Type == v1.SecretTypeDockerConfigJson {\n\t\tdataKey := v1.DockerConfigJsonKey\n\t\toriginalData, ok := secret.Data[dataKey]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"secret %s/%s has no %s key\", namespace, secretName, dataKey)\n\t\t}\n\t\tcorruptedData, corruptErr = corruptDockerConfigJSON(originalData)\n\t} else {\n\t\t// kubernetes.io/dockercfg\n\t\tdataKey := v1.DockerConfigKey\n\t\toriginalData, ok := secret.Data[dataKey]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"secret %s/%s has no %s key\", namespace, secretName, dataKey)\n\t\t}\n\t\tcorruptedData, corruptErr = corruptDockerCfg(originalData)\n\t}\n\n\tif corruptErr != nil {\n\t\t// Rollback: only delete the backup if we created it in this call\n\t\tif createdBackup {\n\t\t\tif delErr := d.client.Delete(ctx, backupSecret); delErr != nil {\n\t\t\t\tlogrusField.Warningf(\"rollback: delete backup secret %s/%s failed: %v\", namespace, backupName, delErr)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"corrupt secret data failed: %v\", corruptErr)\n\t}\n\n\t// Update the Secret with corrupted data\n\tif secret.Type == v1.SecretTypeDockerConfigJson {\n\t\tsecret.Data[v1.DockerConfigJsonKey] = corruptedData\n\t} else {\n\t\tsecret.Data[v1.DockerConfigKey] = corruptedData\n\t}\n\n\tif err := d.client.Update(ctx, secret); err != nil {\n\t\t// Rollback: only delete the backup if we created it in this call\n\t\tif createdBackup {\n\t\t\tif delErr := d.client.Delete(ctx, backupSecret); delErr != nil {\n\t\t\t\tlogrusField.Warningf(\"rollback: delete backup secret %s/%s failed: %v\", namespace, backupName, delErr)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"update secret %s/%s failed: %v\", namespace, secretName, err)\n\t}\n\n\tlogrusField.Infof(\"corrupted credentials in secret %s/%s\", namespace, secretName)\n\treturn nil\n}\n\n// rollbackSecret restores a single Secret from its backup and deletes the backup.\n// secretKey is in the format \"namespace/secretName\".\nfunc (d *ImagePullSecretsErrorActionExecutor) rollbackSecret(ctx context.Context, logrusField *logrus.Entry, experimentId, secretKey string) error {\n\tparts := strings.SplitN(secretKey, \"/\", 2)\n\tif len(parts) != 2 {\n\t\treturn fmt.Errorf(\"invalid secret key format: %s\", secretKey)\n\t}\n\tnamespace, secretName := parts[0], parts[1]\n\tbackupName := generateBackupSecretName(experimentId, namespace, secretName)\n\n\t// Get the backup Secret\n\tbackup := &v1.Secret{}\n\tif err := d.client.Get(ctx, types.NamespacedName{Name: backupName, Namespace: namespace}, backup); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\tlogrusField.Infof(\"rollback: backup secret %s/%s not found, nothing to restore\", namespace, backupName)\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"rollback: get backup secret %s/%s failed: %v\", namespace, backupName, err)\n\t}\n\n\t// Get the original Secret and restore its data\n\toriginalSecret := &v1.Secret{}\n\tif err := d.client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, originalSecret); err != nil {\n\t\tif !apierrors.IsNotFound(err) {\n\t\t\treturn fmt.Errorf(\"rollback: get original secret %s/%s failed: %v\", namespace, secretName, err)\n\t\t}\n\t\t// Original was deleted externally; just clean up the backup\n\t} else {\n\t\toriginalSecret.Data = copySecretData(backup.Data)\n\t\toriginalSecret.Type = backup.Type\n\t\tif err := d.client.Update(ctx, originalSecret); err != nil {\n\t\t\treturn fmt.Errorf(\"rollback: restore secret %s/%s failed: %v\", namespace, secretName, err)\n\t\t}\n\t\tlogrusField.Infof(\"rollback: restored secret %s/%s from backup\", namespace, secretName)\n\t}\n\n\t// Delete the backup Secret\n\tif err := d.client.Delete(ctx, backup); err != nil && !apierrors.IsNotFound(err) {\n\t\tlogrusField.Warningf(\"rollback: delete backup secret %s/%s failed: %v\", namespace, backupName, err)\n\t}\n\treturn nil\n}\n\n// restoreSecretsInNamespace finds all backup Secrets in the given namespace for this experiment\n// and restores the original Secrets from the backups\nfunc (d *ImagePullSecretsErrorActionExecutor) restoreSecretsInNamespace(ctx context.Context, logrusField *logrus.Entry, experimentId, namespace string) error {\n\tbackupList := &v1.SecretList{}\n\tlistOpts := []client.ListOption{\n\t\tclient.InNamespace(namespace),\n\t\tclient.MatchingLabels{ChaosBladeIPSExperimentLabel: experimentId},\n\t}\n\tif err := d.client.List(ctx, backupList, listOpts...); err != nil {\n\t\treturn fmt.Errorf(\"list backup secrets in namespace %s failed: %v\", namespace, err)\n\t}\n\n\tvar errs []string\n\tfor i := range backupList.Items {\n\t\tbackup := &backupList.Items[i]\n\n\t\t// Only process Secrets explicitly marked as IPS backups\n\t\tif backup.Annotations[ChaosBladeIPSBackupAnnotation] != \"true\" {\n\t\t\tcontinue\n\t\t}\n\n\t\toriginalName := backup.Annotations[ChaosBladeIPSOriginalNameAnnotation]\n\t\toriginalNamespace := backup.Annotations[ChaosBladeIPSOriginalNamespaceAnnotation]\n\n\t\tif originalName == \"\" || originalNamespace == \"\" {\n\t\t\tlogrusField.Warningf(\"backup secret %s/%s missing original name/namespace annotations, skip\",\n\t\t\t\tnamespace, backup.Name)\n\t\t\terrs = append(errs, fmt.Sprintf(\"backup %s/%s missing annotations\", namespace, backup.Name))\n\t\t\tcontinue\n\t\t}\n\n\t\t// Get the original Secret\n\t\toriginalSecret := &v1.Secret{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Name: originalName, Namespace: originalNamespace}, originalSecret)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"original secret %s/%s not found, deleting backup\", originalNamespace, originalName)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get original secret %s/%s failed: %v\", originalNamespace, originalName, err)\n\t\t\t\terrs = append(errs, fmt.Sprintf(\"get secret %s/%s failed: %v\", originalNamespace, originalName, err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\t// Restore the original data and type\n\t\t\toriginalSecret.Data = copySecretData(backup.Data)\n\t\t\toriginalSecret.Type = backup.Type\n\t\t\tif err := d.client.Update(ctx, originalSecret); err != nil {\n\t\t\t\tlogrusField.Warningf(\"restore secret %s/%s failed: %v\", originalNamespace, originalName, err)\n\t\t\t\terrs = append(errs, fmt.Sprintf(\"restore secret %s/%s failed: %v\", originalNamespace, originalName, err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlogrusField.Infof(\"restored secret %s/%s from backup\", originalNamespace, originalName)\n\t\t}\n\n\t\t// Delete the backup Secret\n\t\tif err := d.client.Delete(ctx, backup); err != nil {\n\t\t\tif !apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"delete backup secret %s/%s failed: %v\", namespace, backup.Name, err)\n\t\t\t\terrs = append(errs, fmt.Sprintf(\"delete backup %s/%s failed: %v\", namespace, backup.Name, err))\n\t\t\t}\n\t\t} else {\n\t\t\tlogrusField.Infof(\"deleted backup secret %s/%s\", namespace, backup.Name)\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"restore secrets in namespace %s had %d error(s): %s\", namespace, len(errs), strings.Join(errs, \"; \"))\n\t}\n\treturn nil\n}\n\n// generateBackupSecretName creates a deterministic backup Secret name\n// Format: chaosblade-ips-<experimentId[:8]>-<sha256(namespace/secretName)[:8]>\nfunc generateBackupSecretName(experimentId, namespace, secretName string) string {\n\thash := sha256.Sum256([]byte(fmt.Sprintf(\"%s/%s\", namespace, secretName)))\n\thashStr := fmt.Sprintf(\"%x\", hash[:4])\n\n\texpIdPrefix := experimentId\n\tif len(expIdPrefix) > 8 {\n\t\texpIdPrefix = expIdPrefix[:8]\n\t}\n\n\treturn fmt.Sprintf(\"chaosblade-ips-%s-%s\", expIdPrefix, hashStr)\n}\n\n// corruptDockerConfigJSON corrupts the auth credentials in a .dockerconfigjson format Secret\n// The JSON structure is preserved but all credentials are replaced with invalid values\nfunc corruptDockerConfigJSON(data []byte) ([]byte, error) {\n\tvar config map[string]interface{}\n\tif err := json.Unmarshal(data, &config); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshal dockerconfigjson failed: %v\", err)\n\t}\n\n\tauths, ok := config[\"auths\"]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"dockerconfigjson has no auths key\")\n\t}\n\n\tauthsMap, ok := auths.(map[string]interface{})\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"auths field is not a map\")\n\t}\n\n\tif len(authsMap) == 0 {\n\t\treturn nil, fmt.Errorf(\"dockerconfigjson auths is empty, no credentials to corrupt\")\n\t}\n\n\t// Corrupt each registry's credentials\n\tif n := corruptRegistryCredentials(authsMap); n == 0 {\n\t\treturn nil, fmt.Errorf(\"dockerconfigjson: no valid registry credentials found to corrupt\")\n\t}\n\n\tconfig[\"auths\"] = authsMap\n\tresult, err := json.Marshal(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshal corrupted dockerconfigjson failed: %v\", err)\n\t}\n\treturn result, nil\n}\n\n// corruptDockerCfg corrupts the auth credentials in a .dockercfg format Secret\n// The .dockercfg format is: {\"registry\": {\"username\": \"...\", \"password\": \"...\", \"auth\": \"...\"}}\nfunc corruptDockerCfg(data []byte) ([]byte, error) {\n\tvar config map[string]interface{}\n\tif err := json.Unmarshal(data, &config); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshal dockercfg failed: %v\", err)\n\t}\n\n\tif len(config) == 0 {\n\t\treturn nil, fmt.Errorf(\"dockercfg is empty, no credentials to corrupt\")\n\t}\n\n\tif n := corruptRegistryCredentials(config); n == 0 {\n\t\treturn nil, fmt.Errorf(\"dockercfg: no valid registry credentials found to corrupt\")\n\t}\n\n\tresult, err := json.Marshal(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshal corrupted dockercfg failed: %v\", err)\n\t}\n\treturn result, nil\n}\n\n// corruptRegistryCredentials replaces the auth credentials in each registry entry with invalid values.\n// Returns the number of registry entries that were actually corrupted.\nfunc corruptRegistryCredentials(registries map[string]interface{}) int {\n\tinvalidAuth := base64.StdEncoding.EncodeToString([]byte(\"chaosblade-invalid-user:chaosblade-invalid-pass\"))\n\tcorrupted := 0\n\tfor registry, creds := range registries {\n\t\tcredsMap, ok := creds.(map[string]interface{})\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcredsMap[\"username\"] = \"chaosblade-invalid-user\"\n\t\tcredsMap[\"password\"] = \"chaosblade-invalid-pass\"\n\t\tcredsMap[\"auth\"] = invalidAuth\n\t\tdelete(credsMap, \"identitytoken\")\n\t\tdelete(credsMap, \"registrytoken\")\n\t\tregistries[registry] = credsMap\n\t\tcorrupted++\n\t}\n\treturn corrupted\n}\n\n// filterSecretRefs filters the imagePullSecrets list by a specific Secret name\nfunc filterSecretRefs(refs []v1.LocalObjectReference, name string) []v1.LocalObjectReference {\n\tfiltered := make([]v1.LocalObjectReference, 0)\n\tfor _, ref := range refs {\n\t\tif ref.Name == name {\n\t\t\tfiltered = append(filtered, ref)\n\t\t}\n\t}\n\treturn filtered\n}\n\n// copySecretData creates a deep copy of Secret data\nfunc copySecretData(data map[string][]byte) map[string][]byte {\n\tif data == nil {\n\t\treturn nil\n\t}\n\tresult := make(map[string][]byte, len(data))\n\tfor k, v := range data {\n\t\tcopied := make([]byte, len(v))\n\t\tcopy(copied, v)\n\t\tresult[k] = copied\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "exec/pod/imagepullsecretserror_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"testing\"\n\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nfunc TestCorruptDockerConfigJSON(t *testing.T) {\n\t// Valid dockerconfigjson with multiple registries\n\tinput := map[string]interface{}{\n\t\t\"auths\": map[string]interface{}{\n\t\t\t\"registry.example.com\": map[string]interface{}{\n\t\t\t\t\"username\": \"real-user\",\n\t\t\t\t\"password\": \"real-pass\",\n\t\t\t\t\"auth\":     base64.StdEncoding.EncodeToString([]byte(\"real-user:real-pass\")),\n\t\t\t},\n\t\t\t\"docker.io\": map[string]interface{}{\n\t\t\t\t\"username\":      \"docker-user\",\n\t\t\t\t\"password\":      \"docker-pass\",\n\t\t\t\t\"auth\":          base64.StdEncoding.EncodeToString([]byte(\"docker-user:docker-pass\")),\n\t\t\t\t\"identitytoken\": \"some-token\",\n\t\t\t},\n\t\t},\n\t}\n\tinputBytes, err := json.Marshal(input)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to marshal input: %v\", err)\n\t}\n\n\tresult, err := corruptDockerConfigJSON(inputBytes)\n\tif err != nil {\n\t\tt.Fatalf(\"corruptDockerConfigJSON failed: %v\", err)\n\t}\n\n\t// Parse result\n\tvar output map[string]interface{}\n\tif err := json.Unmarshal(result, &output); err != nil {\n\t\tt.Fatalf(\"failed to unmarshal result: %v\", err)\n\t}\n\n\tauths, ok := output[\"auths\"].(map[string]interface{})\n\tif !ok {\n\t\tt.Fatal(\"result has no 'auths' map\")\n\t}\n\n\t// Verify both registries are corrupted\n\texpectedAuth := base64.StdEncoding.EncodeToString([]byte(\"chaosblade-invalid-user:chaosblade-invalid-pass\"))\n\n\tfor registry, creds := range auths {\n\t\tcredsMap, ok := creds.(map[string]interface{})\n\t\tif !ok {\n\t\t\tt.Fatalf(\"credentials for %s is not a map\", registry)\n\t\t}\n\n\t\tif credsMap[\"username\"] != \"chaosblade-invalid-user\" {\n\t\t\tt.Errorf(\"registry %s: expected username 'chaosblade-invalid-user', got '%v'\", registry, credsMap[\"username\"])\n\t\t}\n\t\tif credsMap[\"password\"] != \"chaosblade-invalid-pass\" {\n\t\t\tt.Errorf(\"registry %s: expected password 'chaosblade-invalid-pass', got '%v'\", registry, credsMap[\"password\"])\n\t\t}\n\t\tif credsMap[\"auth\"] != expectedAuth {\n\t\t\tt.Errorf(\"registry %s: expected auth '%s', got '%v'\", registry, expectedAuth, credsMap[\"auth\"])\n\t\t}\n\t\tif _, exists := credsMap[\"identitytoken\"]; exists {\n\t\t\tt.Errorf(\"registry %s: identitytoken should be removed\", registry)\n\t\t}\n\t\tif _, exists := credsMap[\"registrytoken\"]; exists {\n\t\t\tt.Errorf(\"registry %s: registrytoken should be removed\", registry)\n\t\t}\n\t}\n}\n\nfunc TestCorruptDockerConfigJSON_EmptyAuths(t *testing.T) {\n\tinput := map[string]interface{}{\n\t\t\"auths\": map[string]interface{}{},\n\t}\n\tinputBytes, err := json.Marshal(input)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to marshal input: %v\", err)\n\t}\n\n\t_, err = corruptDockerConfigJSON(inputBytes)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for empty auths, got nil\")\n\t}\n}\n\nfunc TestCorruptDockerConfigJSON_NoAuthsKey(t *testing.T) {\n\tinput := map[string]interface{}{\n\t\t\"someOtherKey\": \"value\",\n\t}\n\tinputBytes, err := json.Marshal(input)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to marshal input: %v\", err)\n\t}\n\n\t_, err = corruptDockerConfigJSON(inputBytes)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for missing auths key, got nil\")\n\t}\n}\n\nfunc TestCorruptDockerConfigJSON_InvalidJSON(t *testing.T) {\n\t_, err := corruptDockerConfigJSON([]byte(\"not valid json\"))\n\tif err == nil {\n\t\tt.Fatal(\"expected error for invalid JSON, got nil\")\n\t}\n}\n\nfunc TestCorruptDockerCfg(t *testing.T) {\n\t// .dockercfg format: top-level keys are registries\n\tinput := map[string]interface{}{\n\t\t\"https://index.docker.io/v1/\": map[string]interface{}{\n\t\t\t\"username\": \"myuser\",\n\t\t\t\"password\": \"mypass\",\n\t\t\t\"email\":    \"user@example.com\",\n\t\t\t\"auth\":     base64.StdEncoding.EncodeToString([]byte(\"myuser:mypass\")),\n\t\t},\n\t}\n\tinputBytes, err := json.Marshal(input)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to marshal input: %v\", err)\n\t}\n\n\tresult, err := corruptDockerCfg(inputBytes)\n\tif err != nil {\n\t\tt.Fatalf(\"corruptDockerCfg failed: %v\", err)\n\t}\n\n\tvar output map[string]interface{}\n\tif err := json.Unmarshal(result, &output); err != nil {\n\t\tt.Fatalf(\"failed to unmarshal result: %v\", err)\n\t}\n\n\texpectedAuth := base64.StdEncoding.EncodeToString([]byte(\"chaosblade-invalid-user:chaosblade-invalid-pass\"))\n\tfor registry, creds := range output {\n\t\tcredsMap, ok := creds.(map[string]interface{})\n\t\tif !ok {\n\t\t\tt.Fatalf(\"credentials for %s is not a map\", registry)\n\t\t}\n\t\tif credsMap[\"username\"] != \"chaosblade-invalid-user\" {\n\t\t\tt.Errorf(\"registry %s: expected username 'chaosblade-invalid-user', got '%v'\", registry, credsMap[\"username\"])\n\t\t}\n\t\tif credsMap[\"password\"] != \"chaosblade-invalid-pass\" {\n\t\t\tt.Errorf(\"registry %s: expected password 'chaosblade-invalid-pass', got '%v'\", registry, credsMap[\"password\"])\n\t\t}\n\t\tif credsMap[\"auth\"] != expectedAuth {\n\t\t\tt.Errorf(\"registry %s: expected auth '%s', got '%v'\", registry, expectedAuth, credsMap[\"auth\"])\n\t\t}\n\t\t// email field should be preserved\n\t\tif credsMap[\"email\"] != \"user@example.com\" {\n\t\t\tt.Errorf(\"registry %s: email field should be preserved, got '%v'\", registry, credsMap[\"email\"])\n\t\t}\n\t}\n}\n\nfunc TestCorruptDockerCfg_Empty(t *testing.T) {\n\tinput := map[string]interface{}{}\n\tinputBytes, err := json.Marshal(input)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to marshal input: %v\", err)\n\t}\n\n\t_, err = corruptDockerCfg(inputBytes)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for empty dockercfg, got nil\")\n\t}\n}\n\nfunc TestGenerateBackupSecretName(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\texperimentId string\n\t\tnamespace    string\n\t\tsecretName   string\n\t}{\n\t\t{\n\t\t\tname:         \"normal case\",\n\t\t\texperimentId: \"abc12345def67890\",\n\t\t\tnamespace:    \"default\",\n\t\t\tsecretName:   \"my-registry-secret\",\n\t\t},\n\t\t{\n\t\t\tname:         \"short experiment id\",\n\t\t\texperimentId: \"short\",\n\t\t\tnamespace:    \"kube-system\",\n\t\t\tsecretName:   \"docker-secret\",\n\t\t},\n\t\t{\n\t\t\tname:         \"long experiment id\",\n\t\t\texperimentId: \"very-long-experiment-id-that-exceeds-eight-characters\",\n\t\t\tnamespace:    \"production\",\n\t\t\tsecretName:   \"registry-credentials\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := generateBackupSecretName(tt.experimentId, tt.namespace, tt.secretName)\n\n\t\t\t// Should start with prefix\n\t\t\tif len(result) < len(\"chaosblade-ips-\") {\n\t\t\t\tt.Errorf(\"result too short: %s\", result)\n\t\t\t}\n\n\t\t\t// Should be deterministic\n\t\t\tresult2 := generateBackupSecretName(tt.experimentId, tt.namespace, tt.secretName)\n\t\t\tif result != result2 {\n\t\t\t\tt.Errorf(\"not deterministic: %s != %s\", result, result2)\n\t\t\t}\n\n\t\t\t// Should be a valid DNS subdomain (max 253 chars, lowercase, alphanumeric/dash)\n\t\t\tif len(result) > 253 {\n\t\t\t\tt.Errorf(\"name too long: %d characters\", len(result))\n\t\t\t}\n\n\t\t\t// Different inputs should produce different names\n\t\t\tdifferent := generateBackupSecretName(tt.experimentId, tt.namespace, \"other-secret\")\n\t\t\tif result == different {\n\t\t\t\tt.Errorf(\"different inputs produced same name: %s\", result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGenerateBackupSecretName_Deterministic(t *testing.T) {\n\t// Same inputs should always produce the same output\n\tname1 := generateBackupSecretName(\"exp123\", \"default\", \"my-secret\")\n\tname2 := generateBackupSecretName(\"exp123\", \"default\", \"my-secret\")\n\tif name1 != name2 {\n\t\tt.Errorf(\"expected deterministic result, got %s and %s\", name1, name2)\n\t}\n}\n\nfunc TestImagePullSecretsErrorActionSpec_Name(t *testing.T) {\n\tspec := &ImagePullSecretsErrorActionSpec{}\n\tif spec.Name() != \"imagepullsecretserror\" {\n\t\tt.Errorf(\"expected name 'imagepullsecretserror', got '%s'\", spec.Name())\n\t}\n}\n\nfunc TestImagePullSecretsErrorActionSpec_Aliases(t *testing.T) {\n\tspec := &ImagePullSecretsErrorActionSpec{}\n\taliases := spec.Aliases()\n\tif len(aliases) != 0 {\n\t\tt.Errorf(\"expected no aliases, got %v\", aliases)\n\t}\n}\n\nfunc TestImagePullSecretsErrorActionExecutor_Name(t *testing.T) {\n\texecutor := &ImagePullSecretsErrorActionExecutor{}\n\tif executor.Name() != \"imagepullsecretserror\" {\n\t\tt.Errorf(\"expected name 'imagepullsecretserror', got '%s'\", executor.Name())\n\t}\n}\n\nfunc TestFilterSecretRefs(t *testing.T) {\n\trefs := []v1.LocalObjectReference{\n\t\t{Name: \"secret-a\"},\n\t\t{Name: \"secret-b\"},\n\t\t{Name: \"secret-c\"},\n\t}\n\n\ttests := []struct {\n\t\tname     string\n\t\tfilter   string\n\t\texpected int\n\t}{\n\t\t{\n\t\t\tname:     \"match single\",\n\t\t\tfilter:   \"secret-a\",\n\t\t\texpected: 1,\n\t\t},\n\t\t{\n\t\t\tname:     \"no match\",\n\t\t\tfilter:   \"nonexistent\",\n\t\t\texpected: 0,\n\t\t},\n\t\t{\n\t\t\tname:     \"match last\",\n\t\t\tfilter:   \"secret-c\",\n\t\t\texpected: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := filterSecretRefs(refs, tt.filter)\n\t\t\tif len(result) != tt.expected {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", tt.expected, len(result))\n\t\t\t}\n\t\t\tif tt.expected > 0 && result[0].Name != tt.filter {\n\t\t\t\tt.Errorf(\"expected name %s, got %s\", tt.filter, result[0].Name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFilterSecretRefs_EmptyInput(t *testing.T) {\n\tresult := filterSecretRefs(nil, \"any\")\n\tif len(result) != 0 {\n\t\tt.Errorf(\"expected 0 results for nil input, got %d\", len(result))\n\t}\n\n\tresult = filterSecretRefs([]v1.LocalObjectReference{}, \"any\")\n\tif len(result) != 0 {\n\t\tt.Errorf(\"expected 0 results for empty input, got %d\", len(result))\n\t}\n}\n\nfunc TestFilterSecretRefs_DuplicateNames(t *testing.T) {\n\trefs := []v1.LocalObjectReference{\n\t\t{Name: \"my-secret\"},\n\t\t{Name: \"other-secret\"},\n\t\t{Name: \"my-secret\"},\n\t}\n\tresult := filterSecretRefs(refs, \"my-secret\")\n\tif len(result) != 2 {\n\t\tt.Errorf(\"expected 2 results for duplicate names, got %d\", len(result))\n\t}\n}\n\nfunc TestCopySecretData(t *testing.T) {\n\toriginal := map[string][]byte{\n\t\t\".dockerconfigjson\": []byte(`{\"auths\":{\"registry.io\":{\"auth\":\"dXNlcjpwYXNz\"}}}`),\n\t}\n\n\tcopied := copySecretData(original)\n\n\t// Verify content is the same\n\tif string(copied[\".dockerconfigjson\"]) != string(original[\".dockerconfigjson\"]) {\n\t\tt.Error(\"copied content should match original\")\n\t}\n\n\t// Verify modifying copy doesn't affect original\n\tcopied[\".dockerconfigjson\"][0] = 'X'\n\tif original[\".dockerconfigjson\"][0] == 'X' {\n\t\tt.Error(\"modifying copy should not affect original\")\n\t}\n}\n\nfunc TestCopySecretData_Nil(t *testing.T) {\n\tresult := copySecretData(nil)\n\tif result != nil {\n\t\tt.Errorf(\"expected nil for nil input, got %v\", result)\n\t}\n}\n"
  },
  {
    "path": "exec/pod/pod.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/cpu\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/disk\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/file\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/mem\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/network\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/network/tc\"\n\t\"github.com/chaosblade-io/chaosblade-exec-os/exec/script\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n)\n\ntype ResourceModelSpec struct {\n\tmodel.BaseResourceExpModelSpec\n}\n\nfunc NewResourceModelSpec(client *channel.Client) model.ResourceExpModelSpec {\n\tmodelSpec := &ResourceModelSpec{\n\t\tmodel.NewBaseResourceExpModelSpec(\"pod\", client),\n\t}\n\t// os experiment models\n\tosExpModels := model.NewOSSubResourceModelSpec().ExpModels()\n\tspec.AddExecutorToModelSpec(&model.CommonExecutor{Client: client}, osExpModels...)\n\t// pod-self experiment models\n\texpModels := append(osExpModels, NewSelfExpModelCommandSpec(client))\n\n\tspec.AddFlagsToModelSpec(getResourceFlags, expModels...)\n\tmodelSpec.RegisterExpModels(expModels...)\n\taddActionExamples(modelSpec)\n\treturn modelSpec\n}\n\nfunc addActionExamples(modelSpec *ResourceModelSpec) {\n\tfor _, expModelSpec := range modelSpec.ExpModelSpecs {\n\t\tfor _, action := range expModelSpec.Actions() {\n\t\t\tv := interface{}(action)\n\t\t\tswitch v.(type) {\n\t\t\tcase *disk.FillActionSpec:\n\t\t\t\taction.SetLongDesc(\"The disk fill scenario experiment in the pod\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`\n# Fill the /home directory with 40G of disk space in the pod\nblade create k8s pod-disk fill --path /home --size 40000 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Fill the /home directory with 80% of the disk space in the pod and retains the file handle that populates the disk\nblade create k8s pod-disk fill --path /home --percent 80 --retain-handle --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Perform a fixed-size experimental scenario in the pod\nblade c k8s pod-disk fill --path /home --reserve 1024 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *disk.BurnActionSpec:\n\t\t\t\taction.SetLongDesc(\"Disk read and write IO load experiment in the pod\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The data of rkB/s, wkB/s and % Util were mainly observed. Perform disk read IO high-load scenarios\nblade create k8s pod-disk burn --read --path /home --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Perform disk write IO high-load scenarios\nblade create k8s pod-disk burn --write --path /home --names nginx-app --kubeconfig ~/.kube/config --namespace default8\n\n# Read and write IO load scenarios are performed at the same time. Path is not specified. The default is /\nblade create k8s pod-disk burn --read --write --names nginx-app --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *mem.MemLoadActionCommand:\n\t\t\t\taction.SetLongDesc(\"The memory fill experiment scenario in the pod\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The execution memory footprint is 50%\nblade create k8s pod-mem load --mode ram --mem-percent 50 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# The execution memory footprint is 50%, cache model\nblade create k8s pod-mem load --mode cache --mem-percent 50 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# The execution memory footprint is 50%, usage contains buffer/cache\nblade create k8s pod-mem load --mode ram --mem-percent 50 --include-buffer-cache --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# The execution memory footprint is 50% for 200 seconds\nblade create k8s pod-mem load --mode ram --mem-percent 50 --timeout 200 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# 200M memory is reserved\nblade create k8s pod-mem load --mode ram --reserve 200 --rate 100 --names nginx-app --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *file.FileAppendActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file append experiment scenario in the pod\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file\nblade create k8s pod-file append --filepath=/home/logs/nginx.log --content=\"HELL WORLD\" --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file, interval 10 seconds\nblade create k8s pod-file append --filepath=/home/logs/nginx.log --content=\"HELL WORLD\" --interval 10 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Appends the content \"HELLO WORLD\" to the /home/logs/nginx.log file, enable base64 encoding\nblade create k8s pod-file append --filepath=/home/logs/nginx.log --content=SEVMTE8gV09STEQ= --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# mock interface timeout exception\nblade create k8s pod-file append --filepath=/home/logs/nginx.log --content=\"@{DATE:+%Y-%m-%d %H:%M:%S} ERROR invoke getUser timeout [@{RANDOM:100-200}]ms abc  mock exception\" --names nginx-app --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *file.FileAddActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file add experiment scenario in the pod\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Create a file named nginx.log in the /home directory\nblade create k8s pod-file add --filepath /home/nginx.log --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Create a file named nginx.log in the /home directory with the contents of HELLO WORLD\nblade create k8s pod-file add --filepath /home/nginx.log --content \"HELLO WORLD\" --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Create a file named nginx.log in the /temp directory and automatically create directories that don't exist\nblade create k8s pod-file add --filepath /temp/nginx.log --auto-create-dir --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Create a directory named /nginx in the /temp directory and automatically create directories that don't exist\nblade create k8s pod-file add --directory --filepath /temp/nginx --auto-create-dir --names nginx-app --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\n\t\t\tcase *file.FileChmodActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file permission modification scenario in the pod\")\n\t\t\t\taction.SetExample(`# Modify /home/logs/nginx.log file permissions to 777\nblade create k8s pod-file chmod --filepath /home/logs/nginx.log --mark=777 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n`)\n\t\t\tcase *file.FileDeleteActionSpec:\n\t\t\t\taction.SetLongDesc(\"The file delete scenario in the pod\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Delete the file /home/logs/nginx.log\nblade create k8s pod-file delete --filepath /home/logs/nginx.log --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Force delete the file /home/logs/nginx.log unrecoverable\nblade create k8s pod-file delete --filepath /home/logs/nginx.log --force --names nginx-app --kubeconfig ~/.kube/config --namespace default\n`,\n\t\t\t\t)\n\t\t\tcase *file.FileMoveActionSpec:\n\t\t\t\taction.SetExample(\"The file move scenario in the pod\")\n\t\t\t\taction.SetExample(`# Move the file /home/logs/nginx.log to /tmp\nblade create k8s pod-file move --filepath /home/logs/nginx.log --target /tmp --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Force Move the file /home/logs/nginx.log to /temp\nblade create k8s pod-file move --filepath /home/logs/nginx.log --target /tmp --force --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Move the file /home/logs/nginx.log to /temp/ and automatically create directories that don't exist\nblade create k8s pod-file move --filepath /home/logs/nginx.log --target /temp --auto-create-dir --names nginx-app --kubeconfig ~/.kube/config --namespace default\n`)\n\t\t\tcase *tc.DelayActionSpec:\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Access to native 8080 and 8081 ports is delayed by 3 seconds, and the delay time fluctuates by 1 second\nblade create k8s pod-network delay --time 3000 --offset 1000 --interface eth0 --local-port 8080,8081 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Local access to external 14.215.177.39 machine (ping www.baidu.com obtained IP) port 80 delay of 3 seconds\nblade create k8s pod-network delay --time 3000 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Do a 5 second delay for the entire network card eth0, excluding ports 22 and 8000 to 8080\nblade create k8s pod-network delay --time 5000 --interface eth0 --exclude-port 22,8000-8080 --names nginx-app --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *network.DropActionSpec:\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Experimental scenario of network shielding\nblade create k8s pod-network drop --names nginx-app --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *network.DnsActionSpec:\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# The domain name www.baidu.com is not accessible\nblade create k8s pod-network dns --domain www.baidu.com --ip 10.0.0.0 --names nginx-app --kubeconfig ~/.kube/config --namespace default`,\n\t\t\t\t)\n\t\t\tcase *tc.LossActionSpec:\n\t\t\t\taction.SetExample(`# Access to native 8080 and 8081 ports lost 70% of packets\nblade create k8s pod-network loss --percent 70 --interface eth0 --local-port 8080,8081 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# The machine accesses external 14.215.177.39 machine (ping www.baidu.com) 80 port packet loss rate 100%\nblade create k8s pod-network loss --percent 100 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Do 60% packet loss for the entire network card Eth0, excluding ports 22 and 8000 to 8080\nblade create k8s pod-network loss --percent 60 --interface eth0 --exclude-port 22,8000-8080 --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# Realize the whole network card is not accessible, not accessible time 20 seconds. After executing the following command, the current network is disconnected and restored in 20 seconds. Remember!! Don't forget -timeout parameter\nblade create k8s pod-network loss --percent 100 --interface eth0 --timeout 20 --names nginx-app --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *tc.DuplicateActionSpec:\n\t\t\t\taction.SetExample(`# Specify the network card eth0 and repeat the packet by 10%\nblade create k8s pod-network duplicate --percent=10 --interface=eth0 --names nginx-app --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *tc.CorruptActionSpec:\n\t\t\t\taction.SetExample(`# Access to the specified IP request packet is corrupted, 80% of the time\nblade create k8s pod-network corrupt --percent 80 --destination-ip 180.101.49.12 --interface eth0 --names nginx-app --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *tc.ReorderActionSpec:\n\t\t\t\taction.SetExample(`# Access the specified IP request packet disorder\nblade create k8s pod-network reorder --correlation 80 --percent 50 --gap 2 --time 500 --interface eth0 --destination-ip 180.101.49.12 --names nginx-app --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *network.OccupyActionSpec:\n\t\t\t\taction.SetExample(`#Specify port 8080 occupancy\nblade create k8s pod-network occupy --port 8080 --force --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n# The machine accesses external 14.215.177.39 machine (ping www.baidu.com) 80 port packet loss rate 100%\nblade create k8s pod-network loss --percent 100 --interface eth0 --remote-port 80 --destination-ip 14.215.177.39 --names nginx-app --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *cpu.FullLoadActionCommand:\n\t\t\t\taction.SetExample(`\n# Create a CPU full load experiment\nblade create k8s pod-cpu load --names nginx-app --kubeconfig ~/.kube/config --namespace default\n\n#Specifies two random kernel's full load\nblade create k8s pod-cpu load --names nginx-app --kubeconfig ~/.kube/config --namespace default --cpu-percent 60 --cpu-count 2\n\n# Specifies that the kernel is full load with index 0, 3, and that the kernel's index starts at 0\nblade create k8s pod-cpu load --names nginx-app --kubeconfig ~/.kube/config --namespace default --cpu-list 0,3\n\n# Specify the kernel full load of indexes 1-3\nblade create k8s pod-cpu load --names nginx-app --kubeconfig ~/.kube/config --namespace default --cpu-list 1-3\n\n# Specified percentage load\nblade create k8s pod-cpu load --names nginx-app --kubeconfig ~/.kube/config --namespace default --cpu-percent 60`)\n\t\t\tcase *script.ScriptDelayActionCommand:\n\t\t\t\taction.SetExample(`\n# Add commands to the script \"start0() { sleep 10.000000 ...}\"\nblade create k8s pod-script delay --time 10000 --file test.sh --function-name start0 --names nginx-app --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *script.ScriptExitActionCommand:\n\t\t\t\taction.SetExample(`\n# Add commands to the script \"start0() { echo this-is-error-message; exit 1; ... }\"\nblade create k8s pod-script exit --exit-code 1 --exit-message this-is-error-message --file test.sh --function-name start0 --names nginx-app --kubeconfig ~/.kube/config --namespace default`)\n\t\t\tcase *PodContainerCreatingActionSpec:\n\t\t\t\taction.SetLongDesc(\"Make pod stuck in ContainerCreating state by creating a PV with an unreachable NFS server, a PVC bound to it, and a Pod that mounts the PVC. Since the NFS server is unreachable, the volume mount fails and the Pod remains stuck in ContainerCreating state.\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Create a pod stuck in ContainerCreating state in the default namespace\nblade create k8s pod-pod containercreating --namespace default --kubeconfig ~/.kube/config\n\n# Create a pod stuck in ContainerCreating state with custom volume mount path\nblade create k8s pod-pod containercreating --namespace default --volume-mount-path /data --kubeconfig ~/.kube/config`,\n\t\t\t\t)\n\t\t\tcase *PodSchedulingFailureActionSpec:\n\t\t\t\taction.SetLongDesc(\"Make pod scheduling fail by injecting unreachable affinity rules to the target workload (Deployment/DaemonSet/StatefulSet). The scheduler will not find any node matching the rules, causing the Pod to remain in Pending state.\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Inject scheduling failure to a deployment by node affinity\nblade create k8s pod-pod schedulingfailure --namespace default --workload-type deployment --workload-name nginx-deployment --kubeconfig ~/.kube/config\n\n# Inject scheduling failure using node-selector\nblade create k8s pod-pod schedulingfailure --namespace default --workload-type deployment --workload-name nginx-deployment --affinity-type node-selector --kubeconfig ~/.kube/config`,\n\t\t\t\t)\n\t\t\tcase *ImagePullSecretsErrorActionSpec:\n\t\t\t\taction.SetLongDesc(\"Simulate image pull authentication failure by corrupting the credentials in the Secret referenced by the Pod's imagePullSecrets. The original Secret is backed up and restored when the experiment is destroyed.\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Simulate image pull authentication failure for a specific pod\nblade create k8s pod-pod imagepullsecretserror --names my-app-pod --namespace default --kubeconfig ~/.kube/config\n\n# Simulate image pull authentication failure for pods selected by labels\nblade create k8s pod-pod imagepullsecretserror --labels app=nginx --namespace default --kubeconfig ~/.kube/config\n\n# Corrupt only a specific imagePullSecret\nblade create k8s pod-pod imagepullsecretserror --names my-app-pod --namespace default --secret-name my-registry-secret --kubeconfig ~/.kube/config`,\n\t\t\t\t)\n\t\t\tcase *PodTaintNodeActionSpec:\n\t\t\t\taction.SetLongDesc(\"Make pod scheduling fail by adding unreachable taint to nodes. The scheduler will not schedule Pods without matching tolerations to the tainted nodes, causing the Pods to remain in Pending state.\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Add unreachable taint to nodes to prevent pod scheduling\nblade create k8s pod-pod taintnode --nodes node1,node2 --kubeconfig ~/.kube/config\n\n# Add taint with NoExecute effect (will evict running pods)\nblade create k8s pod-pod taintnode --nodes node1 --taint-effect NoExecute --kubeconfig ~/.kube/config`,\n\t\t\t\t)\n\t\t\tcase *ConfigMapDeleteActionSpec:\n\t\t\t\taction.SetLongDesc(\"Delete a ConfigMap that a Pod depends on, then restart the Pod to simulate startup failure caused by missing ConfigMap. The original ConfigMap is backed up and restored when the experiment is destroyed.\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Delete the auto-selected required ConfigMap for pods matching labels\nblade create k8s pod-pod configmapdelete --labels \"app=test\" --namespace default --kubeconfig ~/.kube/config\n\n# Delete a specific ConfigMap\nblade create k8s pod-pod configmapdelete --labels \"app=test\" --namespace default --configmap-name my-config --kubeconfig ~/.kube/config`,\n\t\t\t\t)\n\t\t\tcase *BadResourceSizeActionSpec:\n\t\t\t\taction.SetLongDesc(\"Modify the CPU/Memory resource limits of a workload (Deployment/DaemonSet/StatefulSet) to simulate incorrect resource sizing. The original resource configuration is backed up and restored when the experiment is destroyed.\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Set CPU resource limit for a deployment\nblade create k8s pod-pod badresourcesize --namespace default --workload-type deployment --workload-name nginx-app --cpu 1m --kubeconfig ~/.kube/config\n\n# Set memory resource limit for a deployment\nblade create k8s pod-pod badresourcesize --namespace default --workload-type deployment --workload-name nginx-app --mem 128m --kubeconfig ~/.kube/config\n\n# Set both CPU and memory resource limits for a deployment\nblade create k8s pod-pod badresourcesize --namespace default --workload-type deployment --workload-name nginx-app --cpu 1m --mem 128m --kubeconfig ~/.kube/config\n\n# Set CPU resource limit for a daemonset\nblade create k8s pod-pod badresourcesize --namespace default --workload-type daemonset --workload-name nginx-ds --cpu 1m --kubeconfig ~/.kube/config\n\n# Set memory resource limit for a statefulset\nblade create k8s pod-pod badresourcesize --namespace default --workload-type statefulset --workload-name nginx-sts --mem 128m --kubeconfig ~/.kube/config`,\n\t\t\t\t)\n\t\t\tcase *FailedMountActionSpec:\n\t\t\t\taction.SetLongDesc(\"Mount a non-existent ConfigMap/Secret/PVC volume to the target workload (Deployment/DaemonSet/StatefulSet) to simulate volume mount failure. The injected volume configuration is tracked and removed when the experiment is destroyed.\")\n\t\t\t\taction.SetExample(\n\t\t\t\t\t`# Mount a non-existent configmap volume to a deployment\nblade create k8s pod-pod failedmount --namespace default --workload-type deployment --workload-name nginx-app --volume-type configmap --kubeconfig ~/.kube/config\n\n# Mount a non-existent secret volume to init containers of a deployment\nblade create k8s pod-pod failedmount --namespace default --workload-type deployment --workload-name nginx-app --volume-type secret --with-initcontainer true --kubeconfig ~/.kube/config\n\n# Mount a non-existent pvc volume to a statefulset\nblade create k8s pod-pod failedmount --namespace default --workload-type statefulset --workload-name redis-app --volume-type pvc --kubeconfig ~/.kube/config`,\n\t\t\t\t)\n\t\t\tdefault:\n\t\t\t\taction.SetExample(strings.Replace(\n\t\t\t\t\taction.Example(),\n\t\t\t\t\tfmt.Sprintf(\"blade create %s %s\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\tfmt.Sprintf(\"blade create k8s pod-%s %s --names nginx-app --kubeconfig ~/.kube/config --namespace default\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\t-1,\n\t\t\t\t))\n\t\t\t\taction.SetExample(strings.Replace(\n\t\t\t\t\taction.Example(),\n\t\t\t\t\tfmt.Sprintf(\"blade c %s %s\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\tfmt.Sprintf(\"blade c k8s pod-%s %s --names nginx-app --kubeconfig ~/.kube/config --namespace default\", expModelSpec.Name(), action.Name()),\n\t\t\t\t\t-1,\n\t\t\t\t))\n\t\t\t}\n\n\t\t}\n\t}\n}\n\nfunc getResourceFlags() []spec.ExpFlagSpec {\n\tcoverageFlags := model.GetResourceCoverageFlags()\n\tcommonFlags := model.GetResourceCommonFlags()\n\tchaosbladeFlags := model.GetChaosBladeFlags()\n\treturn append(append(coverageFlags, commonFlags...), chaosbladeFlags...)\n}\n\ntype SelfExpModelCommandSpec struct {\n\tspec.BaseExpModelCommandSpec\n}\n\nfunc NewSelfExpModelCommandSpec(client *channel.Client) spec.ExpModelCommandSpec {\n\treturn &SelfExpModelCommandSpec{\n\t\tspec.BaseExpModelCommandSpec{\n\t\t\tExpFlags: []spec.ExpFlagSpec{},\n\t\t\tExpActions: []spec.ExpActionCommandSpec{\n\t\t\t\tNewDeletePodActionSpec(client),\n\t\t\t\tNewPodIOActionSpec(client),\n\t\t\t\tNewFailPodActionSpec(client),\n\t\t\t\tNewPodTerminatingActionSpec(client),\n\t\t\t\tNewPodContainerCreatingActionSpec(client),\n\t\t\t\tNewPodContainerCreatingDiskActionSpec(client),\n\t\t\t\tNewPodSchedulingFailureActionSpec(client),\n\t\t\t\tNewImageConfigActionSpec(client),\n\t\t\t\tNewImagePullSecretsErrorActionSpec(client),\n\t\t\t\tNewConfigMapDeleteActionSpec(client),\n\t\t\t\tNewPodTaintNodeActionSpec(client),\n\t\t\t\tNewBadResourceSizeActionSpec(client),\n\t\t\t\tNewFailedMountActionSpec(client),\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (*SelfExpModelCommandSpec) Name() string {\n\treturn \"pod\"\n}\n\nfunc (*SelfExpModelCommandSpec) ShortDesc() string {\n\treturn \"Pod experiments\"\n}\n\nfunc (*SelfExpModelCommandSpec) LongDesc() string {\n\treturn \"Pod experiments\"\n}\n\nfunc (*SelfExpModelCommandSpec) Example() string {\n\treturn \"blade c k8s pod-pod delete --names redis-slave-674d68586-n5s4q --namespace default --kubeconfig ~/.kube/config\"\n}\n"
  },
  {
    "path": "exec/pod/schedulingfailure.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\t// ChaosBladeDeploymentAnnotation indicates the deployment resource is modified\n\tChaosBladeDeploymentAnnotation = \"chaosblade.io/deployment\"\n\t// ChaosBladeDaemonSetAnnotation indicates the daemonset resource is modified\n\tChaosBladeDaemonSetAnnotation = \"chaosblade.io/daemonset\"\n\t// ChaosBladeStatefulSetAnnotation indicates the statefulset resource is modified\n\tChaosBladeStatefulSetAnnotation = \"chaosblade.io/statefulset\"\n\t// ChaosBladeModifyAction indicates modify action\n\tChaosBladeModifyAction = \"modify\"\n\t// ChaosBladeOriginalNodeAffinityAnnotation stores the original node affinity configuration\n\tChaosBladeOriginalNodeAffinityAnnotation = \"chaosblade.io/original-node-affinity\"\n\t// ChaosBladeOriginalPodAffinityAnnotation stores the original pod affinity configuration\n\tChaosBladeOriginalPodAffinityAnnotation = \"chaosblade.io/original-pod-affinity\"\n\t// ChaosBladeOriginalPodAntiAffinityAnnotation stores the original pod anti-affinity configuration\n\tChaosBladeOriginalPodAntiAffinityAnnotation = \"chaosblade.io/original-pod-anti-affinity\"\n\t// ChaosBladeOriginalNodeSelectorAnnotation stores the original node selector configuration\n\tChaosBladeOriginalNodeSelectorAnnotation = \"chaosblade.io/original-nodeselector\"\n\t// ChaosBladeAffinityTypeAnnotation records which affinity type was injected\n\tChaosBladeAffinityTypeAnnotation = \"chaosblade.io/affinity-type\"\n\t// ChaosBladeSchedulingFailureAction indicates scheduling failure action\n\tChaosBladeSchedulingFailureAction = \"schedulingfailure\"\n\t// UnreachableNodeLabel is a label that no node will have\n\tUnreachableNodeLabelKey   = \"chaosblade.io/unreachable\"\n\tUnreachableNodeLabelValue = \"true\"\n)\n\ntype PodSchedulingFailureActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n\tclient *channel.Client\n}\n\nfunc NewPodSchedulingFailureActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &PodSchedulingFailureActionSpec{\n\t\tBaseExpActionCommandSpec: spec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"workload-type\",\n\t\t\t\t\tDesc:     \"Workload type: deployment, daemonset, statefulset. Default: deployment\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t\tDefault:  \"deployment\",\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"workload-name\",\n\t\t\t\t\tDesc:     \"Workload name to inject scheduling failure\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"affinity-type\",\n\t\t\t\t\tDesc:     \"Affinity type to inject: node-affinity, node-selector, pod-affinity, pod-anti-affinity. Default: node-affinity\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t\tDefault:  \"node-affinity\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &PodSchedulingFailureActionExecutor{client: client},\n\t\t\tActionExample: `# Inject scheduling failure to a deployment by node affinity\nblade create k8s pod-pod schedulingfailure --namespace default --workload-type deployment --workload-name nginx-deployment --kubeconfig ~/.kube/config\n\n# Inject scheduling failure using node-selector\nblade create k8s pod-pod schedulingfailure --namespace default --workload-type deployment --workload-name nginx-deployment --affinity-type node-selector --kubeconfig ~/.kube/config\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t\tclient: client,\n\t}\n}\n\nfunc (*PodSchedulingFailureActionSpec) Name() string {\n\treturn \"schedulingfailure\"\n}\n\nfunc (*PodSchedulingFailureActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*PodSchedulingFailureActionSpec) ShortDesc() string {\n\treturn \"Make pod scheduling fail by injecting unreachable affinity rules\"\n}\n\nfunc (*PodSchedulingFailureActionSpec) LongDesc() string {\n\treturn \"Simulate the scenario where a Pod cannot be scheduled due to affinity configuration issues. \" +\n\t\t\"This fault is injected by modifying the target workload's (Deployment/DaemonSet/StatefulSet) Pod template \" +\n\t\t\"to add an unreachable node affinity or node selector. The scheduler will not find any node matching the rules, \" +\n\t\t\"causing the Pod to remain in Pending state. When the experiment is destroyed, the original affinity \" +\n\t\t\"configuration will be restored.\"\n}\n\ntype PodSchedulingFailureActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*PodSchedulingFailureActionExecutor) Name() string {\n\treturn \"schedulingfailure\"\n}\n\nfunc (*PodSchedulingFailureActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *PodSchedulingFailureActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *PodSchedulingFailureActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\t// Parse flags\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\taffinityType := expModel.ActionFlags[\"affinity-type\"]\n\tif affinityType == \"\" {\n\t\taffinityType = \"node-affinity\"\n\t}\n\n\t// Validate required flags\n\tif namespace == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"namespace is required\")\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"workload-name is required\")\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\n\tstatus := v1alpha1.ResourceStatus{\n\t\tKind:       v1alpha1.PodKind,\n\t\tIdentifier: fmt.Sprintf(\"%s//%s//%s\", namespace, workloadType, workloadName),\n\t}\n\n\t// Get and modify the workload\n\tswitch workloadType {\n\tcase \"deployment\":\n\t\tdeployment := &appsv1.Deployment{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, deployment)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"deployment %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"deployment not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get deployment %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get deployment failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\t// Inject scheduling failure\n\t\tif err := d.injectDeploymentSchedulingFailure(ctx, deployment, affinityType, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject scheduling failure to deployment %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject scheduling failure failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected scheduling failure to deployment %s/%s with affinity type %s\", namespace, workloadName, affinityType)\n\n\tcase \"daemonset\":\n\t\tdaemonset := &appsv1.DaemonSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, daemonset)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"daemonset %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"daemonset not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get daemonset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get daemonset failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\tif err := d.injectDaemonSetSchedulingFailure(ctx, daemonset, affinityType, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject scheduling failure to daemonset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject scheduling failure failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected scheduling failure to daemonset %s/%s with affinity type %s\", namespace, workloadName, affinityType)\n\n\tcase \"statefulset\":\n\t\tstatefulset := &appsv1.StatefulSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, statefulset)\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\tlogrusField.Warningf(\"statefulset %s/%s not found\", namespace, workloadName)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"statefulset not found: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t} else {\n\t\t\t\tlogrusField.Warningf(\"get statefulset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get statefulset failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t}\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\n\t\tif err := d.injectStatefulSetSchedulingFailure(ctx, statefulset, affinityType, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"inject scheduling failure to statefulset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject scheduling failure failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"injected scheduling failure to statefulset %s/%s with affinity type %s\", namespace, workloadName, affinityType)\n\n\tdefault:\n\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"unsupported workload type: %s\", workloadType), spec.ParameterIllegal.Code)\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t}\n\n\tstatus = status.CreateSuccessResourceStatus()\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{status}))\n}\n\n// handleGetError handles errors from client.Get operations in destroy.\n// Returns true if the error was handled (NotFound or other error), false otherwise.\n// When returning true, the response pointer is set with the appropriate status.\nfunc handleGetError(err error, namespace, workloadType, workloadName string, status *v1alpha1.ResourceStatus, logrusField *logrus.Entry) (*spec.Response, bool) {\n\tif err == nil {\n\t\treturn nil, false\n\t}\n\tif apierrors.IsNotFound(err) {\n\t\tlogrusField.Infof(\"%s %s/%s already deleted\", workloadType, namespace, workloadName)\n\t\t*status = status.CreateSuccessResourceStatus()\n\t\tstatus.State = v1alpha1.DestroyedState\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{*status})), true\n\t}\n\tlogrusField.Warningf(\"get %s %s/%s failed: %v\", workloadType, namespace, workloadName, err)\n\t*status = status.CreateFailResourceStatus(fmt.Sprintf(\"get %s failed: %v\", workloadType, err), spec.K8sExecFailed.Code)\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{*status})), true\n}\n\nfunc (d *PodSchedulingFailureActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\t// Parse flags\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tstatus := v1alpha1.ResourceStatus{\n\t\tKind:       v1alpha1.PodKind,\n\t\tIdentifier: fmt.Sprintf(\"%s//%s//%s\", namespace, workloadType, workloadName),\n\t}\n\n\t// Restore the workload\n\tswitch workloadType {\n\tcase \"deployment\":\n\t\tdeployment := &appsv1.Deployment{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, deployment)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\n\t\tif err := d.restoreDeployment(ctx, deployment, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore deployment %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore deployment failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored deployment %s/%s\", namespace, workloadName)\n\n\tcase \"daemonset\":\n\t\tdaemonset := &appsv1.DaemonSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, daemonset)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\n\t\tif err := d.restoreDaemonSet(ctx, daemonset, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore daemonset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore daemonset failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored daemonset %s/%s\", namespace, workloadName)\n\n\tcase \"statefulset\":\n\t\tstatefulset := &appsv1.StatefulSet{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workloadName}, statefulset)\n\t\tif resp, handled := handleGetError(err, namespace, workloadType, workloadName, &status, logrusField); handled {\n\t\t\treturn resp\n\t\t}\n\n\t\tif err := d.restoreStatefulSet(ctx, statefulset, experimentId); err != nil {\n\t\t\tlogrusField.Warningf(\"restore statefulset %s/%s failed: %v\", namespace, workloadName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore statefulset failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t\t}\n\t\tlogrusField.Infof(\"restored statefulset %s/%s\", namespace, workloadName)\n\n\tdefault:\n\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"unsupported workload type: %s\", workloadType), spec.ParameterIllegal.Code)\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(status.Error, []v1alpha1.ResourceStatus{status}))\n\t}\n\n\tstatus = status.CreateSuccessResourceStatus()\n\tstatus.State = v1alpha1.DestroyedState\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{status}))\n}\n\n// ensureNoConflictingExperiment checks whether the workload is already modified by\n// another chaosblade experiment. This prevents overwriting backup data from a\n// running experiment, which would make the workload unrecoverable on destroy.\nfunc ensureNoConflictingExperiment(annotations map[string]string, experimentId string) error {\n\tif existingId, ok := annotations[ChaosBladeExperimentAnnotation]; ok && existingId != \"\" && existingId != experimentId {\n\t\treturn fmt.Errorf(\"workload is already modified by another chaosblade experiment: %s\", existingId)\n\t}\n\treturn nil\n}\n\n// injectDeploymentSchedulingFailure injects scheduling failure to a Deployment\nfunc (d *PodSchedulingFailureActionExecutor) injectDeploymentSchedulingFailure(ctx context.Context, deployment *appsv1.Deployment, affinityType, experimentId string) error {\n\tif deployment.Annotations == nil {\n\t\tdeployment.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(deployment.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\t// Idempotent: if already modified by the same experiment, skip re-injection\n\t// to avoid overwriting the saved original affinity backup.\n\tif deployment.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tdeployment.Annotations[ChaosBladeDeploymentAnnotation] = ChaosBladeModifyAction\n\tdeployment.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\t// Backup and inject affinity\n\tif err := d.backupAndInjectAffinity(&deployment.Spec.Template.Spec, deployment.Annotations, affinityType, deployment.Spec.Template.Labels); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, deployment)\n}\n\n// injectDaemonSetSchedulingFailure injects scheduling failure to a DaemonSet\nfunc (d *PodSchedulingFailureActionExecutor) injectDaemonSetSchedulingFailure(ctx context.Context, daemonset *appsv1.DaemonSet, affinityType, experimentId string) error {\n\tif daemonset.Annotations == nil {\n\t\tdaemonset.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(daemonset.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\tif daemonset.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tdaemonset.Annotations[ChaosBladeDaemonSetAnnotation] = ChaosBladeModifyAction\n\tdaemonset.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\tif err := d.backupAndInjectAffinity(&daemonset.Spec.Template.Spec, daemonset.Annotations, affinityType, daemonset.Spec.Template.Labels); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, daemonset)\n}\n\n// injectStatefulSetSchedulingFailure injects scheduling failure to a StatefulSet\nfunc (d *PodSchedulingFailureActionExecutor) injectStatefulSetSchedulingFailure(ctx context.Context, statefulset *appsv1.StatefulSet, affinityType, experimentId string) error {\n\tif statefulset.Annotations == nil {\n\t\tstatefulset.Annotations = make(map[string]string)\n\t}\n\tif err := ensureNoConflictingExperiment(statefulset.Annotations, experimentId); err != nil {\n\t\treturn err\n\t}\n\tif statefulset.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\tstatefulset.Annotations[ChaosBladeStatefulSetAnnotation] = ChaosBladeModifyAction\n\tstatefulset.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\tif err := d.backupAndInjectAffinity(&statefulset.Spec.Template.Spec, statefulset.Annotations, affinityType, statefulset.Spec.Template.Labels); err != nil {\n\t\treturn err\n\t}\n\n\treturn d.client.Update(ctx, statefulset)\n}\n\n// backupAndInjectAffinity backs up original affinity and injects unreachable affinity rules\n// podLabels is used by pod-anti-affinity to target the workload's own labels\nfunc (d *PodSchedulingFailureActionExecutor) backupAndInjectAffinity(podSpec *v1.PodSpec, annotations map[string]string, affinityType string, podLabels map[string]string) error {\n\tannotations[ChaosBladeAffinityTypeAnnotation] = affinityType\n\n\tswitch affinityType {\n\tcase \"node-affinity\":\n\t\t// Backup original affinity\n\t\tif podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity != nil {\n\t\t\toriginalBytes, err := json.Marshal(podSpec.Affinity.NodeAffinity)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshal original node affinity failed: %v\", err)\n\t\t\t}\n\t\t\tannotations[ChaosBladeOriginalNodeAffinityAnnotation] = string(originalBytes)\n\t\t}\n\n\t\t// Inject unreachable node affinity\n\t\tunreachableAffinity := &v1.NodeAffinity{\n\t\t\tRequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{\n\t\t\t\tNodeSelectorTerms: []v1.NodeSelectorTerm{\n\t\t\t\t\t{\n\t\t\t\t\t\tMatchExpressions: []v1.NodeSelectorRequirement{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKey:      UnreachableNodeLabelKey,\n\t\t\t\t\t\t\t\tOperator: v1.NodeSelectorOpIn,\n\t\t\t\t\t\t\t\tValues:   []string{UnreachableNodeLabelValue},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tif podSpec.Affinity == nil {\n\t\t\tpodSpec.Affinity = &v1.Affinity{}\n\t\t}\n\t\tpodSpec.Affinity.NodeAffinity = unreachableAffinity\n\n\tcase \"node-selector\":\n\t\t// Backup original node selector\n\t\tif len(podSpec.NodeSelector) > 0 {\n\t\t\toriginalBytes, err := json.Marshal(podSpec.NodeSelector)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshal original node selector failed: %v\", err)\n\t\t\t}\n\t\t\tannotations[ChaosBladeOriginalNodeSelectorAnnotation] = string(originalBytes)\n\t\t}\n\n\t\t// Inject unreachable node selector\n\t\tpodSpec.NodeSelector = map[string]string{\n\t\t\tUnreachableNodeLabelKey: UnreachableNodeLabelValue,\n\t\t}\n\n\tcase \"pod-affinity\":\n\t\t// Backup original pod affinity\n\t\tif podSpec.Affinity != nil && podSpec.Affinity.PodAffinity != nil {\n\t\t\toriginalBytes, err := json.Marshal(podSpec.Affinity.PodAffinity)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshal original pod affinity failed: %v\", err)\n\t\t\t}\n\t\t\tannotations[ChaosBladeOriginalPodAffinityAnnotation] = string(originalBytes)\n\t\t}\n\n\t\t// Inject unreachable pod affinity\n\t\tunreachablePodAffinity := &v1.PodAffinity{\n\t\t\tRequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{\n\t\t\t\t{\n\t\t\t\t\tLabelSelector: &metav1.LabelSelector{\n\t\t\t\t\t\tMatchExpressions: []metav1.LabelSelectorRequirement{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tKey:      UnreachableNodeLabelKey,\n\t\t\t\t\t\t\t\tOperator: metav1.LabelSelectorOpIn,\n\t\t\t\t\t\t\t\tValues:   []string{UnreachableNodeLabelValue},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tTopologyKey: \"kubernetes.io/hostname\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tif podSpec.Affinity == nil {\n\t\t\tpodSpec.Affinity = &v1.Affinity{}\n\t\t}\n\t\tpodSpec.Affinity.PodAffinity = unreachablePodAffinity\n\n\tcase \"pod-anti-affinity\":\n\t\t// Backup original pod anti-affinity\n\t\tif podSpec.Affinity != nil && podSpec.Affinity.PodAntiAffinity != nil {\n\t\t\toriginalBytes, err := json.Marshal(podSpec.Affinity.PodAntiAffinity)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshal original pod anti-affinity failed: %v\", err)\n\t\t\t}\n\t\t\tannotations[ChaosBladeOriginalPodAntiAffinityAnnotation] = string(originalBytes)\n\t\t}\n\n\t\t// Inject pod anti-affinity against the workload's own labels\n\t\t// This creates a \"one pod per node\" constraint: new pods can't be scheduled\n\t\t// on nodes that already have a pod with the same labels.\n\t\t// With enough replicas (> number of available nodes), pods will be Pending.\n\t\tvar matchExpressions []metav1.LabelSelectorRequirement\n\t\tfor key, value := range podLabels {\n\t\t\tmatchExpressions = append(matchExpressions, metav1.LabelSelectorRequirement{\n\t\t\t\tKey:      key,\n\t\t\t\tOperator: metav1.LabelSelectorOpIn,\n\t\t\t\tValues:   []string{value},\n\t\t\t})\n\t\t}\n\t\tif len(matchExpressions) == 0 {\n\t\t\treturn fmt.Errorf(\"pod template has no labels, cannot inject pod-anti-affinity\")\n\t\t}\n\n\t\tunreachablePodAntiAffinity := &v1.PodAntiAffinity{\n\t\t\tRequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{\n\t\t\t\t{\n\t\t\t\t\tLabelSelector: &metav1.LabelSelector{\n\t\t\t\t\t\tMatchExpressions: matchExpressions,\n\t\t\t\t\t},\n\t\t\t\t\tTopologyKey: \"kubernetes.io/hostname\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tif podSpec.Affinity == nil {\n\t\t\tpodSpec.Affinity = &v1.Affinity{}\n\t\t}\n\t\tpodSpec.Affinity.PodAntiAffinity = unreachablePodAntiAffinity\n\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported affinity type: %s\", affinityType)\n\t}\n\n\treturn nil\n}\n\n// PreCreate implements model.ActionPreProcessor interface.\n// It validates the required flags and prepares the context for schedulingfailure action.\nfunc (a *PodSchedulingFailureActionSpec) PreCreate(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) (context.Context, *spec.Response) {\n\t// Validate required flags\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-sf-%s-%s\", workloadType, workloadName),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\n// PreDestroy implements model.ActionPreProcessor interface.\n// It prepares the context for schedulingfailure destroy flow.\n// Unlike the default destroy path, it always attempts to restore the workload\n// regardless of the old experiment status.\nfunc (a *PodSchedulingFailureActionSpec) PreDestroy(ctx context.Context, expModel *spec.ExpModel, client *channel.Client, oldExpStatus v1alpha1.ExperimentStatus) (context.Context, *spec.Response) {\n\tnamespace := expModel.ActionFlags[model.ResourceNamespaceFlag.Name]\n\tworkloadType := expModel.ActionFlags[\"workload-type\"]\n\tif workloadType == \"\" {\n\t\tworkloadType = \"deployment\"\n\t}\n\tworkloadName := expModel.ActionFlags[\"workload-name\"]\n\n\tif namespace == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, model.ResourceNamespaceFlag.Name)\n\t}\n\tif strings.Contains(namespace, \",\") {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, model.ResourceNamespaceFlag.Name)\n\t}\n\tif workloadName == \"\" {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterLess, \"workload-name\")\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{\n\t\tmodel.ContainerObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-sf-%s-%s\", workloadType, workloadName),\n\t\t},\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\n// restoreDeployment restores a Deployment's original affinity configuration\nfunc (d *PodSchedulingFailureActionExecutor) restoreDeployment(ctx context.Context, deployment *appsv1.Deployment, experimentId string) error {\n\t// Verify this deployment was modified by the same experiment\n\tif deployment.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"deployment was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := d.restoreAffinity(&deployment.Spec.Template.Spec, deployment.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\t// Clean up annotations\n\tdelete(deployment.Annotations, ChaosBladeDeploymentAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeAffinityTypeAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeOriginalNodeAffinityAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeOriginalPodAffinityAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeOriginalPodAntiAffinityAnnotation)\n\tdelete(deployment.Annotations, ChaosBladeOriginalNodeSelectorAnnotation)\n\n\treturn d.client.Update(ctx, deployment)\n}\n\n// restoreDaemonSet restores a DaemonSet's original affinity configuration\nfunc (d *PodSchedulingFailureActionExecutor) restoreDaemonSet(ctx context.Context, daemonset *appsv1.DaemonSet, experimentId string) error {\n\tif daemonset.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"daemonset was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := d.restoreAffinity(&daemonset.Spec.Template.Spec, daemonset.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\tdelete(daemonset.Annotations, ChaosBladeDaemonSetAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeAffinityTypeAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeOriginalNodeAffinityAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeOriginalPodAffinityAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeOriginalPodAntiAffinityAnnotation)\n\tdelete(daemonset.Annotations, ChaosBladeOriginalNodeSelectorAnnotation)\n\n\treturn d.client.Update(ctx, daemonset)\n}\n\n// restoreStatefulSet restores a StatefulSet's original affinity configuration\nfunc (d *PodSchedulingFailureActionExecutor) restoreStatefulSet(ctx context.Context, statefulset *appsv1.StatefulSet, experimentId string) error {\n\tif statefulset.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn fmt.Errorf(\"statefulset was not modified by experiment %s\", experimentId)\n\t}\n\n\tif err := d.restoreAffinity(&statefulset.Spec.Template.Spec, statefulset.Annotations); err != nil {\n\t\treturn err\n\t}\n\n\tdelete(statefulset.Annotations, ChaosBladeStatefulSetAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeExperimentAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeAffinityTypeAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeOriginalNodeAffinityAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeOriginalPodAffinityAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeOriginalPodAntiAffinityAnnotation)\n\tdelete(statefulset.Annotations, ChaosBladeOriginalNodeSelectorAnnotation)\n\n\treturn d.client.Update(ctx, statefulset)\n}\n\n// restoreAffinity restores only the affinity field that was modified during injection.\n// It uses the ChaosBladeAffinityTypeAnnotation to determine which field to restore,\n// avoiding unintentional clearing of pre-existing affinity/selector settings.\nfunc (d *PodSchedulingFailureActionExecutor) restoreAffinity(podSpec *v1.PodSpec, annotations map[string]string) error {\n\taffinityType := annotations[ChaosBladeAffinityTypeAnnotation]\n\tif affinityType == \"\" {\n\t\treturn fmt.Errorf(\"affinity type annotation not found, cannot determine which field to restore\")\n\t}\n\n\tswitch affinityType {\n\tcase \"node-affinity\":\n\t\tif originalNodeAffinityStr, ok := annotations[ChaosBladeOriginalNodeAffinityAnnotation]; ok {\n\t\t\tvar nodeAffinity v1.NodeAffinity\n\t\t\tif err := json.Unmarshal([]byte(originalNodeAffinityStr), &nodeAffinity); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshal original node affinity failed: %v\", err)\n\t\t\t}\n\t\t\tif podSpec.Affinity == nil {\n\t\t\t\tpodSpec.Affinity = &v1.Affinity{}\n\t\t\t}\n\t\t\tpodSpec.Affinity.NodeAffinity = &nodeAffinity\n\t\t} else {\n\t\t\tif podSpec.Affinity != nil {\n\t\t\t\tpodSpec.Affinity.NodeAffinity = nil\n\t\t\t}\n\t\t}\n\n\tcase \"pod-affinity\":\n\t\tif originalPodAffinityStr, ok := annotations[ChaosBladeOriginalPodAffinityAnnotation]; ok {\n\t\t\tvar podAffinity v1.PodAffinity\n\t\t\tif err := json.Unmarshal([]byte(originalPodAffinityStr), &podAffinity); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshal original pod affinity failed: %v\", err)\n\t\t\t}\n\t\t\tif podSpec.Affinity == nil {\n\t\t\t\tpodSpec.Affinity = &v1.Affinity{}\n\t\t\t}\n\t\t\tpodSpec.Affinity.PodAffinity = &podAffinity\n\t\t} else {\n\t\t\tif podSpec.Affinity != nil {\n\t\t\t\tpodSpec.Affinity.PodAffinity = nil\n\t\t\t}\n\t\t}\n\n\tcase \"pod-anti-affinity\":\n\t\tif originalPodAntiAffinityStr, ok := annotations[ChaosBladeOriginalPodAntiAffinityAnnotation]; ok {\n\t\t\tvar podAntiAffinity v1.PodAntiAffinity\n\t\t\tif err := json.Unmarshal([]byte(originalPodAntiAffinityStr), &podAntiAffinity); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshal original pod anti-affinity failed: %v\", err)\n\t\t\t}\n\t\t\tif podSpec.Affinity == nil {\n\t\t\t\tpodSpec.Affinity = &v1.Affinity{}\n\t\t\t}\n\t\t\tpodSpec.Affinity.PodAntiAffinity = &podAntiAffinity\n\t\t} else {\n\t\t\tif podSpec.Affinity != nil {\n\t\t\t\tpodSpec.Affinity.PodAntiAffinity = nil\n\t\t\t}\n\t\t}\n\n\tcase \"node-selector\":\n\t\tif originalNodeSelectorStr, ok := annotations[ChaosBladeOriginalNodeSelectorAnnotation]; ok {\n\t\t\tvar originalNodeSelector map[string]string\n\t\t\tif err := json.Unmarshal([]byte(originalNodeSelectorStr), &originalNodeSelector); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshal original node selector failed: %v\", err)\n\t\t\t}\n\t\t\tpodSpec.NodeSelector = originalNodeSelector\n\t\t} else {\n\t\t\tpodSpec.NodeSelector = nil\n\t\t}\n\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown affinity type in annotation: %s\", affinityType)\n\t}\n\n\t// Clean up empty Affinity struct\n\tif podSpec.Affinity != nil &&\n\t\tpodSpec.Affinity.NodeAffinity == nil &&\n\t\tpodSpec.Affinity.PodAffinity == nil &&\n\t\tpodSpec.Affinity.PodAntiAffinity == nil {\n\t\tpodSpec.Affinity = nil\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "exec/pod/taintnode.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/util/retry\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\t// ChaosBladeTaintAnnotation indicates the node taint was modified by chaosblade\n\tChaosBladeTaintAnnotation = \"chaosblade.io/taint\"\n\t// ChaosBladeOriginalTaintsAnnotation stores the original node taints\n\tChaosBladeOriginalTaintsAnnotation = \"chaosblade.io/original-taints\"\n\t// ChaosBladeInjectedTaintAnnotation stores the taint injected by this experiment\n\tChaosBladeInjectedTaintAnnotation = \"chaosblade.io/injected-taint\"\n\t// DefaultTaintKey is the default taint key for injection\n\tDefaultTaintKey = \"chaosblade.io/unreachable\"\n\t// DefaultTaintValue is the default taint value for injection\n\tDefaultTaintValue = \"true\"\n\t// DefaultTaintEffect is the default taint effect\n\tDefaultTaintEffect = \"NoSchedule\"\n)\n\n// chaosBladeTaintAnnotations returns the taintnode-owned annotation keys for cleanup.\nfunc chaosBladeTaintAnnotations() []string {\n\treturn []string{\n\t\tChaosBladeOriginalTaintsAnnotation,\n\t\tChaosBladeInjectedTaintAnnotation,\n\t\tChaosBladeExperimentAnnotation,\n\t}\n}\n\ntype PodTaintNodeActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n\tclient *channel.Client\n}\n\nfunc NewPodTaintNodeActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &PodTaintNodeActionSpec{\n\t\tBaseExpActionCommandSpec: spec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     \"nodes\",\n\t\t\t\t\tDesc:     \"Node names to inject taint, multiple values separated by commas\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:    \"taint-effect\",\n\t\t\t\t\tDesc:    \"Taint effect: NoSchedule (default), NoExecute, PreferNoSchedule. WARNING: NoExecute will evict running pods without matching tolerations\",\n\t\t\t\t\tNoArgs:  false,\n\t\t\t\t\tDefault: DefaultTaintEffect,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:    \"taint-key\",\n\t\t\t\t\tDesc:    \"Custom taint key. Default: chaosblade.io/unreachable\",\n\t\t\t\t\tNoArgs:  false,\n\t\t\t\t\tDefault: DefaultTaintKey,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:    \"taint-value\",\n\t\t\t\t\tDesc:    \"Custom taint value. Default: true\",\n\t\t\t\t\tNoArgs:  false,\n\t\t\t\t\tDefault: DefaultTaintValue,\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &PodTaintNodeActionExecutor{client: client},\n\t\t\tActionExample: `# Add unreachable taint to nodes to prevent pod scheduling\nblade create k8s pod-pod taintnode --nodes node1,node2 --kubeconfig ~/.kube/config\n\n# Add taint with NoExecute effect (will evict running pods)\nblade create k8s pod-pod taintnode --nodes node1 --taint-effect NoExecute --kubeconfig ~/.kube/config\n\n# Add custom taint\nblade create k8s pod-pod taintnode --nodes node1 --taint-key dedicated --taint-value gpu --taint-effect NoSchedule --kubeconfig ~/.kube/config\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t\tclient: client,\n\t}\n}\n\nfunc (*PodTaintNodeActionSpec) Name() string {\n\treturn \"taintnode\"\n}\n\nfunc (*PodTaintNodeActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*PodTaintNodeActionSpec) ShortDesc() string {\n\treturn \"Make pod scheduling fail by adding unreachable taint to nodes\"\n}\n\nfunc (*PodTaintNodeActionSpec) LongDesc() string {\n\treturn \"Simulate the scenario where a Pod cannot be scheduled due to Taint/Toleration mismatch. \" +\n\t\t\"This fault is injected by adding an unreachable taint to the target nodes. \" +\n\t\t\"Pods without matching tolerations will not be scheduled to these nodes. \" +\n\t\t\"When the experiment is destroyed, the original taints will be restored. \" +\n\t\t\"WARNING: Using NoExecute effect will evict running pods that do not have matching tolerations.\"\n}\n\ntype PodTaintNodeActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*PodTaintNodeActionExecutor) Name() string {\n\treturn \"taintnode\"\n}\n\nfunc (*PodTaintNodeActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *PodTaintNodeActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\n// parseTaintNodeFlags parses and validates flags for taintnode action.\nfunc parseTaintNodeFlags(expModel *spec.ExpModel) (nodeNames []string, taintKey, taintValue, taintEffect string, err error) {\n\tnodesFlag := expModel.ActionFlags[\"nodes\"]\n\tif nodesFlag == \"\" {\n\t\treturn nil, \"\", \"\", \"\", fmt.Errorf(\"nodes flag is required\")\n\t}\n\tnodeNames, err = parseNodeNames(nodesFlag)\n\tif err != nil {\n\t\treturn nil, \"\", \"\", \"\", err\n\t}\n\ttaintKey = expModel.ActionFlags[\"taint-key\"]\n\tif taintKey == \"\" {\n\t\ttaintKey = DefaultTaintKey\n\t}\n\ttaintValue = expModel.ActionFlags[\"taint-value\"]\n\tif taintValue == \"\" {\n\t\ttaintValue = DefaultTaintValue\n\t}\n\ttaintEffect = expModel.ActionFlags[\"taint-effect\"]\n\tif taintEffect == \"\" {\n\t\ttaintEffect = DefaultTaintEffect\n\t}\n\n\t// Validate taint effect\n\tswitch taintEffect {\n\tcase string(v1.TaintEffectNoSchedule), string(v1.TaintEffectNoExecute), string(v1.TaintEffectPreferNoSchedule):\n\tdefault:\n\t\treturn nil, \"\", \"\", \"\", fmt.Errorf(\"unsupported taint effect: %s, supported values: NoSchedule, NoExecute, PreferNoSchedule\", taintEffect)\n\t}\n\n\treturn nodeNames, taintKey, taintValue, taintEffect, nil\n}\n\nfunc (d *PodTaintNodeActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tnodeNames, taintKey, taintValue, taintEffect, err := parseTaintNodeFlags(expModel)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterIllegal, err.Error())\n\t}\n\n\tvar resourceStatuses []v1alpha1.ResourceStatus\n\tfor _, nodeName := range nodeNames {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.NodeKind,\n\t\t\tIdentifier: nodeName,\n\t\t}\n\n\t\tif err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {\n\t\t\t// Re-get latest node to avoid conflict\n\t\t\tlatest := &v1.Node{}\n\t\t\tif err := d.client.Get(ctx, types.NamespacedName{Name: nodeName}, latest); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn d.injectTaintToNode(ctx, latest, taintKey, taintValue, taintEffect, experimentId)\n\t\t}); err != nil {\n\t\t\tlogrusField.Warningf(\"inject taint to node %s failed: %v\", nodeName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"inject taint to node %s failed: %v\", nodeName, err), spec.K8sExecFailed.Code)\n\t\t\tresourceStatuses = append(resourceStatuses, status)\n\t\t\tcontinue\n\t\t}\n\t\tlogrusField.Infof(\"injected taint %s=%s:%s to node %s\", taintKey, taintValue, taintEffect, nodeName)\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tresourceStatuses = append(resourceStatuses, status)\n\t}\n\n\t// Check if all nodes failed\n\tallFailed := true\n\tfor _, s := range resourceStatuses {\n\t\tif s.Success {\n\t\t\tallFailed = false\n\t\t\tbreak\n\t\t}\n\t}\n\tif allFailed {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"all nodes failed\", resourceStatuses))\n\t}\n\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateSuccessExperimentStatus(resourceStatuses))\n}\n\nfunc (d *PodTaintNodeActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tnodesFlag := expModel.ActionFlags[\"nodes\"]\n\tnodeNames, err := parseNodeNames(nodesFlag)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterIllegal, err.Error())\n\t}\n\n\tvar resourceStatuses []v1alpha1.ResourceStatus\n\tfor _, nodeName := range nodeNames {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.NodeKind,\n\t\t\tIdentifier: nodeName,\n\t\t}\n\n\t\tif err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {\n\t\t\t// Re-get latest node to avoid conflict\n\t\t\tlatest := &v1.Node{}\n\t\t\tif err := d.client.Get(ctx, types.NamespacedName{Name: nodeName}, latest); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn d.restoreNodeTaints(ctx, latest, experimentId)\n\t\t}); err != nil {\n\t\t\tlogrusField.Warningf(\"restore node %s taints failed: %v\", nodeName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"restore node %s taints failed: %v\", nodeName, err), spec.K8sExecFailed.Code)\n\t\t\tresourceStatuses = append(resourceStatuses, status)\n\t\t\tcontinue\n\t\t}\n\t\tlogrusField.Infof(\"restored node %s taints\", nodeName)\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatus.State = v1alpha1.DestroyedState\n\t\tresourceStatuses = append(resourceStatuses, status)\n\t}\n\n\tallFailed := true\n\tfor _, s := range resourceStatuses {\n\t\tif s.Success {\n\t\t\tallFailed = false\n\t\t\tbreak\n\t\t}\n\t}\n\tif allFailed {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"all nodes restore failed\", resourceStatuses))\n\t}\n\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus(resourceStatuses))\n}\n\n// injectTaintToNode adds an unreachable taint to a node.\nfunc (d *PodTaintNodeActionExecutor) injectTaintToNode(ctx context.Context, node *v1.Node, taintKey, taintValue, taintEffect, experimentId string) error {\n\tif node.Annotations == nil {\n\t\tnode.Annotations = make(map[string]string)\n\t}\n\n\t// Check for conflicting experiment\n\tif existingId, ok := node.Annotations[ChaosBladeExperimentAnnotation]; ok && existingId != \"\" && existingId != experimentId {\n\t\treturn fmt.Errorf(\"node is already modified by another chaosblade experiment: %s\", existingId)\n\t}\n\n\t// Idempotent: if already modified by the same experiment, skip re-injection\n\tif node.Annotations[ChaosBladeExperimentAnnotation] == experimentId {\n\t\treturn nil\n\t}\n\n\t// Backup original taints\n\tif len(node.Spec.Taints) > 0 {\n\t\toriginalBytes, err := json.Marshal(node.Spec.Taints)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"marshal original taints failed: %v\", err)\n\t\t}\n\t\tnode.Annotations[ChaosBladeOriginalTaintsAnnotation] = string(originalBytes)\n\t}\n\n\t// Add the unreachable taint; refuse to overwrite an existing taint with the same key+effect\n\tnewTaint := v1.Taint{\n\t\tKey:    taintKey,\n\t\tValue:  taintValue,\n\t\tEffect: v1.TaintEffect(taintEffect),\n\t}\n\tif idx := findTaintIndex(node.Spec.Taints, newTaint.Key, newTaint.Effect); idx >= 0 {\n\t\texisting := node.Spec.Taints[idx]\n\t\treturn fmt.Errorf(\"node already has taint with key %q and effect %q (value %q); refusing to overwrite existing taint\", existing.Key, existing.Effect, existing.Value)\n\t}\n\tnode.Spec.Taints = append(node.Spec.Taints, newTaint)\n\n\t// Record injected taint for surgical removal during restore\n\tinjectedBytes, _ := json.Marshal(newTaint)\n\tnode.Annotations[ChaosBladeInjectedTaintAnnotation] = string(injectedBytes)\n\n\t// Set annotations\n\tnode.Annotations[ChaosBladeTaintAnnotation] = ChaosBladeModifyAction\n\tnode.Annotations[ChaosBladeExperimentAnnotation] = experimentId\n\n\treturn d.client.Update(ctx, node)\n}\n\n// restoreNodeTaints removes only the taint injected by this experiment,\n// preserving any taints added by other controllers during the experiment.\nfunc (d *PodTaintNodeActionExecutor) restoreNodeTaints(ctx context.Context, node *v1.Node, experimentId string) error {\n\tif node.Annotations == nil {\n\t\treturn nil\n\t}\n\n\t// If this experiment did not modify the node, nothing to restore\n\tif node.Annotations[ChaosBladeExperimentAnnotation] != experimentId {\n\t\treturn nil\n\t}\n\n\t// Remove only the injected taint, preserving taints added by other controllers\n\tif injectedStr, ok := node.Annotations[ChaosBladeInjectedTaintAnnotation]; ok {\n\t\tvar injected v1.Taint\n\t\tif err := json.Unmarshal([]byte(injectedStr), &injected); err != nil {\n\t\t\treturn fmt.Errorf(\"unmarshal injected taint failed: %v\", err)\n\t\t}\n\t\tnode.Spec.Taints = removeTaintByKeyEffect(node.Spec.Taints, injected.Key, injected.Effect)\n\t} else {\n\t\t// Fallback: no injected-taint annotation, use original snapshot\n\t\tif originalTaintsStr, ok := node.Annotations[ChaosBladeOriginalTaintsAnnotation]; ok {\n\t\t\tvar originalTaints []v1.Taint\n\t\t\tif err := json.Unmarshal([]byte(originalTaintsStr), &originalTaints); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshal original taints failed: %v\", err)\n\t\t\t}\n\t\t\tnode.Spec.Taints = originalTaints\n\t\t} else {\n\t\t\tnode.Spec.Taints = nil\n\t\t}\n\t}\n\n\t// Clean up annotations\n\tdelete(node.Annotations, ChaosBladeTaintAnnotation)\n\tfor _, key := range chaosBladeTaintAnnotations() {\n\t\tdelete(node.Annotations, key)\n\t}\n\n\treturn d.client.Update(ctx, node)\n}\n\n// findTaintIndex returns the index of the first taint matching key+effect, or -1.\nfunc findTaintIndex(taints []v1.Taint, key string, effect v1.TaintEffect) int {\n\tfor i, t := range taints {\n\t\tif t.Key == key && t.Effect == effect {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\n// removeTaintByKeyEffect removes the first taint matching key+effect from the list.\n// Kubernetes guarantees key+effect uniqueness per node, so this is sufficient for removal.\nfunc removeTaintByKeyEffect(taints []v1.Taint, key string, effect v1.TaintEffect) []v1.Taint {\n\tfor i, t := range taints {\n\t\tif t.Key == key && t.Effect == effect {\n\t\t\treturn append(taints[:i], taints[i+1:]...)\n\t\t}\n\t}\n\treturn taints\n}\n\n// parseNodeNames splits a comma-separated nodes flag, trims whitespace, and rejects empty entries.\nfunc parseNodeNames(nodesFlag string) ([]string, error) {\n\tvar result []string\n\tfor _, n := range strings.Split(nodesFlag, \",\") {\n\t\tn = strings.TrimSpace(n)\n\t\tif n == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"nodes flag contains empty node name\")\n\t\t}\n\t\tresult = append(result, n)\n\t}\n\treturn result, nil\n}\n\n// validateTaintNodeFlags validates the nodes flag.\nfunc validateTaintNodeFlags(nodes string) *spec.Response {\n\tif nodes == \"\" {\n\t\treturn spec.ResponseFailWithFlags(spec.ParameterLess, \"nodes\")\n\t}\n\treturn nil\n}\n\n// PreCreate implements model.ActionPreProcessor interface.\nfunc (a *PodTaintNodeActionSpec) PreCreate(ctx context.Context, expModel *spec.ExpModel, client *channel.Client) (context.Context, *spec.Response) {\n\tnodes := expModel.ActionFlags[\"nodes\"]\n\tif resp := validateTaintNodeFlags(nodes); resp != nil {\n\t\treturn ctx, resp\n\t}\n\n\tnodeNames, err := parseNodeNames(nodes)\n\tif err != nil {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterIllegal, err.Error())\n\t}\n\ttaintEffect := expModel.ActionFlags[\"taint-effect\"]\n\tif taintEffect == \"\" {\n\t\ttaintEffect = DefaultTaintEffect\n\t}\n\t// Validate taint effect in PreCreate to fail fast\n\tswitch taintEffect {\n\tcase string(v1.TaintEffectNoSchedule), string(v1.TaintEffectNoExecute), string(v1.TaintEffectPreferNoSchedule):\n\tdefault:\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterIllegal, fmt.Sprintf(\"unsupported taint effect: %s, supported values: NoSchedule, NoExecute, PreferNoSchedule\", taintEffect))\n\t}\n\n\tcontainerObjectMetaList := model.ContainerMatchedList{}\n\tfor _, nodeName := range nodeNames {\n\t\tcontainerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{\n\t\t\tNamespace: \"\",\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-tn-%s\", nodeName),\n\t\t\tNodeName:  nodeName,\n\t\t})\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n\n// PreDestroy implements model.ActionPreProcessor interface.\nfunc (a *PodTaintNodeActionSpec) PreDestroy(ctx context.Context, expModel *spec.ExpModel, client *channel.Client, oldExpStatus v1alpha1.ExperimentStatus) (context.Context, *spec.Response) {\n\tnodes := expModel.ActionFlags[\"nodes\"]\n\tif resp := validateTaintNodeFlags(nodes); resp != nil {\n\t\treturn ctx, resp\n\t}\n\n\tnodeNames, err := parseNodeNames(nodes)\n\tif err != nil {\n\t\treturn ctx, spec.ResponseFailWithFlags(spec.ParameterIllegal, err.Error())\n\t}\n\tcontainerObjectMetaList := model.ContainerMatchedList{}\n\tfor _, nodeName := range nodeNames {\n\t\tcontainerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{\n\t\t\tNamespace: \"\",\n\t\t\tPodName:   fmt.Sprintf(\"chaosblade-tn-%s\", nodeName),\n\t\t\tNodeName:  nodeName,\n\t\t})\n\t}\n\n\tctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList)\n\treturn ctx, nil\n}\n"
  },
  {
    "path": "exec/pod/taintnode_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestParseTaintNodeFlags(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tactionFlags map[string]string\n\t\twantNodes   []string\n\t\twantKey     string\n\t\twantValue   string\n\t\twantEffect  string\n\t\twantErr     bool\n\t\terrContains string\n\t}{\n\t\t{\n\t\t\tname:        \"default values\",\n\t\t\tactionFlags: map[string]string{\"nodes\": \"node1\"},\n\t\t\twantNodes:   []string{\"node1\"},\n\t\t\twantKey:     DefaultTaintKey,\n\t\t\twantValue:   DefaultTaintValue,\n\t\t\twantEffect:  DefaultTaintEffect,\n\t\t},\n\t\t{\n\t\t\tname:        \"multiple nodes\",\n\t\t\tactionFlags: map[string]string{\"nodes\": \"node1,node2,node3\"},\n\t\t\twantNodes:   []string{\"node1\", \"node2\", \"node3\"},\n\t\t\twantKey:     DefaultTaintKey,\n\t\t\twantValue:   DefaultTaintValue,\n\t\t\twantEffect:  DefaultTaintEffect,\n\t\t},\n\t\t{\n\t\t\tname: \"custom taint params\",\n\t\t\tactionFlags: map[string]string{\n\t\t\t\t\"nodes\":        \"node1\",\n\t\t\t\t\"taint-key\":    \"dedicated\",\n\t\t\t\t\"taint-value\":  \"gpu\",\n\t\t\t\t\"taint-effect\": \"NoExecute\",\n\t\t\t},\n\t\t\twantNodes:  []string{\"node1\"},\n\t\t\twantKey:    \"dedicated\",\n\t\t\twantValue:  \"gpu\",\n\t\t\twantEffect: \"NoExecute\",\n\t\t},\n\t\t{\n\t\t\tname:        \"missing nodes flag\",\n\t\t\tactionFlags: map[string]string{},\n\t\t\twantErr:     true,\n\t\t\terrContains: \"nodes flag is required\",\n\t\t},\n\t\t{\n\t\t\tname: \"unsupported taint effect\",\n\t\t\tactionFlags: map[string]string{\n\t\t\t\t\"nodes\":        \"node1\",\n\t\t\t\t\"taint-effect\": \"InvalidEffect\",\n\t\t\t},\n\t\t\twantErr:     true,\n\t\t\terrContains: \"unsupported taint effect\",\n\t\t},\n\t\t{\n\t\t\tname:        \"PreferNoSchedule is valid\",\n\t\t\tactionFlags: map[string]string{\"nodes\": \"node1\", \"taint-effect\": \"PreferNoSchedule\"},\n\t\t\twantNodes:   []string{\"node1\"},\n\t\t\twantKey:     DefaultTaintKey,\n\t\t\twantValue:   DefaultTaintValue,\n\t\t\twantEffect:  \"PreferNoSchedule\",\n\t\t},\n\t\t{\n\t\t\tname:        \"whitespace trimmed\",\n\t\t\tactionFlags: map[string]string{\"nodes\": \" node1 , node2 \"},\n\t\t\twantNodes:   []string{\"node1\", \"node2\"},\n\t\t\twantKey:     DefaultTaintKey,\n\t\t\twantValue:   DefaultTaintValue,\n\t\t\twantEffect:  DefaultTaintEffect,\n\t\t},\n\t\t{\n\t\t\tname:        \"trailing comma rejected\",\n\t\t\tactionFlags: map[string]string{\"nodes\": \"node1,\"},\n\t\t\twantErr:     true,\n\t\t\terrContains: \"empty node name\",\n\t\t},\n\t\t{\n\t\t\tname:        \"double comma rejected\",\n\t\t\tactionFlags: map[string]string{\"nodes\": \"node1,,node2\"},\n\t\t\twantErr:     true,\n\t\t\terrContains: \"empty node name\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\texpModel := &spec.ExpModel{ActionFlags: tt.actionFlags}\n\t\t\tnodes, key, value, effect, err := parseTaintNodeFlags(expModel)\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"parseTaintNodeFlags() expected error containing %q, got nil\", tt.errContains)\n\t\t\t\t} else if tt.errContains != \"\" && !containsString(err.Error(), tt.errContains) {\n\t\t\t\t\tt.Errorf(\"parseTaintNodeFlags() error = %q, want containing %q\", err.Error(), tt.errContains)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"parseTaintNodeFlags() unexpected error: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif len(nodes) != len(tt.wantNodes) {\n\t\t\t\tt.Errorf(\"parseTaintNodeFlags() nodes = %v, want %v\", nodes, tt.wantNodes)\n\t\t\t}\n\t\t\tfor i, n := range nodes {\n\t\t\t\tif n != tt.wantNodes[i] {\n\t\t\t\t\tt.Errorf(\"parseTaintNodeFlags() nodes[%d] = %q, want %q\", i, n, tt.wantNodes[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\tif key != tt.wantKey {\n\t\t\t\tt.Errorf(\"parseTaintNodeFlags() key = %q, want %q\", key, tt.wantKey)\n\t\t\t}\n\t\t\tif value != tt.wantValue {\n\t\t\t\tt.Errorf(\"parseTaintNodeFlags() value = %q, want %q\", value, tt.wantValue)\n\t\t\t}\n\t\t\tif effect != tt.wantEffect {\n\t\t\t\tt.Errorf(\"parseTaintNodeFlags() effect = %q, want %q\", effect, tt.wantEffect)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestValidateTaintNodeFlags(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tnodes    string\n\t\twantFail bool\n\t}{\n\t\t{\"valid nodes\", \"node1\", false},\n\t\t{\"empty nodes\", \"\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresp := validateTaintNodeFlags(tt.nodes)\n\t\t\tif tt.wantFail && resp == nil {\n\t\t\t\tt.Error(\"validateTaintNodeFlags() expected failure, got nil\")\n\t\t\t}\n\t\t\tif !tt.wantFail && resp != nil {\n\t\t\t\tt.Errorf(\"validateTaintNodeFlags() unexpected failure: %v\", resp)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInjectTaintToNode_ConflictDetection(t *testing.T) {\n\tnode := &v1.Node{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"test-node\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladeExperimentAnnotation: \"other-experiment\",\n\t\t\t},\n\t\t},\n\t}\n\n\texecutor := &PodTaintNodeActionExecutor{}\n\t// injectTaintToNode is a method, so we test the conflict logic directly\n\terr := executor.injectTaintToNode(nil, node, DefaultTaintKey, DefaultTaintValue, DefaultTaintEffect, \"my-experiment\")\n\tif err == nil {\n\t\tt.Error(\"expected conflict error when node is already modified by another experiment\")\n\t}\n\tif !containsString(err.Error(), \"already modified by another chaosblade experiment\") {\n\t\tt.Errorf(\"unexpected error message: %v\", err)\n\t}\n}\n\nfunc TestInjectTaintToNode_Idempotent(t *testing.T) {\n\tnode := &v1.Node{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"test-node\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladeExperimentAnnotation: \"my-experiment\",\n\t\t\t},\n\t\t},\n\t}\n\n\texecutor := &PodTaintNodeActionExecutor{}\n\t// Same experiment ID should skip injection (idempotent)\n\terr := executor.injectTaintToNode(nil, node, DefaultTaintKey, DefaultTaintValue, DefaultTaintEffect, \"my-experiment\")\n\tif err != nil {\n\t\tt.Errorf(\"expected nil for idempotent injection, got: %v\", err)\n\t}\n}\n\nfunc TestInjectTaintToNode_BackupAndInject(t *testing.T) {\n\toriginalTaints := []v1.Taint{\n\t\t{Key: \"existing-key\", Value: \"existing-value\", Effect: v1.TaintEffectNoSchedule},\n\t}\n\tnode := &v1.Node{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        \"test-node\",\n\t\t\tAnnotations: map[string]string{},\n\t\t},\n\t\tSpec: v1.NodeSpec{\n\t\t\tTaints: originalTaints,\n\t\t},\n\t}\n\n\t// Simulate the injection logic without calling injectTaintToNode\n\t// (which requires a real client for Update)\n\tbackupBytes, _ := json.Marshal(originalTaints)\n\tnode.Annotations[ChaosBladeOriginalTaintsAnnotation] = string(backupBytes)\n\tnode.Spec.Taints = append(node.Spec.Taints, v1.Taint{\n\t\tKey:    DefaultTaintKey,\n\t\tValue:  DefaultTaintValue,\n\t\tEffect: v1.TaintEffect(DefaultTaintEffect),\n\t})\n\tnode.Annotations[ChaosBladeTaintAnnotation] = ChaosBladeModifyAction\n\tnode.Annotations[ChaosBladeExperimentAnnotation] = \"my-experiment\"\n\n\t// Verify original taints are backed up\n\tbackupStr := node.Annotations[ChaosBladeOriginalTaintsAnnotation]\n\tif backupStr == \"\" {\n\t\tt.Error(\"expected original taints to be backed up in annotations\")\n\t}\n\tvar backedUp []v1.Taint\n\tif err := json.Unmarshal([]byte(backupStr), &backedUp); err != nil {\n\t\tt.Fatalf(\"failed to unmarshal backup: %v\", err)\n\t}\n\tif len(backedUp) != 1 || backedUp[0].Key != \"existing-key\" {\n\t\tt.Errorf(\"backup = %v, want [{existing-key existing-value NoSchedule}]\", backedUp)\n\t}\n\n\t// Verify new taint was appended\n\tfound := false\n\tfor _, tt := range node.Spec.Taints {\n\t\tif tt.Key == DefaultTaintKey {\n\t\t\tfound = true\n\t\t\tif tt.Value != DefaultTaintValue || tt.Effect != v1.TaintEffectNoSchedule {\n\t\t\t\tt.Errorf(\"injected taint = %v, want key=%s value=%s effect=NoSchedule\", tt, DefaultTaintKey, DefaultTaintValue)\n\t\t\t}\n\t\t}\n\t}\n\tif !found {\n\t\tt.Error(\"expected chaosblade taint to be added to node spec\")\n\t}\n\n\t// Verify annotations\n\tif node.Annotations[ChaosBladeTaintAnnotation] != ChaosBladeModifyAction {\n\t\tt.Errorf(\"chaosblade.io/taint annotation = %q, want %q\", node.Annotations[ChaosBladeTaintAnnotation], ChaosBladeModifyAction)\n\t}\n\tif node.Annotations[ChaosBladeExperimentAnnotation] != \"my-experiment\" {\n\t\tt.Errorf(\"chaosblade.io/experiment annotation = %q, want %q\", node.Annotations[ChaosBladeExperimentAnnotation], \"my-experiment\")\n\t}\n}\n\nfunc TestInjectTaintToNode_DuplicateKeyEffect(t *testing.T) {\n\t// Node already has a taint with the same key+effect as the one we want to inject\n\tnode := &v1.Node{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:        \"test-node\",\n\t\t\tAnnotations: map[string]string{},\n\t\t},\n\t\tSpec: v1.NodeSpec{\n\t\t\tTaints: []v1.Taint{\n\t\t\t\t{Key: DefaultTaintKey, Value: \"existing-value\", Effect: v1.TaintEffectNoSchedule},\n\t\t\t},\n\t\t},\n\t}\n\n\texecutor := &PodTaintNodeActionExecutor{}\n\terr := executor.injectTaintToNode(nil, node, DefaultTaintKey, DefaultTaintValue, DefaultTaintEffect, \"my-experiment\")\n\tif err == nil {\n\t\tt.Error(\"expected error when node already has taint with same key+effect\")\n\t}\n\tif !strings.Contains(err.Error(), \"refusing to overwrite\") {\n\t\tt.Errorf(\"unexpected error message: %v\", err)\n\t}\n\n\t// Verify node taints were NOT modified\n\tif len(node.Spec.Taints) != 1 {\n\t\tt.Fatalf(\"expected 1 taint (unchanged), got %d\", len(node.Spec.Taints))\n\t}\n\tif node.Spec.Taints[0].Value != \"existing-value\" {\n\t\tt.Errorf(\"existing taint value should be unchanged, got %q\", node.Spec.Taints[0].Value)\n\t}\n}\n\nfunc TestRestoreNodeTaints_NotModifiedByExperiment(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tannotations map[string]string\n\t\texperiment  string\n\t\twantErr     bool\n\t}{\n\t\t{\n\t\t\tname:        \"nil annotations - no error\",\n\t\t\tannotations: nil,\n\t\t\texperiment:  \"my-experiment\",\n\t\t\twantErr:     false,\n\t\t},\n\t\t{\n\t\t\tname:        \"different experiment - skip restore\",\n\t\t\tannotations: map[string]string{ChaosBladeExperimentAnnotation: \"other-experiment\"},\n\t\t\texperiment:  \"my-experiment\",\n\t\t\twantErr:     false,\n\t\t},\n\t\t{\n\t\t\tname:        \"empty annotations - skip restore\",\n\t\t\tannotations: map[string]string{},\n\t\t\texperiment:  \"my-experiment\",\n\t\t\twantErr:     false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tnode := &v1.Node{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:        \"test-node\",\n\t\t\t\t\tAnnotations: tt.annotations,\n\t\t\t\t},\n\t\t\t}\n\t\t\texecutor := &PodTaintNodeActionExecutor{}\n\t\t\terr := executor.restoreNodeTaints(nil, node, tt.experiment)\n\t\t\tif tt.wantErr && err == nil {\n\t\t\t\tt.Error(\"expected error, got nil\")\n\t\t\t}\n\t\t\tif !tt.wantErr && err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRestoreNodeTaints_SuccessfulRestore(t *testing.T) {\n\toriginalTaints := []v1.Taint{\n\t\t{Key: \"sigma.ali/resource-pool\", Value: \"ackee_pool\", Effect: v1.TaintEffectNoSchedule},\n\t}\n\tbackupBytes, _ := json.Marshal(originalTaints)\n\tinjectedTaint := v1.Taint{Key: DefaultTaintKey, Value: DefaultTaintValue, Effect: v1.TaintEffectNoSchedule}\n\tinjectedBytes, _ := json.Marshal(injectedTaint)\n\n\tnode := &v1.Node{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"test-node\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladeExperimentAnnotation:     \"my-experiment\",\n\t\t\t\tChaosBladeOriginalTaintsAnnotation: string(backupBytes),\n\t\t\t\tChaosBladeInjectedTaintAnnotation:  string(injectedBytes),\n\t\t\t\tChaosBladeTaintAnnotation:          ChaosBladeModifyAction,\n\t\t\t},\n\t\t},\n\t\tSpec: v1.NodeSpec{\n\t\t\tTaints: []v1.Taint{\n\t\t\t\t{Key: \"sigma.ali/resource-pool\", Value: \"ackee_pool\", Effect: v1.TaintEffectNoSchedule},\n\t\t\t\t{Key: DefaultTaintKey, Value: DefaultTaintValue, Effect: v1.TaintEffectNoSchedule},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Simulate restore logic: remove only the injected taint\n\tif injectedStr, ok := node.Annotations[ChaosBladeInjectedTaintAnnotation]; ok {\n\t\tvar injected v1.Taint\n\t\tif err := json.Unmarshal([]byte(injectedStr), &injected); err != nil {\n\t\t\tt.Fatalf(\"failed to unmarshal injected taint: %v\", err)\n\t\t}\n\t\tnode.Spec.Taints = removeTaintByKeyEffect(node.Spec.Taints, injected.Key, injected.Effect)\n\t}\n\tdelete(node.Annotations, ChaosBladeTaintAnnotation)\n\tfor _, key := range chaosBladeTaintAnnotations() {\n\t\tdelete(node.Annotations, key)\n\t}\n\n\t// Verify injected taint removed, original preserved\n\tif len(node.Spec.Taints) != 1 {\n\t\tt.Fatalf(\"expected 1 taint after restore, got %d\", len(node.Spec.Taints))\n\t}\n\tif node.Spec.Taints[0].Key != \"sigma.ali/resource-pool\" {\n\t\tt.Errorf(\"remaining taint key = %q, want sigma.ali/resource-pool\", node.Spec.Taints[0].Key)\n\t}\n\n\t// Verify annotations cleaned up\n\tfor _, key := range chaosBladeTaintAnnotations() {\n\t\tif _, ok := node.Annotations[key]; ok {\n\t\t\tt.Errorf(\"annotation %q should be cleaned up\", key)\n\t\t}\n\t}\n\tif _, ok := node.Annotations[ChaosBladeTaintAnnotation]; ok {\n\t\tt.Error(\"chaosblade.io/taint annotation should be cleaned up\")\n\t}\n}\n\nfunc TestRestoreNodeTaints_PreservesNewTaints(t *testing.T) {\n\t// Simulate: another controller added a taint while experiment was running\n\toriginalTaints := []v1.Taint{\n\t\t{Key: \"sigma.ali/resource-pool\", Value: \"ackee_pool\", Effect: v1.TaintEffectNoSchedule},\n\t}\n\tbackupBytes, _ := json.Marshal(originalTaints)\n\tinjectedTaint := v1.Taint{Key: DefaultTaintKey, Value: DefaultTaintValue, Effect: v1.TaintEffectNoSchedule}\n\tinjectedBytes, _ := json.Marshal(injectedTaint)\n\n\tnode := &v1.Node{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"test-node\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladeExperimentAnnotation:     \"my-experiment\",\n\t\t\t\tChaosBladeOriginalTaintsAnnotation: string(backupBytes),\n\t\t\t\tChaosBladeInjectedTaintAnnotation:  string(injectedBytes),\n\t\t\t\tChaosBladeTaintAnnotation:          ChaosBladeModifyAction,\n\t\t\t},\n\t\t},\n\t\tSpec: v1.NodeSpec{\n\t\t\tTaints: []v1.Taint{\n\t\t\t\t{Key: \"sigma.ali/resource-pool\", Value: \"ackee_pool\", Effect: v1.TaintEffectNoSchedule},\n\t\t\t\t{Key: \"other-controller/key\", Value: \"val\", Effect: v1.TaintEffectNoSchedule},\n\t\t\t\t{Key: DefaultTaintKey, Value: DefaultTaintValue, Effect: v1.TaintEffectNoSchedule},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Simulate restore logic\n\tif injectedStr, ok := node.Annotations[ChaosBladeInjectedTaintAnnotation]; ok {\n\t\tvar injected v1.Taint\n\t\tif err := json.Unmarshal([]byte(injectedStr), &injected); err != nil {\n\t\t\tt.Fatalf(\"failed to unmarshal injected taint: %v\", err)\n\t\t}\n\t\tnode.Spec.Taints = removeTaintByKeyEffect(node.Spec.Taints, injected.Key, injected.Effect)\n\t}\n\tdelete(node.Annotations, ChaosBladeTaintAnnotation)\n\tfor _, key := range chaosBladeTaintAnnotations() {\n\t\tdelete(node.Annotations, key)\n\t}\n\n\t// Verify: injected taint removed, other-controller taint preserved\n\tif len(node.Spec.Taints) != 2 {\n\t\tt.Fatalf(\"expected 2 taints after restore, got %d: %v\", len(node.Spec.Taints), node.Spec.Taints)\n\t}\n\tfound := false\n\tfor _, t := range node.Spec.Taints {\n\t\tif t.Key == \"other-controller/key\" {\n\t\t\tfound = true\n\t\t}\n\t}\n\tif !found {\n\t\tt.Error(\"expected other-controller taint to be preserved after restore\")\n\t}\n}\n\nfunc TestRestoreNodeTaints_NoBackup(t *testing.T) {\n\t// Node had no taints before injection, so no backup exists\n\tinjectedTaint := v1.Taint{Key: DefaultTaintKey, Value: DefaultTaintValue, Effect: v1.TaintEffectNoSchedule}\n\tinjectedBytes, _ := json.Marshal(injectedTaint)\n\n\tnode := &v1.Node{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"test-node\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tChaosBladeExperimentAnnotation:    \"my-experiment\",\n\t\t\t\tChaosBladeInjectedTaintAnnotation: string(injectedBytes),\n\t\t\t\tChaosBladeTaintAnnotation:         ChaosBladeModifyAction,\n\t\t\t},\n\t\t},\n\t\tSpec: v1.NodeSpec{\n\t\t\tTaints: []v1.Taint{\n\t\t\t\t{Key: DefaultTaintKey, Value: DefaultTaintValue, Effect: v1.TaintEffectNoSchedule},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Simulate restore logic: remove only the injected taint\n\tif injectedStr, ok := node.Annotations[ChaosBladeInjectedTaintAnnotation]; ok {\n\t\tvar injected v1.Taint\n\t\tif err := json.Unmarshal([]byte(injectedStr), &injected); err != nil {\n\t\t\tt.Fatalf(\"failed to unmarshal injected taint: %v\", err)\n\t\t}\n\t\tnode.Spec.Taints = removeTaintByKeyEffect(node.Spec.Taints, injected.Key, injected.Effect)\n\t}\n\tdelete(node.Annotations, ChaosBladeTaintAnnotation)\n\tfor _, key := range chaosBladeTaintAnnotations() {\n\t\tdelete(node.Annotations, key)\n\t}\n\n\t// Should have no taints after restore\n\tif len(node.Spec.Taints) != 0 {\n\t\tt.Errorf(\"expected 0 taints after restore (node had no original taints), got %d: %v\", len(node.Spec.Taints), node.Spec.Taints)\n\t}\n}\n\nfunc containsString(s, substr string) bool {\n\treturn strings.Contains(s, substr)\n}\n"
  },
  {
    "path": "exec/pod/terminating.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\t// PodTerminatingFinalizer is the finalizer added to the target pod to block its deletion\n\tPodTerminatingFinalizer = \"chaosblade.io/pod-terminating\"\n)\n\ntype PodTerminatingActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc NewPodTerminatingActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &PodTerminatingActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags:    []spec.ExpFlagSpec{},\n\t\t\tActionExecutor: &PodTerminatingActionExecutor{client: client},\n\t\t\tActionExample: `# Make the pod stuck in Terminating state in the default namespace\nblade create k8s pod-pod terminating --names nginx-app --namespace default --kubeconfig ~/.kube/config\n\n# Make pods stuck in Terminating state by labels\nblade create k8s pod-pod terminating --labels app=guestbook --namespace default --evict-count 2 --kubeconfig ~/.kube/config\n`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*PodTerminatingActionSpec) Name() string {\n\treturn \"terminating\"\n}\n\nfunc (*PodTerminatingActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*PodTerminatingActionSpec) ShortDesc() string {\n\treturn \"Make pod stuck in Terminating state by adding a finalizer\"\n}\n\nfunc (*PodTerminatingActionSpec) LongDesc() string {\n\treturn \"Simulate the scenario where a Pod is stuck in Terminating state due to uncleaned finalizers. \" +\n\t\t\"This fault injects by adding a custom finalizer to the target Pod and then deleting it, \" +\n\t\t\"which causes the Pod to remain in Terminating state because the finalizer prevents garbage collection. \" +\n\t\t\"When the experiment is destroyed, the finalizer will be removed so the Pod can be fully deleted.\"\n}\n\ntype PodTerminatingActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*PodTerminatingActionExecutor) Name() string {\n\treturn \"terminating\"\n}\n\nfunc (*PodTerminatingActionExecutor) SetChannel(channel spec.Channel) {}\n\nfunc (d *PodTerminatingActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *PodTerminatingActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\n\tfor _, meta := range containerObjectMetaList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: meta.GetIdentifier(),\n\t\t}\n\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Name: meta.PodName, Namespace: meta.Namespace}, pod)\n\t\tif err != nil {\n\t\t\tlogrusField.Warningf(\"get pod %s/%s err, %v\", meta.Namespace, meta.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Skip if pod is already being deleted\n\t\tif pod.DeletionTimestamp != nil {\n\t\t\tlogrusField.Warningf(\"pod %s/%s is already terminating, cannot inject fault\", meta.Namespace, meta.PodName)\n\t\t\tstatus = status.CreateFailResourceStatus(\"pod is already in Terminating state, no fault injected\", spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Step 1: Add the finalizer to the pod\n\t\tif err := d.addFinalizer(ctx, pod); err != nil {\n\t\t\tlogrusField.Warningf(\"add finalizer to pod %s/%s err, %v\", meta.Namespace, meta.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"add finalizer failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Step 2: Delete the pod, it will be stuck in Terminating because of the finalizer\n\t\tif err := d.client.Delete(ctx, pod); err != nil {\n\t\t\tlogrusField.Warningf(\"delete pod %s/%s err, %v\", meta.Namespace, meta.PodName, err)\n\t\t\t// Best-effort rollback: remove the finalizer we just added\n\t\t\tif rbErr := d.removeFinalizer(ctx, pod); rbErr != nil {\n\t\t\t\tlogrusField.Warningf(\"rollback finalizer for pod %s/%s failed: %v\", meta.Namespace, meta.PodName, rbErr)\n\t\t\t}\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"delete pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\tlogrusField.Infof(\"pod %s/%s is now stuck in Terminating state with finalizer %s\",\n\t\t\tmeta.Namespace, meta.PodName, PodTerminatingFinalizer)\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatuses = append(statuses, status)\n\t\tsuccess = true\n\t}\n\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *PodTerminatingActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrusField := logrus.WithField(\"experiment\", experimentId)\n\n\tcontainerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{}))\n\t}\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tallSuccess := true\n\n\tfor _, meta := range containerObjectMetaList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.PodKind,\n\t\t\tIdentifier: meta.GetIdentifier(),\n\t\t}\n\n\t\tpod := &v1.Pod{}\n\t\terr := d.client.Get(ctx, types.NamespacedName{Name: meta.PodName, Namespace: meta.Namespace}, pod)\n\t\tif err != nil {\n\t\t\t// Distinguish between NotFound and other errors (RBAC, API server unreachable, etc.)\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\t// Pod is already fully deleted, treat as success\n\t\t\t\tlogrusField.Infof(\"pod %s/%s already deleted\", meta.Namespace, meta.PodName)\n\t\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t\t\tstatus.State = v1alpha1.DestroyedState\n\t\t\t} else {\n\t\t\t\t// Other errors mean the finalizer may still be present\n\t\t\t\tlogrusField.Warningf(\"get pod %s/%s err, %v\", meta.Namespace, meta.PodName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"get pod failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\t\tallSuccess = false\n\t\t\t}\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Remove the finalizer to allow the pod to be fully deleted\n\t\tif err := d.removeFinalizer(ctx, pod); err != nil {\n\t\t\tlogrusField.Warningf(\"remove finalizer from pod %s/%s err, %v\", meta.Namespace, meta.PodName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(fmt.Sprintf(\"remove finalizer failed: %v\", err), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tallSuccess = false\n\t\t\tcontinue\n\t\t}\n\n\t\tlogrusField.Infof(\"removed finalizer from pod %s/%s, pod will be fully deleted\",\n\t\t\tmeta.Namespace, meta.PodName)\n\n\t\tstatus = status.CreateSuccessResourceStatus()\n\t\tstatus.State = v1alpha1.DestroyedState\n\t\tstatuses = append(statuses, status)\n\t}\n\n\tif allSuccess {\n\t\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus(statuses))\n\t}\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses))\n}\n\n// addFinalizer adds the chaosblade pod-terminating finalizer to the pod\nfunc (d *PodTerminatingActionExecutor) addFinalizer(ctx context.Context, pod *v1.Pod) error {\n\tfinalizers := pod.GetFinalizers()\n\tfor _, f := range finalizers {\n\t\tif f == PodTerminatingFinalizer {\n\t\t\t// Finalizer already exists\n\t\t\treturn nil\n\t\t}\n\t}\n\tpod.SetFinalizers(append(finalizers, PodTerminatingFinalizer))\n\treturn d.client.Update(ctx, pod)\n}\n\n// removeFinalizer removes the chaosblade pod-terminating finalizer from the pod\nfunc (d *PodTerminatingActionExecutor) removeFinalizer(ctx context.Context, pod *v1.Pod) error {\n\tfinalizers := pod.GetFinalizers()\n\tnewFinalizers := make([]string, 0, len(finalizers))\n\tfound := false\n\tfor _, f := range finalizers {\n\t\tif f == PodTerminatingFinalizer {\n\t\t\tfound = true\n\t\t\tcontinue\n\t\t}\n\t\tnewFinalizers = append(newFinalizers, f)\n\t}\n\tif !found {\n\t\t// Finalizer not found, nothing to remove\n\t\treturn nil\n\t}\n\tpod.SetFinalizers(newFinalizers)\n\treturn d.client.Update(ctx, pod)\n}\n"
  },
  {
    "path": "exec/service/controller.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage service\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst ServiceMetaListKey = \"ServiceMetaListKey\"\n\ntype ServiceMeta struct {\n\tId          string\n\tServiceName string\n\tNamespace   string\n}\n\ntype ServiceMetaList []ServiceMeta\n\nfunc GetServiceMetaListFromContext(ctx context.Context) (ServiceMetaList, error) {\n\tval := ctx.Value(ServiceMetaListKey)\n\tif val == nil {\n\t\treturn nil, fmt.Errorf(\"less service meta in context\")\n\t}\n\treturn val.(ServiceMetaList), nil\n}\n\nfunc SetServiceMetaListToContext(ctx context.Context, list ServiceMetaList) context.Context {\n\treturn context.WithValue(ctx, ServiceMetaListKey, list)\n}\n\ntype ExpController struct {\n\tmodel.BaseExperimentController\n}\n\nfunc NewExpController(client *channel.Client) model.ExperimentController {\n\treturn &ExpController{\n\t\tmodel.BaseExperimentController{\n\t\t\tClient:            client,\n\t\t\tResourceModelSpec: NewResourceModelSpec(client),\n\t\t},\n\t}\n}\n\nfunc (*ExpController) Name() string {\n\treturn \"service\"\n}\n\nfunc (e *ExpController) Create(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response {\n\texpModel := model.ExtractExpModelFromExperimentSpec(expSpec)\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrus.WithField(\"experiment\", experimentId).Infof(\"creating service experiment\")\n\treturn e.Exec(ctx, expModel)\n}\n\nfunc (e *ExpController) Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) *spec.Response {\n\texperimentId := model.GetExperimentIdFromContext(ctx)\n\tlogrus.WithField(\"experiment\", experimentId).Infoln(\"start to destroy service experiment\")\n\texpModel := model.ExtractExpModelFromExperimentSpec(expSpec)\n\tstatuses := oldExpStatus.ResStatuses\n\tif statuses == nil {\n\t\treturn spec.ReturnSuccess(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{}))\n\t}\n\tserviceMetaList := ServiceMetaList{}\n\tfor _, status := range statuses {\n\t\tif !status.Success {\n\t\t\tcontinue\n\t\t}\n\t\tmeta := parseServiceIdentifier(status.Identifier)\n\t\tmeta.Id = status.Id\n\t\tserviceMetaList = append(serviceMetaList, meta)\n\t}\n\tctx = SetServiceMetaListToContext(ctx, serviceMetaList)\n\treturn e.Exec(ctx, expModel)\n}\n\n// parseServiceIdentifier parses identifier in format \"Namespace/ServiceName\"\nfunc parseServiceIdentifier(identifier string) ServiceMeta {\n\tparts := strings.SplitN(identifier, \"/\", 2)\n\tmeta := ServiceMeta{}\n\tif len(parts) >= 1 {\n\t\tmeta.Namespace = parts[0]\n\t}\n\tif len(parts) >= 2 {\n\t\tmeta.ServiceName = parts[1]\n\t}\n\treturn meta\n}\n"
  },
  {
    "path": "exec/service/create.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage service\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\tNamePrefixFlag      = \"name-prefix\"\n\tServiceCountFlag    = \"service-count\"\n\tPortsPerServiceFlag = \"ports-per-service\"\n)\n\ntype CreateServiceActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc NewCreateServiceActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &CreateServiceActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     NamePrefixFlag,\n\t\t\t\t\tDesc:     \"Name prefix for the created services\",\n\t\t\t\t\tNoArgs:   false,\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     ServiceCountFlag,\n\t\t\t\t\tDesc:     \"Number of services to create\",\n\t\t\t\t\tNoArgs:   false,\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:   PortsPerServiceFlag,\n\t\t\t\t\tDesc:   \"Number of ports per service, min 1, max 100, default 10\",\n\t\t\t\t\tNoArgs: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &CreateServiceActionExecutor{client: client},\n\t\t\tActionExample: `# Create 2000 services with prefix my-service in default namespace\nblade create k8s service-self create --name-prefix my-service --namespace default --service-count 2000 --kubeconfig ~/.kube/config`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*CreateServiceActionSpec) Name() string {\n\treturn \"create\"\n}\n\nfunc (*CreateServiceActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*CreateServiceActionSpec) ShortDesc() string {\n\treturn \"Create services in batch\"\n}\n\nfunc (*CreateServiceActionSpec) LongDesc() string {\n\treturn \"Create the specified number of Kubernetes services with given name prefix, each containing configurable port mappings and no selector\"\n}\n\ntype CreateServiceActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*CreateServiceActionExecutor) Name() string {\n\treturn \"create\"\n}\n\nfunc (*CreateServiceActionExecutor) SetChannel(channel spec.Channel) {\n}\n\nfunc (d *CreateServiceActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *CreateServiceActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx))\n\n\tnamePrefix := expModel.ActionFlags[NamePrefixFlag]\n\tif namePrefix == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"name-prefix is required\")\n\t\treturn spec.ResponseFailWithResult(spec.ParameterLess,\n\t\t\tv1alpha1.CreateFailExperimentStatus(\"name-prefix is required\", []v1alpha1.ResourceStatus{}),\n\t\t\tNamePrefixFlag)\n\t}\n\n\tnamespace := expModel.ActionFlags[\"namespace\"]\n\tif namespace == \"\" {\n\t\tnamespace = \"default\"\n\t}\n\n\tserviceCountStr := expModel.ActionFlags[ServiceCountFlag]\n\tif serviceCountStr == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"service-count is required\")\n\t\treturn spec.ResponseFailWithResult(spec.ParameterLess,\n\t\t\tv1alpha1.CreateFailExperimentStatus(\"service-count is required\", []v1alpha1.ResourceStatus{}),\n\t\t\tServiceCountFlag)\n\t}\n\tserviceCount, err := strconv.Atoi(serviceCountStr)\n\tif err != nil {\n\t\treturn spec.ResponseFailWithResult(spec.ParameterIllegal,\n\t\t\tv1alpha1.CreateFailExperimentStatus(fmt.Sprintf(\"service-count is invalid: %v\", err), []v1alpha1.ResourceStatus{}),\n\t\t\tServiceCountFlag, serviceCountStr, err)\n\t}\n\tif serviceCount < 1 || serviceCount > 20000 {\n\t\treturn spec.ResponseFailWithResult(spec.ParameterIllegal,\n\t\t\tv1alpha1.CreateFailExperimentStatus(fmt.Sprintf(\"service-count must be between 1 and 20000, got %d\", serviceCount), []v1alpha1.ResourceStatus{}),\n\t\t\tServiceCountFlag, strconv.Itoa(serviceCount), \"must be between 1 and 20000\")\n\t}\n\n\tportsPerService := 10\n\tif v := expModel.ActionFlags[PortsPerServiceFlag]; v != \"\" {\n\t\tportsPerService, err = strconv.Atoi(v)\n\t\tif err != nil {\n\t\t\treturn spec.ResponseFailWithResult(spec.ParameterIllegal,\n\t\t\t\tv1alpha1.CreateFailExperimentStatus(fmt.Sprintf(\"ports-per-service is invalid: %v\", err), []v1alpha1.ResourceStatus{}),\n\t\t\t\tPortsPerServiceFlag, v, err)\n\t\t}\n\t}\n\tif portsPerService < 1 || portsPerService > 100 {\n\t\treturn spec.ResponseFailWithResult(spec.ParameterIllegal,\n\t\t\tv1alpha1.CreateFailExperimentStatus(fmt.Sprintf(\"ports-per-service must be between 1 and 100, got %d\", portsPerService), []v1alpha1.ResourceStatus{}),\n\t\t\tPortsPerServiceFlag, strconv.Itoa(portsPerService), \"must be between 1 and 100\")\n\t}\n\n\tlogrusField.Infof(\"creating %d services with prefix %s in namespace %s\", serviceCount, namePrefix, namespace)\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\tfor i := 0; i < serviceCount; i++ {\n\t\tserviceName := fmt.Sprintf(\"%s-%s-%d\", namePrefix, uid, i)\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tKind:       v1alpha1.ServiceKind,\n\t\t\tIdentifier: fmt.Sprintf(\"%s/%s\", namespace, serviceName),\n\t\t}\n\n\t\tsvc := buildService(serviceName, namespace, portsPerService, uid)\n\t\tif err := d.client.Create(context.TODO(), svc); err != nil {\n\t\t\tlogrusField.Warningf(\"create service %s err, %v\", serviceName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t} else {\n\t\t\tstatus = status.CreateSuccessResourceStatus()\n\t\t\tsuccess = true\n\t\t}\n\t\tstatuses = append(statuses, status)\n\t}\n\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc (d *CreateServiceActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx))\n\n\tserviceMetaList, err := GetServiceMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(\"cannot get service meta from context\", []v1alpha1.ResourceStatus{}))\n\t}\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tsuccess := false\n\tfor _, meta := range serviceMetaList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tId:         meta.Id,\n\t\t\tKind:       v1alpha1.ServiceKind,\n\t\t\tIdentifier: fmt.Sprintf(\"%s/%s\", meta.Namespace, meta.ServiceName),\n\t\t}\n\n\t\tsvc := &v1.Service{}\n\t\tobjectKey := types.NamespacedName{Name: meta.ServiceName, Namespace: meta.Namespace}\n\t\tif err := d.client.Get(context.TODO(), objectKey, svc); err != nil {\n\t\t\tlogrusField.Warningf(\"get service %s err, %v\", meta.ServiceName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\tif _, ok := svc.Annotations[ServiceAnnotation]; !ok {\n\t\t\terrMsg := fmt.Sprintf(\"service %s/%s is not created by chaosblade (missing annotation %s), skip delete\",\n\t\t\t\tmeta.Namespace, meta.ServiceName, ServiceAnnotation)\n\t\t\tlogrusField.Warning(errMsg)\n\t\t\tstatus = status.CreateFailResourceStatus(errMsg, spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\tobjectMeta := metav1.ObjectMeta{Name: meta.ServiceName, Namespace: meta.Namespace}\n\t\tif err := d.client.Delete(context.TODO(), &v1.Service{ObjectMeta: objectMeta}); err != nil {\n\t\t\tlogrusField.Warningf(\"delete service %s err, %v\", meta.ServiceName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t} else {\n\t\t\tstatus.State = v1alpha1.DestroyedState\n\t\t\tstatus.Success = true\n\t\t\tsuccess = true\n\t\t}\n\t\tstatuses = append(statuses, status)\n\t}\n\n\tvar experimentStatus v1alpha1.ExperimentStatus\n\tif success {\n\t\texperimentStatus = v1alpha1.CreateDestroyedExperimentStatus(statuses)\n\t} else {\n\t\texperimentStatus = v1alpha1.CreateFailExperimentStatus(\"see resStatuses for details\", statuses)\n\t}\n\treturn spec.ReturnResultIgnoreCode(experimentStatus)\n}\n\nfunc buildService(name, namespace string, portsPerService int, uid string) *v1.Service {\n\tconst portBase = 8000\n\tports := make([]v1.ServicePort, 0, portsPerService)\n\tfor i := 0; i < portsPerService; i++ {\n\t\tport := int32(portBase + i)\n\t\tports = append(ports, v1.ServicePort{\n\t\t\tName:       fmt.Sprintf(\"p%d\", port),\n\t\t\tPort:       port,\n\t\t\tTargetPort: intstr.FromInt32(port),\n\t\t})\n\t}\n\n\treturn &v1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      name,\n\t\t\tNamespace: namespace,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\t\"chaosblade.io/service\": fmt.Sprintf(\"create-%s\", uid),\n\t\t\t},\n\t\t},\n\t\tSpec: v1.ServiceSpec{\n\t\t\tPorts: ports,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "exec/service/modify.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage service\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nconst (\n\tServiceNameFlag                = \"name\"\n\tExternalTrafficPolicyFlag      = \"externalTrafficPolicy\"\n\tInternalTrafficPolicyFlag      = \"internalTrafficPolicy\"\n\tServiceAnnotation              = \"chaosblade.io/service\"\n\tServiceModifyHistoryAnnotation = \"chaosblade.io/service-modify-history\"\n)\n\ntype ModifyServiceActionSpec struct {\n\tspec.BaseExpActionCommandSpec\n}\n\nfunc NewModifyServiceActionSpec(client *channel.Client) spec.ExpActionCommandSpec {\n\treturn &ModifyServiceActionSpec{\n\t\tspec.BaseExpActionCommandSpec{\n\t\t\tActionMatchers: []spec.ExpFlagSpec{},\n\t\t\tActionFlags: []spec.ExpFlagSpec{\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:     ServiceNameFlag,\n\t\t\t\t\tDesc:     \"Service name to modify\",\n\t\t\t\t\tNoArgs:   false,\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:   ExternalTrafficPolicyFlag,\n\t\t\t\t\tDesc:   \"Set externalTrafficPolicy, values: Local or Cluster\",\n\t\t\t\t\tNoArgs: false,\n\t\t\t\t},\n\t\t\t\t&spec.ExpFlag{\n\t\t\t\t\tName:   InternalTrafficPolicyFlag,\n\t\t\t\t\tDesc:   \"Set internalTrafficPolicy, values: Local or Cluster\",\n\t\t\t\t\tNoArgs: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tActionExecutor: &ModifyServiceActionExecutor{client: client},\n\t\t\tActionExample: `# Modify externalTrafficPolicy to Local\nblade create k8s service-self modify --name my-service --namespace default --externalTrafficPolicy Local --kubeconfig ~/.kube/config\n\n# Modify internalTrafficPolicy to Local\nblade create k8s service-self modify --name my-service --namespace default --internalTrafficPolicy Local --kubeconfig ~/.kube/config\n\n# Modify both policies\nblade create k8s service-self modify --name my-service --namespace default --externalTrafficPolicy Local --internalTrafficPolicy Cluster --kubeconfig ~/.kube/config`,\n\t\t\tActionCategories: []string{model.CategorySystemContainer},\n\t\t},\n\t}\n}\n\nfunc (*ModifyServiceActionSpec) Name() string {\n\treturn \"modify\"\n}\n\nfunc (*ModifyServiceActionSpec) Aliases() []string {\n\treturn []string{}\n}\n\nfunc (*ModifyServiceActionSpec) ShortDesc() string {\n\treturn \"Modify service traffic policy\"\n}\n\nfunc (*ModifyServiceActionSpec) LongDesc() string {\n\treturn \"Modify existing Kubernetes service's externalTrafficPolicy or internalTrafficPolicy\"\n}\n\ntype ModifyServiceActionExecutor struct {\n\tclient *channel.Client\n}\n\nfunc (*ModifyServiceActionExecutor) Name() string {\n\treturn \"modify\"\n}\n\nfunc (*ModifyServiceActionExecutor) SetChannel(channel spec.Channel) {\n}\n\nfunc (d *ModifyServiceActionExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tif _, ok := spec.IsDestroy(ctx); ok {\n\t\treturn d.destroy(uid, ctx, expModel)\n\t}\n\treturn d.create(uid, ctx, expModel)\n}\n\nfunc (d *ModifyServiceActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx))\n\n\tserviceName := expModel.ActionFlags[ServiceNameFlag]\n\tif serviceName == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"name is required\")\n\t\treturn spec.ResponseFailWithResult(spec.ParameterLess,\n\t\t\tv1alpha1.CreateFailExperimentStatus(\"name is required\", []v1alpha1.ResourceStatus{}),\n\t\t\tServiceNameFlag)\n\t}\n\n\tnamespace := expModel.ActionFlags[\"namespace\"]\n\tif namespace == \"\" {\n\t\tnamespace = \"default\"\n\t}\n\n\texternalPolicy := expModel.ActionFlags[ExternalTrafficPolicyFlag]\n\tinternalPolicy := expModel.ActionFlags[InternalTrafficPolicyFlag]\n\tif externalPolicy == \"\" && internalPolicy == \"\" {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), \"at least one of externalTrafficPolicy or internalTrafficPolicy is required\")\n\t\treturn spec.ResponseFailWithResult(spec.ParameterLess,\n\t\t\tv1alpha1.CreateFailExperimentStatus(\"at least one of externalTrafficPolicy or internalTrafficPolicy is required\", []v1alpha1.ResourceStatus{}),\n\t\t\tfmt.Sprintf(\"%s or %s\", ExternalTrafficPolicyFlag, InternalTrafficPolicyFlag))\n\t}\n\n\tif externalPolicy != \"\" && !isValidPolicy(externalPolicy) {\n\t\treturn spec.ResponseFailWithResult(spec.ParameterIllegal,\n\t\t\tv1alpha1.CreateFailExperimentStatus(fmt.Sprintf(\"invalid externalTrafficPolicy: %s, must be Local or Cluster\", externalPolicy), []v1alpha1.ResourceStatus{}),\n\t\t\tExternalTrafficPolicyFlag, externalPolicy, \"must be Local or Cluster\")\n\t}\n\tif internalPolicy != \"\" && !isValidPolicy(internalPolicy) {\n\t\treturn spec.ResponseFailWithResult(spec.ParameterIllegal,\n\t\t\tv1alpha1.CreateFailExperimentStatus(fmt.Sprintf(\"invalid internalTrafficPolicy: %s, must be Local or Cluster\", internalPolicy), []v1alpha1.ResourceStatus{}),\n\t\t\tInternalTrafficPolicyFlag, internalPolicy, \"must be Local or Cluster\")\n\t}\n\n\tlogrusField.Infof(\"modifying service %s/%s, externalTrafficPolicy=%s, internalTrafficPolicy=%s\",\n\t\tnamespace, serviceName, externalPolicy, internalPolicy)\n\n\tstatus := v1alpha1.ResourceStatus{\n\t\tKind:       v1alpha1.ServiceKind,\n\t\tIdentifier: fmt.Sprintf(\"%s/%s\", namespace, serviceName),\n\t}\n\n\tsvc := &v1.Service{}\n\tobjectKey := types.NamespacedName{Name: serviceName, Namespace: namespace}\n\tif err := d.client.Get(context.TODO(), objectKey, svc); err != nil {\n\t\tlogrusField.Errorf(\"get service %s err, %v\", serviceName, err)\n\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\treturn spec.ReturnResultIgnoreCode(\n\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{status}),\n\t\t)\n\t}\n\n\tif existing, ok := svc.Annotations[ServiceAnnotation]; ok && existing != \"\" {\n\t\terr := fmt.Errorf(\"service %s/%s already has chaos experiment injected (annotation %s=%s), modifying service configuration is not allowed\",\n\t\t\tnamespace, serviceName, ServiceAnnotation, existing)\n\t\tlogrusField.Warningf(\"%v\", err)\n\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\treturn spec.ReturnResultIgnoreCode(\n\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{status}),\n\t\t)\n\t}\n\n\tif svc.Annotations == nil {\n\t\tsvc.Annotations = make(map[string]string)\n\t}\n\n\thistory := make(map[string]string)\n\n\tif externalPolicy != \"\" {\n\t\thistory[ExternalTrafficPolicyFlag] = string(svc.Spec.ExternalTrafficPolicy)\n\t\tswitch externalPolicy {\n\t\tcase string(v1.ServiceExternalTrafficPolicyTypeLocal):\n\t\t\tsvc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal\n\t\tcase string(v1.ServiceExternalTrafficPolicyTypeCluster):\n\t\t\tsvc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeCluster\n\t\tdefault:\n\t\t\terr := fmt.Errorf(\"invalid externalTrafficPolicy %q, must be %q or %q\",\n\t\t\t\texternalPolicy,\n\t\t\t\tv1.ServiceExternalTrafficPolicyTypeLocal,\n\t\t\t\tv1.ServiceExternalTrafficPolicyTypeCluster)\n\t\t\tlogrusField.Errorf(\"modify service %s err, %v\", serviceName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t\treturn spec.ReturnResultIgnoreCode(\n\t\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{status}),\n\t\t\t)\n\t\t}\n\t}\n\n\tif internalPolicy != \"\" {\n\t\toriginalInternal := \"\"\n\t\tif svc.Spec.InternalTrafficPolicy != nil {\n\t\t\toriginalInternal = string(*svc.Spec.InternalTrafficPolicy)\n\t\t}\n\t\thistory[InternalTrafficPolicyFlag] = originalInternal\n\t\tpolicy := v1.ServiceInternalTrafficPolicyType(internalPolicy)\n\t\tsvc.Spec.InternalTrafficPolicy = &policy\n\t}\n\n\thistoryBytes, err := json.Marshal(history)\n\tif err != nil {\n\t\tlogrusField.Errorf(\"marshal modify history for service %s err, %v\", serviceName, err)\n\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\treturn spec.ReturnResultIgnoreCode(\n\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{status}),\n\t\t)\n\t}\n\tsvc.Annotations[ServiceAnnotation] = fmt.Sprintf(\"modify-%s\", uid)\n\tsvc.Annotations[ServiceModifyHistoryAnnotation] = string(historyBytes)\n\n\tif err := d.client.Update(context.TODO(), svc); err != nil {\n\t\tlogrusField.Errorf(\"update service %s err, %v\", serviceName, err)\n\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\treturn spec.ReturnResultIgnoreCode(\n\t\t\tv1alpha1.CreateFailExperimentStatus(err.Error(), []v1alpha1.ResourceStatus{status}),\n\t\t)\n\t}\n\n\tstatus = status.CreateSuccessResourceStatus()\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{status}))\n}\n\nfunc (d *ModifyServiceActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {\n\tlogrusField := logrus.WithField(\"experiment\", model.GetExperimentIdFromContext(ctx))\n\n\tserviceMetaList, err := GetServiceMetaListFromContext(ctx)\n\tif err != nil {\n\t\tutil.Errorf(uid, util.GetRunFuncName(), err.Error())\n\t\treturn spec.ResponseFailWithResult(spec.ContainerInContextNotFound,\n\t\t\tv1alpha1.CreateFailExperimentStatus(\"cannot get service meta from context\", []v1alpha1.ResourceStatus{}))\n\t}\n\n\tstatuses := make([]v1alpha1.ResourceStatus, 0)\n\tfor _, meta := range serviceMetaList {\n\t\tstatus := v1alpha1.ResourceStatus{\n\t\t\tId:         meta.Id,\n\t\t\tKind:       v1alpha1.ServiceKind,\n\t\t\tIdentifier: fmt.Sprintf(\"%s/%s\", meta.Namespace, meta.ServiceName),\n\t\t}\n\n\t\tsvc := &v1.Service{}\n\t\tobjectKey := types.NamespacedName{Name: meta.ServiceName, Namespace: meta.Namespace}\n\t\tif err := d.client.Get(context.TODO(), objectKey, svc); err != nil {\n\t\t\tlogrusField.Errorf(\"get service %s for restoring err, %v\", meta.ServiceName, err)\n\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t\tstatuses = append(statuses, status)\n\t\t\tcontinue\n\t\t}\n\n\t\texpected := fmt.Sprintf(\"modify-%s\", uid)\n\t\tif existing, ok := svc.Annotations[ServiceAnnotation]; ok && existing == expected {\n\t\t\tif historyStr, hasHistory := svc.Annotations[ServiceModifyHistoryAnnotation]; hasHistory {\n\t\t\t\thistory := make(map[string]string)\n\t\t\t\tif err := json.Unmarshal([]byte(historyStr), &history); err != nil {\n\t\t\t\t\tlogrusField.Errorf(\"unmarshal modify history for service %s err, %v\", meta.ServiceName, err)\n\t\t\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif original, exists := history[ExternalTrafficPolicyFlag]; exists {\n\t\t\t\t\tsvc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyType(original)\n\t\t\t\t}\n\t\t\t\tif original, exists := history[InternalTrafficPolicyFlag]; exists {\n\t\t\t\t\tif original == \"\" {\n\t\t\t\t\t\tsvc.Spec.InternalTrafficPolicy = nil\n\t\t\t\t\t} else {\n\t\t\t\t\t\trestored := v1.ServiceInternalTrafficPolicyType(original)\n\t\t\t\t\t\tsvc.Spec.InternalTrafficPolicy = &restored\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete(svc.Annotations, ServiceModifyHistoryAnnotation)\n\t\t\t}\n\t\t\tdelete(svc.Annotations, ServiceAnnotation)\n\t\t\tif err := d.client.Update(context.TODO(), svc); err != nil {\n\t\t\t\tlogrusField.Errorf(\"restore service %s err, %v\", meta.ServiceName, err)\n\t\t\t\tstatus = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code)\n\t\t\t\tstatuses = append(statuses, status)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tstatus.State = v1alpha1.DestroyedState\n\t\tstatus.Success = true\n\t\tstatuses = append(statuses, status)\n\t}\n\treturn spec.ReturnResultIgnoreCode(v1alpha1.CreateDestroyedExperimentStatus(statuses))\n}\n\nfunc isValidPolicy(policy string) bool {\n\treturn policy == \"Local\" || policy == \"Cluster\"\n}\n"
  },
  {
    "path": "exec/service/service.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage service\n\nimport (\n\t\"github.com/chaosblade-io/chaosblade-spec-go/spec\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n)\n\ntype ResourceModelSpec struct {\n\tmodel.BaseResourceExpModelSpec\n}\n\nfunc NewResourceModelSpec(client *channel.Client) model.ResourceExpModelSpec {\n\tmodelSpec := &ResourceModelSpec{\n\t\tmodel.NewBaseResourceExpModelSpec(\"service\", client),\n\t}\n\texpModels := []spec.ExpModelCommandSpec{\n\t\tNewSelfExpModelCommandSpec(client),\n\t}\n\tspec.AddFlagsToModelSpec(getResourceFlags, expModels...)\n\tmodelSpec.RegisterExpModels(expModels...)\n\treturn modelSpec\n}\n\nfunc getResourceFlags() []spec.ExpFlagSpec {\n\treturn []spec.ExpFlagSpec{\n\t\t&spec.ExpFlag{\n\t\t\tName:     \"namespace\",\n\t\t\tDesc:     \"Namespace for the services, default is default\",\n\t\t\tNoArgs:   false,\n\t\t\tRequired: false,\n\t\t},\n\t}\n}\n\ntype SelfExpModelCommandSpec struct {\n\tspec.BaseExpModelCommandSpec\n}\n\nfunc NewSelfExpModelCommandSpec(client *channel.Client) spec.ExpModelCommandSpec {\n\treturn &SelfExpModelCommandSpec{\n\t\tspec.BaseExpModelCommandSpec{\n\t\t\tExpFlags: []spec.ExpFlagSpec{},\n\t\t\tExpActions: []spec.ExpActionCommandSpec{\n\t\t\t\tNewCreateServiceActionSpec(client),\n\t\t\t\tNewModifyServiceActionSpec(client),\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (*SelfExpModelCommandSpec) Name() string {\n\treturn \"self\"\n}\n\nfunc (*SelfExpModelCommandSpec) ShortDesc() string {\n\treturn \"Service experiments\"\n}\n\nfunc (*SelfExpModelCommandSpec) LongDesc() string {\n\treturn \"Service experiments, such as creating services in batch or modifying service traffic policy\"\n}\n\nfunc (*SelfExpModelCommandSpec) Example() string {\n\treturn \"blade create k8s service-self create --name-prefix my-service --namespace default --count 1000 --kubeconfig ~/.kube/config\"\n}\n"
  },
  {
    "path": "go.mod",
    "content": "// Copyright 2025 The ChaosBlade Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nmodule github.com/chaosblade-io/chaosblade-operator\n\ngo 1.25\n\nrequire (\n\tgithub.com/chaosblade-io/chaosblade-exec-cri v1.8.0\n\tgithub.com/chaosblade-io/chaosblade-exec-os v1.8.1-0.20260422111537-3efa2ec759a6\n\tgithub.com/chaosblade-io/chaosblade-spec-go v1.8.0\n\tgithub.com/ethercflow/hookfs v0.3.0\n\tgithub.com/hanwen/go-fuse v1.0.0\n\tgithub.com/operator-framework/operator-sdk v0.17.0\n\tgithub.com/sirupsen/logrus v1.9.3\n\tgithub.com/spf13/pflag v1.0.5\n\tk8s.io/api v0.31.0\n\tk8s.io/apimachinery v0.31.0\n\tk8s.io/client-go v12.0.0+incompatible\n\tsigs.k8s.io/controller-runtime v0.19.4\n)\n\nrequire (\n\tcyphar.com/go-pathrs v0.2.1 // indirect\n\tgithub.com/Microsoft/go-winio v0.4.17 // indirect\n\tgithub.com/Microsoft/hcsshim v0.8.21 // indirect\n\tgithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/cilium/ebpf v0.16.0 // indirect\n\tgithub.com/containerd/cgroups v1.0.2-0.20210605143700-23b51209bf7b // indirect\n\tgithub.com/containerd/containerd v1.5.6 // indirect\n\tgithub.com/containerd/continuity v0.1.0 // indirect\n\tgithub.com/containerd/fifo v1.0.0 // indirect\n\tgithub.com/containerd/ttrpc v1.0.2 // indirect\n\tgithub.com/containerd/typeurl v1.0.2 // indirect\n\tgithub.com/coreos/go-systemd/v22 v22.6.0 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.6.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dimchansky/utfbom v1.1.1 // indirect\n\tgithub.com/docker/distribution v2.7.1+incompatible // indirect\n\tgithub.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce // indirect\n\tgithub.com/docker/go-connections v0.4.0 // indirect\n\tgithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.11.0 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.7.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/zapr v1.3.0 // indirect\n\tgithub.com/go-ole/go-ole v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.6 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.22.4 // indirect\n\tgithub.com/godbus/dbus/v5 v5.1.0 // indirect\n\tgithub.com/gogo/googleapis v1.4.0 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/goodhosts/hostsfile v0.1.6 // indirect\n\tgithub.com/google/gnostic-models v0.6.8 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/gorilla/websocket v1.5.0 // indirect\n\tgithub.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect\n\tgithub.com/imdario/mergo v0.3.12 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.11.13 // indirect\n\tgithub.com/magefile/mage v1.15.0 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/moby/locker v1.0.1 // indirect\n\tgithub.com/moby/spdystream v0.5.1 // indirect\n\tgithub.com/moby/sys/mountinfo v0.7.1 // indirect\n\tgithub.com/moby/sys/user v0.3.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.0.1 // indirect\n\tgithub.com/opencontainers/runc v1.2.8 // indirect\n\tgithub.com/opencontainers/runtime-spec v1.2.1 // indirect\n\tgithub.com/opencontainers/selinux v1.13.0 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/prometheus/client_golang v1.19.1 // indirect\n\tgithub.com/prometheus/client_model v0.6.1 // indirect\n\tgithub.com/prometheus/common v0.55.0 // indirect\n\tgithub.com/prometheus/procfs v0.15.1 // indirect\n\tgithub.com/shirou/gopsutil v3.21.11+incompatible // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.15 // indirect\n\tgithub.com/tklauser/numcpus v0.10.0 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.4 // indirect\n\tgo.opencensus.io v0.22.3 // indirect\n\tgo.uber.org/automaxprocs v1.6.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.26.0 // indirect\n\tgolang.org/x/crypto v0.46.0 // indirect\n\tgolang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect\n\tgolang.org/x/net v0.48.0 // indirect\n\tgolang.org/x/oauth2 v0.34.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.39.0 // indirect\n\tgolang.org/x/term v0.38.0 // indirect\n\tgolang.org/x/text v0.32.0 // indirect\n\tgolang.org/x/time v0.3.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect\n\tgoogle.golang.org/grpc v1.79.3 // indirect\n\tgoogle.golang.org/protobuf v1.36.10 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/klog v1.0.0 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect\n\tk8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect\n\tsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect\n\tsigs.k8s.io/yaml v1.4.0 // indirect\n)\n\nreplace (\n\tk8s.io/client-go => k8s.io/client-go v0.31.0\n\tk8s.io/client-go/kubernetes/scheme => k8s.io/client-go/kubernetes/scheme v0.31.0\n)\n"
  },
  {
    "path": "go.sum",
    "content": "bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=\nbou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.3.0/go.mod h1:9IAwXhoyBJ7z9LcAwkj0/7NnPzYaPeZxxVp3zm+5IqA=\ncontrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=\ncyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=\ncyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=\ngithub.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=\ngithub.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/azure-sdk-for-go v36.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=\ngithub.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=\ngithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=\ngithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=\ngithub.com/Azure/go-autorest/autorest v0.9.3-0.20191028180845-3492b2aff503/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=\ngithub.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=\ngithub.com/Azure/go-autorest/autorest/adal v0.8.1-0.20191028180845-3492b2aff503/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=\ngithub.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=\ngithub.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=\ngithub.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=\ngithub.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=\ngithub.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=\ngithub.com/Azure/go-autorest/autorest/to v0.3.1-0.20191028180845-3492b2aff503/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=\ngithub.com/Azure/go-autorest/autorest/validation v0.2.1-0.20191028180845-3492b2aff503/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=\ngithub.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=\ngithub.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=\ngithub.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=\ngithub.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=\ngithub.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=\ngithub.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU=\ngithub.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=\ngithub.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=\ngithub.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=\ngithub.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=\ngithub.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=\ngithub.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=\ngithub.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=\ngithub.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=\ngithub.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w=\ngithub.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=\ngithub.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=\ngithub.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=\ngithub.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=\ngithub.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8=\ngithub.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=\ngithub.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00=\ngithub.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600=\ngithub.com/Microsoft/hcsshim v0.8.21 h1:btRfUDThBE5IKcvI8O8jOiIkujUsAMBSRsYDYmEi6oM=\ngithub.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4=\ngithub.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU=\ngithub.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=\ngithub.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=\ngithub.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=\ngithub.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=\ngithub.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=\ngithub.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=\ngithub.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=\ngithub.com/aliyun/aliyun-oss-go-sdk v2.0.4+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=\ngithub.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=\ngithub.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=\ngithub.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=\ngithub.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=\ngithub.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=\ngithub.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=\ngithub.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=\ngithub.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=\ngithub.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=\ngithub.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=\ngithub.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=\ngithub.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=\ngithub.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=\ngithub.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=\ngithub.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=\ngithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=\ngithub.com/brancz/gojsontoyaml v0.0.0-20190425155809-e8bd32d46b3d/go.mod h1:IyUJYN1gvWjtLF5ZuygmxbnsAyP3aJS6cHzIuZY50B0=\ngithub.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=\ngithub.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=\ngithub.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=\ngithub.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=\ngithub.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=\ngithub.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=\ngithub.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v0.0.0-20181017004759-096ff4a8a059/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=\ngithub.com/chaosblade-io/chaosblade-exec-cri v1.8.0 h1:mLv5fp0aEzZ5DFHZ1bZIyNJCVagh1OSm8Qqi0NQEApU=\ngithub.com/chaosblade-io/chaosblade-exec-cri v1.8.0/go.mod h1:o+W6ELOzrLodXclewVKq8TIQdmdMoNaWw34Va4pyX9s=\ngithub.com/chaosblade-io/chaosblade-exec-os v1.8.1-0.20260422111537-3efa2ec759a6 h1:KhSS5jhrTpxePjRM39JllNcIr9aqUoKu+/1uhcT8gAQ=\ngithub.com/chaosblade-io/chaosblade-exec-os v1.8.1-0.20260422111537-3efa2ec759a6/go.mod h1:k6QrG3w8mEwvk/6CCGC+K1S+K8u0s1xfHoa/dOGdADo=\ngithub.com/chaosblade-io/chaosblade-spec-go v1.8.0 h1:UtwBZCXUJMAtqlmUrfTwmzolxfdnY02Itz1o84Ua0b4=\ngithub.com/chaosblade-io/chaosblade-spec-go v1.8.0/go.mod h1:xxkKn6Ve25MmqX1vWp1/swl4g+o0rJP1Rkp2ph3DzT8=\ngithub.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=\ngithub.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=\ngithub.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=\ngithub.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=\ngithub.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=\ngithub.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=\ngithub.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=\ngithub.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=\ngithub.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=\ngithub.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=\ngithub.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=\ngithub.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=\ngithub.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=\ngithub.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=\ngithub.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=\ngithub.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=\ngithub.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=\ngithub.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=\ngithub.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=\ngithub.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=\ngithub.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=\ngithub.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E=\ngithub.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=\ngithub.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss=\ngithub.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI=\ngithub.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=\ngithub.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=\ngithub.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=\ngithub.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=\ngithub.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=\ngithub.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=\ngithub.com/containerd/cgroups v1.0.2-0.20210605143700-23b51209bf7b h1:mrRq0rkLJnQOfalr7EwNn1ULsMoyGvD+8kN+hxeNRms=\ngithub.com/containerd/cgroups v1.0.2-0.20210605143700-23b51209bf7b/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=\ngithub.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=\ngithub.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=\ngithub.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=\ngithub.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=\ngithub.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=\ngithub.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=\ngithub.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ=\ngithub.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU=\ngithub.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI=\ngithub.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=\ngithub.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=\ngithub.com/containerd/containerd v1.5.6 h1:yi692sMr9kyyaps9dyodk3vVOTNM9fIPvlZp4UnyT4U=\ngithub.com/containerd/containerd v1.5.6/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=\ngithub.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=\ngithub.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=\ngithub.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=\ngithub.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=\ngithub.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo=\ngithub.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=\ngithub.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=\ngithub.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8=\ngithub.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=\ngithub.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=\ngithub.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=\ngithub.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=\ngithub.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=\ngithub.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=\ngithub.com/containerd/fifo v1.0.0 h1:6PirWBr9/L7GDamKr+XM0IeUFXu5mf3M/BPpH9gaLBU=\ngithub.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4=\ngithub.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU=\ngithub.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk=\ngithub.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=\ngithub.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=\ngithub.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=\ngithub.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=\ngithub.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok=\ngithub.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0=\ngithub.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA=\ngithub.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow=\ngithub.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms=\ngithub.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=\ngithub.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=\ngithub.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=\ngithub.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=\ngithub.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=\ngithub.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=\ngithub.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=\ngithub.com/containerd/ttrpc v1.0.2 h1:2/O3oTZN36q2xRolk0a2WWGgh7/Vf/liElg5hFYLX9U=\ngithub.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=\ngithub.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=\ngithub.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=\ngithub.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=\ngithub.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY=\ngithub.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s=\ngithub.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw=\ngithub.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y=\ngithub.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=\ngithub.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=\ngithub.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY=\ngithub.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=\ngithub.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=\ngithub.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=\ngithub.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM=\ngithub.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=\ngithub.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc=\ngithub.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=\ngithub.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=\ngithub.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=\ngithub.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=\ngithub.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=\ngithub.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=\ngithub.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=\ngithub.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=\ngithub.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=\ngithub.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=\ngithub.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=\ngithub.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=\ngithub.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=\ngithub.com/coreos/prometheus-operator v0.38.0/go.mod h1:xZC7/TgeC0/mBaJk+1H9dbHaiEvLYHgX6Mi1h40UPh8=\ngithub.com/corpix/uarand v0.0.0-20170723150923-031be390f409 h1:9A+mfQmwzZ6KwUXPc8nHxFtKgn9VIvO3gXAOspIcE3s=\ngithub.com/corpix/uarand v0.0.0-20170723150923-031be390f409/go.mod h1:JSm890tOkDN+M1jqN8pUGDKnzJrsVbJwSMHBY4zwz7M=\ngithub.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=\ngithub.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=\ngithub.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=\ngithub.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=\ngithub.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg=\ngithub.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=\ngithub.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4=\ngithub.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A=\ngithub.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=\ngithub.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE=\ngithub.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=\ngithub.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=\ngithub.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8=\ngithub.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=\ngithub.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=\ngithub.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=\ngithub.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=\ngithub.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=\ngithub.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As=\ngithub.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=\ngithub.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=\ngithub.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=\ngithub.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc=\ngithub.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=\ngithub.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=\ngithub.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=\ngithub.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=\ngithub.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=\ngithub.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=\ngithub.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce h1:KXS1Jg+ddGcWA8e1N7cupxaHHZhit5rB9tfDU+mfjyY=\ngithub.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=\ngithub.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=\ngithub.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=\ngithub.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=\ngithub.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=\ngithub.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=\ngithub.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=\ngithub.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=\ngithub.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=\ngithub.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=\ngithub.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=\ngithub.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=\ngithub.com/elastic/go-sysinfo v1.0.1/go.mod h1:O/D5m1VpYLwGjCYzEt63g3Z1uO3jXfwyzzjiW90t8cY=\ngithub.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=\ngithub.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=\ngithub.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=\ngithub.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=\ngithub.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=\ngithub.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/ethercflow/hookfs v0.3.0 h1:PfA/56Q22Dc7LtAM/6dDnlom4GAo4OpaFc3K2Xo7ZvM=\ngithub.com/ethercflow/hookfs v0.3.0/go.mod h1:c4t7EbwfiU+xsSjeZrlup9p5boB/PT3HD9sTq57K/4M=\ngithub.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=\ngithub.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=\ngithub.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=\ngithub.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=\ngithub.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=\ngithub.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=\ngithub.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=\ngithub.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=\ngithub.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=\ngithub.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=\ngithub.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=\ngithub.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=\ngithub.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=\ngithub.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=\ngithub.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=\ngithub.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=\ngithub.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=\ngithub.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=\ngithub.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=\ngithub.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=\ngithub.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=\ngithub.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=\ngithub.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=\ngithub.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=\ngithub.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=\ngithub.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=\ngithub.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=\ngithub.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=\ngithub.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=\ngithub.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=\ngithub.com/go-openapi/jsonpointer v0.17.2/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=\ngithub.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=\ngithub.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=\ngithub.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=\ngithub.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=\ngithub.com/go-openapi/jsonreference v0.17.2/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=\ngithub.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=\ngithub.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=\ngithub.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=\ngithub.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=\ngithub.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=\ngithub.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=\ngithub.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=\ngithub.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=\ngithub.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=\ngithub.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=\ngithub.com/go-openapi/runtime v0.18.0/go.mod h1:uI6pHuxWYTy94zZxgcwJkUWa9wbIlhteGfloI10GD4U=\ngithub.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=\ngithub.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=\ngithub.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=\ngithub.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=\ngithub.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=\ngithub.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=\ngithub.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=\ngithub.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=\ngithub.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=\ngithub.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=\ngithub.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=\ngithub.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=\ngithub.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=\ngithub.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=\ngithub.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=\ngithub.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=\ngithub.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=\ngithub.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=\ngithub.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=\ngithub.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=\ngithub.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=\ngithub.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=\ngithub.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=\ngithub.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=\ngithub.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=\ngithub.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=\ngithub.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=\ngithub.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=\ngithub.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=\ngithub.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=\ngithub.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=\ngithub.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=\ngithub.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=\ngithub.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=\ngithub.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=\ngithub.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=\ngithub.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=\ngithub.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=\ngithub.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=\ngithub.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=\ngithub.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=\ngithub.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=\ngithub.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=\ngithub.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI=\ngithub.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=\ngithub.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=\ngithub.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-migrate/migrate/v4 v4.6.2/go.mod h1:JYi6reN3+Z734VZ0akNuyOJNcrg45ZL7LDBMW3WGJL0=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=\ngithub.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8=\ngithub.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=\ngithub.com/goodhosts/hostsfile v0.1.6 h1:aK6DxpNV6pZ1NbdvNE2vYBMTnvIJF5O2J/8ZOlp2eMY=\ngithub.com/goodhosts/hostsfile v0.1.6/go.mod h1:bkCocEIf3Ca0hcBustUZoWYhOgKUaIK+47m8fBjoBx8=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=\ngithub.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=\ngithub.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=\ngithub.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=\ngithub.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=\ngithub.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=\ngithub.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=\ngithub.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=\ngithub.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=\ngithub.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=\ngithub.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=\ngithub.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=\ngithub.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=\ngithub.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=\ngithub.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=\ngithub.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=\ngithub.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=\ngithub.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=\ngithub.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=\ngithub.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=\ngithub.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=\ngithub.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db/go.mod h1:uBKkC2RbarFsvS5jMJHpVhTLvGlGQj9JJwkaePE3FWI=\ngithub.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=\ngithub.com/hanwen/go-fuse v0.0.0-20190111173210-425e8d5301f6/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=\ngithub.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=\ngithub.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=\ngithub.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=\ngithub.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=\ngithub.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=\ngithub.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=\ngithub.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=\ngithub.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=\ngithub.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=\ngithub.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=\ngithub.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=\ngithub.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=\ngithub.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=\ngithub.com/hashicorp/serf v0.8.5/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k=\ngithub.com/helm/helm-2to3 v0.5.1/go.mod h1:AXFpQX2cSQpss+47ROPEeu7Sm4+CRJ1jKWCEQdHP3/c=\ngithub.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=\ngithub.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=\ngithub.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=\ngithub.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2 h1:qU3v73XG4QAqCPHA4HOpfC1EfUvtLIDvQK4mNQ0LvgI=\ngithub.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2/go.mod h1:dQ6TM/OGAe+cMws81eTe4Btv1dKxfPZ2CX+YaAFAPN4=\ngithub.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=\ngithub.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=\ngithub.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/influxdata/influxdb v1.7.7/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=\ngithub.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=\ngithub.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=\ngithub.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=\ngithub.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=\ngithub.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=\ngithub.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=\ngithub.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=\ngithub.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=\ngithub.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=\ngithub.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=\ngithub.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=\ngithub.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jsonnet-bundler/jsonnet-bundler v0.2.0/go.mod h1:/by7P/OoohkI3q4CgSFqcoFsVY+IaNbzOVDknEsKDeU=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=\ngithub.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=\ngithub.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=\ngithub.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=\ngithub.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE=\ngithub.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=\ngithub.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=\ngithub.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=\ngithub.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=\ngithub.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=\ngithub.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=\ngithub.com/lightstep/lightstep-tracer-go v0.18.0/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=\ngithub.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=\ngithub.com/lovoo/gcloud-opentracing v0.3.0/go.mod h1:ZFqk2y38kMDDikZPAK7ynTTGuyt17nSPdS3K5e+ZTBY=\ngithub.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=\ngithub.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=\ngithub.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=\ngithub.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/maorfr/helm-plugin-utils v0.0.0-20200216074820-36d2fcf6ae86/go.mod h1:p3gwmRSFqbWw6plBpR0sKl3n3vpu8kX70gvCJKMvvCA=\ngithub.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=\ngithub.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=\ngithub.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=\ngithub.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=\ngithub.com/mattn/go-ieproxy v0.0.0-20191113090002-7c0f6868bffe/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=\ngithub.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=\ngithub.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=\ngithub.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=\ngithub.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=\ngithub.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=\ngithub.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=\ngithub.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=\ngithub.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=\ngithub.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=\ngithub.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RRf2RRxieJU=\ngithub.com/mikefarah/yq/v2 v2.4.1/go.mod h1:i8SYf1XdgUvY2OFwSqGAtWOOgimD2McJ6iutoxRm4k0=\ngithub.com/minio/minio-go/v6 v6.0.49/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=\ngithub.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=\ngithub.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=\ngithub.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=\ngithub.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=\ngithub.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=\ngithub.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=\ngithub.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=\ngithub.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=\ngithub.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=\ngithub.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=\ngithub.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=\ngithub.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=\ngithub.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=\ngithub.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=\ngithub.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=\ngithub.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=\ngithub.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=\ngithub.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=\ngithub.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=\ngithub.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=\ngithub.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=\ngithub.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=\ngithub.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\ngithub.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=\ngithub.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\ngithub.com/mozillazg/go-cos v0.13.0/go.mod h1:Zp6DvvXn0RUOXGJ2chmWt2bLEqRAnJnS3DnAZsJsoaE=\ngithub.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=\ngithub.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=\ngithub.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\ngithub.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=\ngithub.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=\ngithub.com/oklog/ulid v0.0.0-20170117200651-66bb6560562f/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=\ngithub.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=\ngithub.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=\ngithub.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=\ngithub.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=\ngithub.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=\ngithub.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=\ngithub.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=\ngithub.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=\ngithub.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=\ngithub.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=\ngithub.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=\ngithub.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=\ngithub.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=\ngithub.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=\ngithub.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=\ngithub.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=\ngithub.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=\ngithub.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=\ngithub.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=\ngithub.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=\ngithub.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=\ngithub.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=\ngithub.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=\ngithub.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=\ngithub.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=\ngithub.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=\ngithub.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=\ngithub.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=\ngithub.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=\ngithub.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=\ngithub.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=\ngithub.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=\ngithub.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=\ngithub.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=\ngithub.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=\ngithub.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=\ngithub.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=\ngithub.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=\ngithub.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=\ngithub.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=\ngithub.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=\ngithub.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=\ngithub.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=\ngithub.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=\ngithub.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=\ngithub.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=\ngithub.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=\ngithub.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=\ngithub.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=\ngithub.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0=\ngithub.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=\ngithub.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=\ngithub.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=\ngithub.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=\ngithub.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=\ngithub.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=\ngithub.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=\ngithub.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=\ngithub.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84=\ngithub.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s=\ngithub.com/openshift/api v0.0.0-20200205133042-34f0ec8dab87/go.mod h1:fT6U/JfG8uZzemTRwZA2kBDJP5nWz7v05UHnty/D+pk=\ngithub.com/openshift/client-go v0.0.0-20190923180330-3b6373338c9b/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk=\ngithub.com/openshift/origin v0.0.0-20160503220234-8f127d736703/go.mod h1:0Rox5r9C8aQn6j1oAOQ0c1uC86mYbUFObzjBRvUKHII=\ngithub.com/openshift/prom-label-proxy v0.1.1-0.20191016113035-b8153a7f39f1/go.mod h1:p5MuxzsYP1JPsNGwtjtcgRHHlGziCJJfztff91nNixw=\ngithub.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w=\ngithub.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=\ngithub.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=\ngithub.com/operator-framework/api v0.1.1/go.mod h1:yzNYR7qyJqRGOOp+bT6Z/iYSbSPNxeh3Si93Gx/3OBY=\ngithub.com/operator-framework/operator-lifecycle-manager v0.0.0-20200321030439-57b580e57e88/go.mod h1:7Ut8p9jJ8C6RZyyhZfZypmlibCIJwK5Wcc+WZDgLkOA=\ngithub.com/operator-framework/operator-registry v1.5.3/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY=\ngithub.com/operator-framework/operator-registry v1.6.1/go.mod h1:sx4wWMiZtYhlUiaKscg3QQUPPM/c1bkrAs4n4KipDb4=\ngithub.com/operator-framework/operator-registry v1.6.2-0.20200330184612-11867930adb5/go.mod h1:SHff373z8asEkPo6aWpN0qId4Y/feQTjZxRF8PRhti8=\ngithub.com/operator-framework/operator-sdk v0.17.0 h1:+TTrGjXa+lm7g7Cm0UtFcgOjnw1x9/lBorydpsIIhOY=\ngithub.com/operator-framework/operator-sdk v0.17.0/go.mod h1:wmYi08aoUmtgfoUamURmssI4dkdFGNtSI1Egj+ZfBnk=\ngithub.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=\ngithub.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc=\ngithub.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=\ngithub.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=\ngithub.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZeFXocWuvtcS0XSFLcf2XUSDHkq9t1jU4=\ngithub.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=\ngithub.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M=\ngithub.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=\ngithub.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=\ngithub.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=\ngithub.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=\ngithub.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=\ngithub.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=\ngithub.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE=\ngithub.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2aVtrORKwKVCQ+nbrg=\ngithub.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=\ngithub.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=\ngithub.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=\ngithub.com/prometheus/client_golang v1.2.0/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=\ngithub.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=\ngithub.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=\ngithub.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=\ngithub.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=\ngithub.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=\ngithub.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=\ngithub.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=\ngithub.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=\ngithub.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=\ngithub.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=\ngithub.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=\ngithub.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=\ngithub.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=\ngithub.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=\ngithub.com/prometheus/prometheus v0.0.0-20180315085919-58e2a31db8de/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s=\ngithub.com/prometheus/prometheus v1.8.2-0.20200110114423-1e64d757f711/go.mod h1:7U90zPoLkWjEIQcy/rweQla82OCTUzxVHE51G3OhJbI=\ngithub.com/prometheus/prometheus v2.3.2+incompatible/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s=\ngithub.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=\ngithub.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=\ngithub.com/robfig/cron v0.0.0-20170526150127-736158dc09e1/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=\ngithub.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=\ngithub.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=\ngithub.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=\ngithub.com/rubenv/sql-migrate v0.0.0-20191025130928-9355dd04f4b3/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=\ngithub.com/samuel/go-zookeeper v0.0.0-20190810000440-0ceca61e4d75/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=\ngithub.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=\ngithub.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=\ngithub.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=\ngithub.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=\ngithub.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=\ngithub.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=\ngithub.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=\ngithub.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/vfsgen v0.0.0-20180825020608-02ddb050ef6b/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=\ngithub.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=\ngithub.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=\ngithub.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=\ngithub.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=\ngithub.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=\ngithub.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=\ngithub.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=\ngithub.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=\ngithub.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=\ngithub.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=\ngithub.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=\ngithub.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=\ngithub.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=\ngithub.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=\ngithub.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=\ngithub.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=\ngithub.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=\ngithub.com/thanos-io/thanos v0.11.0/go.mod h1:N/Yes7J68KqvmY+xM6J5CJqEvWIvKSR5sqGtmuD6wDc=\ngithub.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=\ngithub.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=\ngithub.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=\ngithub.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=\ngithub.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=\ngithub.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=\ngithub.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=\ngithub.com/uber/jaeger-client-go v2.20.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=\ngithub.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=\ngithub.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=\ngithub.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=\ngithub.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=\ngithub.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=\ngithub.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=\ngithub.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=\ngithub.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=\ngithub.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=\ngithub.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=\ngithub.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=\ngithub.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=\ngithub.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=\ngithub.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=\ngithub.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=\ngithub.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=\ngithub.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=\ngithub.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=\ngithub.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=\ngithub.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=\ngithub.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=\ngithub.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngithub.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=\ngithub.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=\ngithub.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=\ngithub.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=\ngitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=\ngo.elastic.co/apm v1.5.0/go.mod h1:OdB9sPtM6Vt7oz3VXt7+KR96i9li74qrxBGHTQygFvk=\ngo.elastic.co/apm/module/apmhttp v1.5.0/go.mod h1:1FbmNuyD3ddauwzgVwFB0fqY6KbZt3JkV187tGCYYhY=\ngo.elastic.co/apm/module/apmot v1.5.0/go.mod h1:d2KYwhJParTpyw2WnTNy8geNlHKKFX+4oK3YLlsesWE=\ngo.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs=\ngo.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=\ngo.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=\ngo.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=\ngo.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=\ngo.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=\ngo.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=\ngo.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=\ngo.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=\ngo.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=\ngo.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=\ngo.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/automaxprocs v1.2.0/go.mod h1:YfO3fm683kQpzETxlTGZhGIVmXAhaw3gxeBADbpZtnU=\ngo.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=\ngo.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=\ngo.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=\ngo.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=\ngolang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=\ngolang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=\ngolang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=\ngolang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=\ngolang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=\ngolang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=\ngolang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=\ngolang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=\ngolang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=\ngolang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=\ngolang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=\ngolang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=\ngolang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=\ngolang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=\ngolang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=\ngolang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=\ngolang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=\ngolang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190310054646-10058d7d4faa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190425145619-16072639606e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=\ngolang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=\ngolang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=\ngolang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=\ngolang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=\ngolang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=\ngolang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=\ngolang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=\ngolang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=\ngolang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=\ngolang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=\ngolang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=\ngolang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180805044716-cb6730876b98/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=\ngolang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=\ngolang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=\ngolang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=\ngolang.org/x/tools v0.0.0-20190813034749-528a2984e271/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190918214516-5a1a30219888/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191111182352-50fa39b762bc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200327195553-82bb89366a1e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=\ngolang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=\ngolang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=\ngolang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=\ngolang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=\ngolang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=\ngolang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=\ngolang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=\ngolang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=\ngolang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=\ngomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=\ngomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\ngomodules.xyz/jsonpatch/v3 v3.0.1/go.mod h1:CBhndykehEwTOlEfnsfJwvkFQbSN8YZFr9M+cIHAJto=\ngomodules.xyz/orderedmap v0.1.0/go.mod h1:g9/TPUCm1t2gwD3j3zfV8uylyYhVdCNSi+xCEIu7yTU=\ngonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=\ngonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=\ngoogle.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=\ngoogle.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=\ngoogle.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=\ngoogle.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=\ngoogle.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngoogle.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=\ngopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=\ngopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=\ngopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=\ngopkg.in/imdario/mergo.v0 v0.3.7/go.mod h1:9qPP6AGrlC1G2PTNXko614FwGZvorN7MiBU0Eppok+U=\ngopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=\ngopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=\ngopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=\ngopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=\ngopkg.in/yaml.v2 v2.1.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=\ngotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=\ngotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=\ngotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=\ngotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=\nhelm.sh/helm/v3 v3.1.0/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g=\nhelm.sh/helm/v3 v3.1.2/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhowett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=\nk8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A=\nk8s.io/api v0.0.0-20190813020757-36bff7324fb7/go.mod h1:3Iy+myeAORNCLgjd/Xu9ebwN7Vh59Bw0vh9jhoX+V58=\nk8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=\nk8s.io/api v0.0.0-20191115095533-47f6de673b26/go.mod h1:iA/8arsvelvo4IDqIhX4IbjTEKBGgvsf2OraTuRtLFU=\nk8s.io/api v0.16.7/go.mod h1:oUAiGRgo4t+5yqcxjOu5LoHT3wJ8JSbgczkaFYS5L7I=\nk8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=\nk8s.io/api v0.17.1/go.mod h1:zxiAc5y8Ngn4fmhWUtSxuUlkfz1ixT7j9wESokELzOg=\nk8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=\nk8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=\nk8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA=\nk8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=\nk8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=\nk8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=\nk8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo=\nk8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE=\nk8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=\nk8s.io/apiextensions-apiserver v0.16.7/go.mod h1:6xYRp4trGp6eT5WZ6tPi/TB2nfWQCzwUvBlpg8iswe0=\nk8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8=\nk8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=\nk8s.io/apiextensions-apiserver v0.17.3/go.mod h1:CJbCyMfkKftAd/X/V6OTHYhVn7zXnDdnkUjS1h0GTeY=\nk8s.io/apiextensions-apiserver v0.17.4/go.mod h1:rCbbbaFS/s3Qau3/1HbPlHblrWpFivoaLYccCffvQGI=\nk8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk=\nk8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=\nk8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA=\nk8s.io/apimachinery v0.0.0-20190809020650-423f5d784010/go.mod h1:Waf/xTS2FGRrgXCkO5FP3XxTOWh0qLf2QhL1qFZZ/R8=\nk8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=\nk8s.io/apimachinery v0.0.0-20191115015347-3c7067801da2/go.mod h1:dXFS2zaQR8fyzuvRdJDHw2Aerij/yVGJSre0bZQSVJA=\nk8s.io/apimachinery v0.16.7/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE=\nk8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=\nk8s.io/apimachinery v0.17.1/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=\nk8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=\nk8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=\nk8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=\nk8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=\nk8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=\nk8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=\nk8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc=\nk8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=\nk8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=\nk8s.io/apiserver v0.16.7/go.mod h1:/5zSatF30/L9zYfMTl55jzzOnx7r/gGv5a5wtRp8yAw=\nk8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg=\nk8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=\nk8s.io/apiserver v0.17.3/go.mod h1:iJtsPpu1ZpEnHaNawpSV0nYTGBhhX2dUlnn7/QS7QiY=\nk8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=\nk8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=\nk8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=\nk8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=\nk8s.io/autoscaler v0.0.0-20190607113959-1b4f1855cb8e/go.mod h1:QEXezc9uKPT91dwqhSJq3GNI3B1HxFRQHiku9kmrsSA=\nk8s.io/cli-runtime v0.17.2/go.mod h1:aa8t9ziyQdbkuizkNLAw3qe3srSyWh9zlSB7zTqRNPI=\nk8s.io/cli-runtime v0.17.3/go.mod h1:X7idckYphH4SZflgNpOOViSxetiMj6xI0viMAjM81TA=\nk8s.io/cli-runtime v0.17.4/go.mod h1:IVW4zrKKx/8gBgNNkhiUIc7nZbVVNhc1+HcQh+PiNHc=\nk8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=\nk8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU=\nk8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=\nk8s.io/code-generator v0.16.7/go.mod h1:wFdrXdVi/UC+xIfLi+4l9elsTT/uEF61IfcN2wOLULQ=\nk8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=\nk8s.io/code-generator v0.17.1/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=\nk8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=\nk8s.io/code-generator v0.17.3/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=\nk8s.io/code-generator v0.17.4/go.mod h1:l8BLVwASXQZTo2xamW5mQNFCe1XPiAesVq7Y1t7PiQQ=\nk8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=\nk8s.io/component-base v0.16.7/go.mod h1:ikdyfezOFMu5O0qJjy/Y9eXwj+fV3pVwdmt0ulVcIR0=\nk8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=\nk8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=\nk8s.io/component-base v0.17.3/go.mod h1:GeQf4BrgelWm64PXkIXiPh/XS0hnO42d9gx9BtbZRp8=\nk8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE=\nk8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=\nk8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI=\nk8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM=\nk8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM=\nk8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=\nk8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI=\nk8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc=\nk8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/gengo v0.0.0-20191010091904-7fa3014cb28f/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=\nk8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8=\nk8s.io/helm v2.16.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=\nk8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=\nk8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=\nk8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=\nk8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=\nk8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=\nk8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=\nk8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=\nk8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=\nk8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=\nk8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=\nk8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=\nk8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-aggregator v0.17.3/go.mod h1:1dMwMFQbmH76RKF0614L7dNenMl3dwnUJuOOyZ3GMXA=\nk8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=\nk8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=\nk8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=\nk8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=\nk8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=\nk8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=\nk8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=\nk8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=\nk8s.io/kube-state-metrics v1.7.2/go.mod h1:U2Y6DRi07sS85rmVPmBFlmv+2peBcL8IWGjM+IjYA/E=\nk8s.io/kubectl v0.17.2/go.mod h1:y4rfLV0n6aPmvbRCqZQjvOp3ezxsFgpqL+zF5jH/lxk=\nk8s.io/kubectl v0.17.3/go.mod h1:NUn4IBY7f7yCMwSop2HCXlw/MVYP4HJBiUmOR3n9w28=\nk8s.io/kubectl v0.17.4/go.mod h1:im5QWmh6fvtmJkkNm4HToLe8z9aM3jihYK5X/wOybcY=\nk8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=\nk8s.io/metrics v0.17.2/go.mod h1:3TkNHET4ROd+NfzNxkjoVfQ0Ob4iZnaHmSEA4vYpwLw=\nk8s.io/metrics v0.17.3/go.mod h1:HEJGy1fhHOjHggW9rMDBJBD3YuGroH3Y1pnIRw9FFaI=\nk8s.io/metrics v0.17.4/go.mod h1:6rylW2iD3M9VppnEAAtJASY1XS8Pt9tcYh+tHxBeV3I=\nk8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=\nk8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=\nk8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=\nk8s.io/utils v0.0.0-20191114200735-6ca3b61696b6/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=\nk8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=\nk8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nk8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=\nk8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nmodernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=\nmodernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=\nmodernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=\nmodernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=\nmodernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=\nsigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=\nsigs.k8s.io/controller-runtime v0.5.2/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A=\nsigs.k8s.io/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo=\nsigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=\nsigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA=\nsigs.k8s.io/controller-tools v0.2.8/go.mod h1:9VKHPszmf2DHz/QmHkcfZoewO6BL7pPs9uAiBVsaJSE=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=\nsigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=\nsigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=\nsigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=\nsigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=\nsigs.k8s.io/structured-merge-diff v1.0.2/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=\nsigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=\nsigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=\nsigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=\nsigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=\nvbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=\n"
  },
  {
    "path": "hack/init.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nfunction git_find() {\n    # Similar to find but faster and easier to understand.  We want to include\n    # modified and untracked files because this might be running against code\n    # which is not tracked by git yet.\n    git ls-files -cmo --exclude-standard \\\n        ':!:vendor/*'        `# catches vendor/...` \\\n        ':!:*/vendor/*'      `# catches any subdir/vendor/...` \\\n        ':!:third_party/*'   `# catches third_party/...` \\\n        ':!:*/third_party/*' `# catches third_party/...` \\\n        ':!:*/testdata/*'    `# catches any subdir/testdata/...` \\\n        ':(glob)**/*.go' \\\n        \"$@\"\n}"
  },
  {
    "path": "hack/update-gofmt.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nsource \"$(dirname \"$0\")/init.sh\"\ngo install mvdan.cc/gofumpt@v0.10.0\n\n# Serially process each file to avoid concurrent write issues\nfor f in $(git_find); do\n  gofumpt -w \"$f\"\ndone\n\n"
  },
  {
    "path": "hack/update-imports.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nsource \"$(dirname \"$0\")/init.sh\"\ngo install golang.org/x/tools/cmd/goimports@latest\n\n# Serially process each file to avoid concurrent write issues\nfor f in $(git_find); do\n  goimports -w -local github.com/chaosblade-io/chaosblade-operator -srcdir \"$(dirname \"$f\")\" \"$f\"\ndone\n\n"
  },
  {
    "path": "hack/verify-gofmt.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nsource \"$(dirname \"$0\")/init.sh\"\ngo install mvdan.cc/gofumpt@v0.10.0\n\n# gofmt exits with non-zero exit code if it finds a problem unrelated to\n# formatting (e.g., a file does not parse correctly). Without \"|| true\" this\n# would have led to no useful error message from gofmt, because the script would\n# have failed before getting to the \"echo\" in the block below.\ndiff=$(git_find | xargs gofumpt -d 2>&1) || true\nif [[ -n \"${diff}\" ]]; then\n  echo \"${diff}\" >&2\n  echo >&2\n  echo \"Run ./hack/update-gofmt.sh\" >&2\n  exit 1\nfi"
  },
  {
    "path": "hack/verify-imports.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nsource \"$(dirname \"$0\")/init.sh\"\ngo install golang.org/x/tools/cmd/goimports@latest\n\ndiff=$(git_find | xargs goimports -l -local github.com/chaosblade-io/chaosblade-operator 2>&1) || true\nif [[ -n \"${diff}\" ]]; then\n  echo \"The following files have incorrect import order. Please run ./hack/update-imports.sh to fix them:\" >&2\n  echo \"${diff}\" >&2\n  exit 1\nfi\n"
  },
  {
    "path": "licenserc.toml",
    "content": "# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Base directory for the whole execution.\n# All relative paths is based on this path.\n# default: current working directory\nbaseDir = \".\"\n\nheaderPath = \"Apache-2.0.txt\"\n\n# On enabled, check the license header matches exactly with whitespace.\n# Otherwise, strip the header in one line and check.\n# default: true\nstrictCheck = true\n\n# Whether you use the default excludes. Check Default.EXCLUDES for the completed list.\n# To suppress part of excludes in the list, declare exact the same pattern in `includes` list.\n# default: true\nuseDefaultExcludes = true\n\nexcludes = [\n    \"*.txt\",\n]\n\n# The supported patterns of includes and excludes follow gitignore pattern format, plus that:\n# 1. `includes` does not support `!`\n# 2. backslash does not escape letter\n# 3. whitespaces and `#` are normal since we configure line by line\n# See also https://git-scm.com/docs/gitignore#_pattern_format\n\n# Keywords that should occur in the header, case-insensitive.\n# default: [\"copyright\"]\nkeywords = [\"copyright\", ]\n\n# Whether you use the default mapping. Check DocumentType.defaultMapping() for the completed list.\n# default: true\nuseDefaultMapping = true\n\n# Properties to fulfill the template.\n# For a defined key-value pair, you can use {{props[\"key\"]}} in the header template, which will be\n# substituted with the corresponding value.\n[properties]\ninceptionYear = 2025\ncopyrightOwner = \"The ChaosBlade Authors\"\n\n# There are also preset attributes that can be used in the header template (no need to surround them with `props[]`).:\n# * 'attrs.filename' is the current file name, like: pom.xml.\n\n# Options to configure Git features.\n[git]\n# If enabled, do not process files that are ignored by Git; possible value: ['auto', 'enable', 'disable']\n# 'auto' means this feature tries to be enabled with:\n#   * gix - if `basedir` is in a Git repository.\n#   * ignore crate's gitignore rules - if `basedir` is not in a Git repository.\n# 'enable' means always enabled with gix; failed if it is impossible.\n# default: 'auto'\nignore = 'auto'\n# If enabled, populate file attrs determinated by Git; possible value: ['auto', 'enable', 'disable']\n# Attributes contains:\n#   * 'attrs.git_file_created_year'\n#   * 'attrs.git_file_modified_year'\n# 'auto' means this feature tries to be enabled with:\n#   * gix - if `basedir` is in a Git repository.\n# 'enable' means always enabled with gix; failed if it is impossible.\n# default: 'disable'\nattrs = 'disable'\n"
  },
  {
    "path": "pkg/apis/addtoscheme_chaosblade_v1alpha1.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apis\n\nimport (\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\nfunc init() {\n\t// Register the types with the Scheme so the components can map objects to GroupVersionKinds and back\n\tAddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme)\n}\n"
  },
  {
    "path": "pkg/apis/apis.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage apis\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// AddToSchemes may be used to add all resources defined in the project to a Scheme\nvar AddToSchemes runtime.SchemeBuilder\n\n// AddToScheme adds all Resources to the Scheme\nfunc AddToScheme(s *runtime.Scheme) error {\n\treturn AddToSchemes.AddToScheme(s)\n}\n"
  },
  {
    "path": "pkg/apis/chaosblade/group.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package chaosblade contains chaosblade API versions.\n//\n// This file ensures Go source parsers acknowledge the chaosblade package\n// and any child packages. It can be removed if any other Go source files are\n// added to this package.\npackage chaosblade\n"
  },
  {
    "path": "pkg/apis/chaosblade/v1alpha1/doc.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package v1alpha1 contains API Schema definitions for the chaosblade v1alpha1 API group\n// +k8s:deepcopy-gen=package,register\n// +groupName=chaosblade.io\npackage v1alpha1\n"
  },
  {
    "path": "pkg/apis/chaosblade/v1alpha1/register.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// NOTE: Boilerplate only.  Ignore this file.\n\n// Package v1alpha1 contains API Schema definitions for the chaosblade v1alpha1 API group\n// +k8s:deepcopy-gen=package,register\n// +groupName=chaosblade.io\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects\n\tSchemeGroupVersion = schema.GroupVersion{Group: \"chaosblade.io\", Version: \"v1alpha1\"}\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme\n\tSchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}\n)\n"
  },
  {
    "path": "pkg/apis/chaosblade/v1alpha1/types.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\ntype ClusterPhase string\n\nconst (\n\tClusterPhaseInitial     ClusterPhase = \"\"\n\tClusterPhaseInitialized ClusterPhase = \"Initialized\"\n\tClusterPhaseRunning     ClusterPhase = \"Running\"\n\tClusterPhaseUpdating    ClusterPhase = \"Updating\"\n\tClusterPhaseDestroying  ClusterPhase = \"Destroying\"\n\tClusterPhaseDestroyed   ClusterPhase = \"Destroyed\"\n\tClusterPhaseError       ClusterPhase = \"Error\"\n)\n\n// ChaosBladeSpec defines the desired state of ChaosBlade\n// +k8s:openapi-gen=true\ntype ChaosBladeSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\tExperiments []ExperimentSpec `json:\"experiments\"`\n}\n\ntype ExperimentSpec struct {\n\t// Scope is the area of the experiments, currently support node, pod and container\n\tScope string `json:\"scope\"`\n\t// Target is the experiment target, such as cpu, network\n\tTarget string `json:\"target\"`\n\t// Action is the experiment scenario of the target, such as delay, load\n\tAction string `json:\"action\"`\n\t// Desc is the experiment description\n\tDesc string `json:\"desc,omitempty\"`\n\t// Matchers is the experiment rules\n\tMatchers []FlagSpec `json:\"matchers,omitempty\"`\n}\n\ntype FlagSpec struct {\n\t// Name is the name of flag\n\tName string `json:\"name\"`\n\t// TODO: Temporarily defined as an array for all flags\n\t// Value is the value of flag\n\tValue []string `json:\"value\"`\n}\n\n// ChaosBladeStatus defines the observed state of ChaosBlade\n// +k8s:openapi-gen=true\ntype ChaosBladeStatus struct {\n\t// Phase indicates the state of the experiment\n\t//   Initial -> Running -> Updating -> Destroying -> Destroyed\n\tPhase ClusterPhase `json:\"phase,omitempty\"`\n\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\tExpStatuses []ExperimentStatus `json:\"expStatuses\"`\n}\n\nfunc (in *ResourceStatus) CreateFailResourceStatus(err string, code int32) ResourceStatus {\n\tin.State = ErrorState\n\tin.Error = err\n\tin.Success = false\n\tin.Code = code\n\treturn *in\n}\n\nfunc (in *ResourceStatus) CreateSuccessResourceStatus() ResourceStatus {\n\tin.State = SuccessState\n\tin.Success = true\n\treturn *in\n}\n\nconst (\n\tPodKind       = \"pod\"\n\tContainerKind = \"container\"\n\tNodeKind      = \"node\"\n\tServiceKind   = \"service\"\n)\n\ntype ResourceStatus struct {\n\t// experiment uid in chaosblade\n\tId string `json:\"id,omitempty\"`\n\t// experiment state\n\tState string `json:\"state\"`\n\t// experiment code\n\tCode int32 `json:\"code,omitempty\"`\n\t// experiment error\n\tError string `json:\"error,omitempty\"`\n\t// success\n\tSuccess bool `json:\"success\"`\n\n\t// Kind\n\tKind string `json:\"kind\"`\n\t// Resource identifier, rules as following:\n\t// container: Namespace/NodeName/PodName/ContainerName\n\t// pod： Namespace/NodeName/PodName\n\tIdentifier string `json:\"identifier,omitempty\"`\n}\n\nconst (\n\tSuccessState   = \"Success\"\n\tErrorState     = \"Error\"\n\tDestroyedState = \"Destroyed\"\n)\n\nfunc CreateFailExperimentStatus(err string, ResStatuses []ResourceStatus) ExperimentStatus {\n\treturn ExperimentStatus{Success: false, State: ErrorState, Error: err, ResStatuses: ResStatuses}\n}\n\nfunc CreateSuccessExperimentStatus(ResStatuses []ResourceStatus) ExperimentStatus {\n\treturn ExperimentStatus{Success: true, State: SuccessState, ResStatuses: ResStatuses}\n}\n\nfunc CreateDestroyedExperimentStatus(ResStatuses []ResourceStatus) ExperimentStatus {\n\treturn ExperimentStatus{Success: true, State: DestroyedState, ResStatuses: ResStatuses}\n}\n\nfunc CreateFailResStatuses(code int32, err, uid string) []ResourceStatus {\n\tstatuses := make([]ResourceStatus, 0)\n\tstatuses = append(statuses, ResourceStatus{\n\t\tError:   err,\n\t\tCode:    code,\n\t\tId:      uid,\n\t\tSuccess: false,\n\t})\n\treturn statuses\n}\n\ntype ExperimentStatus struct {\n\t// experiment scope for cache\n\tScope  string `json:\"scope\"`\n\tTarget string `json:\"target\"`\n\tAction string `json:\"action\"`\n\t// Success is used to judge the experiment result\n\tSuccess bool `json:\"success\"`\n\t// State is used to describe the experiment result\n\tState string `json:\"state\"`\n\tError string `json:\"error,omitempty\"`\n\t// ResStatuses is the details of the experiment\n\tResStatuses []ResourceStatus `json:\"resStatuses,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ChaosBlade is the Schema for the chaosblades API\n// +k8s:openapi-gen=true\n// +kubebuilder:subresource:status\ntype ChaosBlade struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   ChaosBladeSpec   `json:\"spec,omitempty\"`\n\tStatus ChaosBladeStatus `json:\"status,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ChaosBladeList contains a list of ChaosBlade\ntype ChaosBladeList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []ChaosBlade `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&ChaosBlade{}, &ChaosBladeList{})\n}\n"
  },
  {
    "path": "pkg/apis/chaosblade/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Code generated by operator-sdk. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChaosBlade) DeepCopyInto(out *ChaosBlade) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosBlade.\nfunc (in *ChaosBlade) DeepCopy() *ChaosBlade {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChaosBlade)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ChaosBlade) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChaosBladeList) DeepCopyInto(out *ChaosBladeList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]ChaosBlade, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosBladeList.\nfunc (in *ChaosBladeList) DeepCopy() *ChaosBladeList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChaosBladeList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *ChaosBladeList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChaosBladeSpec) DeepCopyInto(out *ChaosBladeSpec) {\n\t*out = *in\n\tif in.Experiments != nil {\n\t\tin, out := &in.Experiments, &out.Experiments\n\t\t*out = make([]ExperimentSpec, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosBladeSpec.\nfunc (in *ChaosBladeSpec) DeepCopy() *ChaosBladeSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChaosBladeSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ChaosBladeStatus) DeepCopyInto(out *ChaosBladeStatus) {\n\t*out = *in\n\tif in.ExpStatuses != nil {\n\t\tin, out := &in.ExpStatuses, &out.ExpStatuses\n\t\t*out = make([]ExperimentStatus, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosBladeStatus.\nfunc (in *ChaosBladeStatus) DeepCopy() *ChaosBladeStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ChaosBladeStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ExperimentSpec) DeepCopyInto(out *ExperimentSpec) {\n\t*out = *in\n\tif in.Matchers != nil {\n\t\tin, out := &in.Matchers, &out.Matchers\n\t\t*out = make([]FlagSpec, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExperimentSpec.\nfunc (in *ExperimentSpec) DeepCopy() *ExperimentSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ExperimentSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ExperimentStatus) DeepCopyInto(out *ExperimentStatus) {\n\t*out = *in\n\tif in.ResStatuses != nil {\n\t\tin, out := &in.ResStatuses, &out.ResStatuses\n\t\t*out = make([]ResourceStatus, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExperimentStatus.\nfunc (in *ExperimentStatus) DeepCopy() *ExperimentStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ExperimentStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *FlagSpec) DeepCopyInto(out *FlagSpec) {\n\t*out = *in\n\tif in.Value != nil {\n\t\tin, out := &in.Value, &out.Value\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlagSpec.\nfunc (in *FlagSpec) DeepCopy() *FlagSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(FlagSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceStatus.\nfunc (in *ResourceStatus) DeepCopy() *ResourceStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ResourceStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "pkg/controller/add_chaosblade.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage controller\n\nimport (\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/controller/chaosblade\"\n)\n\nfunc init() {\n\t// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.\n\tAddToManagerFuncs = append(AddToManagerFuncs, chaosblade.Add)\n}\n"
  },
  {
    "path": "pkg/controller/chaosblade/controller.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage chaosblade\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/util/retry\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/channel\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec\"\n\t\"github.com/chaosblade-io/chaosblade-operator/exec/model\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n\truntime2 \"github.com/chaosblade-io/chaosblade-operator/pkg/runtime\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n\t\"github.com/chaosblade-io/chaosblade-operator/version\"\n)\n\nconst chaosbladeFinalizer = \"finalizer.chaosblade.io\"\n\n/**\n* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller\n* business logic.  Delete these comments after modifying this file.*\n */\n\n// Add creates a new ChaosBlade Controller and adds it to the Manager. The Manager will set fields on the Controller\n// and Start it when the Manager is Started.\nfunc Add(mgr manager.Manager) error {\n\tif err := add(mgr, newReconciler(mgr)); err != nil {\n\t\treturn err\n\t}\n\t// add periodically clean up blade ticker\n\treturn mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {\n\t\tstartPeriodicallyCleanUpBlade(ctx, mgr)\n\t\treturn nil\n\t}))\n}\n\n// newReconciler returns a new reconcile.Reconciler\nfunc newReconciler(mgr manager.Manager) *ReconcileChaosBlade {\n\tcbClient := mgr.GetClient().(*channel.Client)\n\treturn &ReconcileChaosBlade{\n\t\tclient:   cbClient,\n\t\tscheme:   mgr.GetScheme(),\n\t\tExecutor: exec.NewDispatcherExecutor(cbClient),\n\t}\n}\n\n// add adds a new Controller to mgr with r as the reconcile.Reconciler\nfunc add(mgr manager.Manager, rcb *ReconcileChaosBlade) error {\n\t// Create a new controller\n\tc, err := controller.New(\"chaosblade-controller\", mgr, controller.Options{\n\t\tReconciler:              rcb,\n\t\tMaxConcurrentReconciles: runtime2.MaxConcurrentReconciles,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Watch for changes to primary resource ChaosBlade\n\tcb := v1alpha1.ChaosBlade{}\n\terr = c.Watch(source.Kind(\n\t\tmgr.GetCache(),\n\t\t&cb,\n\t\t&handler.TypedEnqueueRequestForObject[*v1alpha1.ChaosBlade]{},\n\t\t&SpecUpdatedPredicateForRunningPhase{},\n\t))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif chaosblade.DaemonsetEnable {\n\t\t//namespace, err := k8sutil.GetOperatorNamespace()\n\t\t//if err != nil {\n\t\t//\treturn err\n\t\t//}\n\t\t//chaosblade.DaemonsetPodNamespace = namespace\n\t\t//// deploy chaosblade tool\n\t\t//if err := deployChaosBladeTool(rcb); err != nil {\n\t\t//\tlogrus.WithField(\"product\", version.Product).WithError(err).Errorln(\"Failed to deploy chaosblade tool\")\n\t\t//\treturn err\n\t\t//}\n\t\tlogrus.WithField(\"product\", version.Product).WithField(\"daemonset.enable\", chaosblade.DaemonsetEnable).\n\t\t\tInfoln(\"enable chaosblade-tool deamonset\")\n\t}\n\treturn nil\n}\n\n// if blade status is destroying\nfunc startPeriodicallyCleanUpBlade(ctx context.Context, mgr manager.Manager) {\n\tgo func() {\n\t\tcli := mgr.GetClient()\n\t\tduration, err := time.ParseDuration(chaosblade.RemoveBladeInterval)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"parse interval error: %v, use default interval: %s\", err, chaosblade.DefaultRemoveBladeInterval)\n\t\t\tduration, err = time.ParseDuration(chaosblade.DefaultRemoveBladeInterval)\n\t\t\tchaosblade.RemoveBladeInterval = chaosblade.DefaultRemoveBladeInterval\n\t\t\tif err != nil {\n\t\t\t\tlogrus.Fatalf(\"start periodically clean up blade, ticker error: %v\", err)\n\t\t\t}\n\t\t}\n\t\t// first clean up\n\t\tperiodicallyCleanUpBlade(ctx, cli, duration)\n\n\t\t// ticker clean up\n\t\tticker := time.NewTicker(time.Second * time.Duration(duration.Seconds()))\n\t\tlogrus.Infof(\"start periodically clean up blade ticker, interval: %s\", chaosblade.RemoveBladeInterval)\n\t\tfor range ticker.C {\n\t\t\tperiodicallyCleanUpBlade(ctx, cli, duration)\n\t\t}\n\t}()\n}\n\nfunc periodicallyCleanUpBlade(ctx context.Context, cli client.Client, interval time.Duration) {\n\tresults := &v1alpha1.ChaosBladeList{}\n\tif err := cli.List(ctx, results, &client.ListOptions{}); err != nil {\n\t\tlogrus.Errorf(\"periodically clean up, list blade error: %v\", err)\n\t}\n\tlogrus.Infof(\"periodically clean up blade, blade size: %d\", len(results.Items))\n\tfor _, item := range results.Items {\n\t\tif item.DeletionTimestamp == nil {\n\t\t\tcontinue\n\t\t}\n\t\tsub := time.Now().Sub(item.DeletionTimestamp.Time)\n\t\tif item.Status.Phase == v1alpha1.ClusterPhaseDestroying && sub.Seconds() > interval.Seconds() {\n\t\t\tlogrus.Infof(\"periodically clean up blade %s, deletion time: %s\", item.Name, item.DeletionTimestamp.String())\n\t\t\t// patch blade\n\t\t\tif err := cli.Patch(\n\t\t\t\tctx,\n\t\t\t\t&v1alpha1.ChaosBlade{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"chaosblade.io/v1alpha1\",\n\t\t\t\t\t\tKind:       \"ChaosBlade\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{Name: item.Name},\n\t\t\t\t},\n\t\t\t\tclient.RawPatch(types.MergePatchType, []byte(`{\"metadata\":{\"finalizers\":[]}}`)),\n\t\t\t); err != nil {\n\t\t\t\tlogrus.Errorf(\"patch blade: %s, error: %v\", item.Name, err)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// blank assignment to verify that ReconcileChaosBlade implements reconcile.Reconciler\nvar _ reconcile.Reconciler = &ReconcileChaosBlade{}\n\n// ReconcileChaosBlade reconciles a ChaosBlade object\ntype ReconcileChaosBlade struct {\n\t// This client, initialized using mgr.Client() above, is a split client\n\t// that reads objects from the cache and writes to the apiserver\n\tclient   *channel.Client\n\tscheme   *runtime.Scheme\n\tExecutor model.ExpController\n}\n\n// Reconcile reads that state of the cluster for a ChaosBlade object and makes changes based on the state read\n// and what is in the ChaosBlade.Spec\n// Note:\n// The Controller will requeue the Request to be processed again if the returned error is non-nil or\n// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.\nfunc (r *ReconcileChaosBlade) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {\n\treqLogger := logrus.WithField(\"Request.Name\", request.Name)\n\tforget := reconcile.Result{}\n\t// Fetch the RC instance\n\tcb := &v1alpha1.ChaosBlade{}\n\terr := r.client.Get(ctx, request.NamespacedName, cb)\n\tif err != nil {\n\t\treturn forget, nil\n\t}\n\tif len(cb.Spec.Experiments) == 0 {\n\t\treturn forget, nil\n\t}\n\t// reqLogger.Info(fmt.Sprintf(\"chaosblade obj: %+v\", cb))\n\n\t// Destroyed->delete\n\t// Remove the Finalizer if the CR object status is destroyed to delete it\n\tif cb.Status.Phase == v1alpha1.ClusterPhaseDestroyed {\n\t\t// Re-fetch the CR to avoid conflict\n\t\tlatestCb := &v1alpha1.ChaosBlade{}\n\t\tif err := r.client.Get(ctx, request.NamespacedName, latestCb); err != nil {\n\t\t\treqLogger.WithError(err).Errorln(\"re-fetch chaosblade failed before removing finalizer\")\n\t\t\treturn reconcile.Result{}, err\n\t\t}\n\t\tlatestCb.SetFinalizers(remove(latestCb.GetFinalizers(), chaosbladeFinalizer))\n\t\terr := r.client.Update(ctx, latestCb)\n\t\tif err != nil {\n\t\t\treqLogger.WithError(err).Errorln(\"remove chaosblade finalizer failed at destroyed phase\")\n\t\t\treturn reconcile.Result{}, err\n\t\t}\n\t\treturn forget, nil\n\t}\n\tif cb.Status.Phase == v1alpha1.ClusterPhaseDestroying || cb.GetDeletionTimestamp() != nil {\n\t\terr := r.finalizeChaosBlade(ctx, reqLogger, cb, request.NamespacedName)\n\t\tif err != nil {\n\t\t\treqLogger.WithError(err).Errorln(\"finalize chaosblade failed at destroying phase\")\n\t\t\treturn reconcile.Result{}, err\n\t\t}\n\t\treturn forget, nil\n\t}\n\t// Initial->Initialized\n\tif cb.Status.Phase == v1alpha1.ClusterPhaseInitial {\n\t\tif contains(cb.GetFinalizers(), chaosbladeFinalizer) {\n\t\t\tcb.Status.Phase = v1alpha1.ClusterPhaseInitialized\n\t\t\tcb.Status.ExpStatuses = make([]v1alpha1.ExperimentStatus, 0)\n\t\t\tif err := r.client.Status().Update(ctx, cb); err != nil {\n\t\t\t\treqLogger.WithError(err).Errorln(\"update chaosblade phase to Initialized failed\")\n\t\t\t\treturn reconcile.Result{}, err\n\t\t\t}\n\t\t} else {\n\t\t\tcb.SetFinalizers(append(cb.GetFinalizers(), chaosbladeFinalizer))\n\t\t\t// Update CR\n\t\t\tif err := r.client.Update(ctx, cb); err != nil {\n\t\t\t\treqLogger.WithError(err).Errorln(\"add finalizer to chaosblade failed\")\n\t\t\t\treturn reconcile.Result{}, err\n\t\t\t}\n\t\t}\n\t\treturn forget, nil\n\t}\n\t// Initialized->Running/Error\n\t// TODO When all the master nodes are inaccessible, there is the possibility of re-execution.\n\tif cb.Status.Phase == v1alpha1.ClusterPhaseInitialized ||\n\t\tcb.Status.Phase == v1alpha1.ClusterPhaseUpdating {\n\t\toriginalPhase := cb.Status.Phase\n\t\texpStatusList := make([]v1alpha1.ExperimentStatus, 0)\n\t\tphase := v1alpha1.ClusterPhaseError\n\t\tfor _, exp := range cb.Spec.Experiments {\n\t\t\texperimentStatus := r.Executor.Create(cb.Name, exp)\n\t\t\tif experimentStatus.Success {\n\t\t\t\tphase = v1alpha1.ClusterPhaseRunning\n\t\t\t}\n\t\t\texpStatusList = append(expStatusList, experimentStatus)\n\t\t}\n\t\t// Retry status update on conflict to avoid re-executing Create side effects\n\t\tif err := retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\t\tlatestCb := &v1alpha1.ChaosBlade{}\n\t\t\tif err := r.client.Get(ctx, request.NamespacedName, latestCb); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlatestCb.Status.ExpStatuses = expStatusList\n\t\t\tlatestCb.Status.Phase = phase\n\t\t\treturn r.client.Status().Update(ctx, latestCb)\n\t\t}); err != nil {\n\t\t\treqLogger.WithError(err).Errorf(\"update phase from %s to %s failed\", originalPhase, phase)\n\t\t\treturn reconcile.Result{}, err\n\t\t}\n\t\treturn forget, nil\n\t}\n\n\t// Running/Error->Updating/Destroying\n\tif cb.Status.Phase == v1alpha1.ClusterPhaseRunning ||\n\t\tcb.Status.Phase == v1alpha1.ClusterPhaseError {\n\t\t// Update CR, firstly destroy it and re-create the new CR\n\t\tphase := v1alpha1.ClusterPhaseUpdating\n\t\toriginalPhase := cb.Status.Phase\n\t\tlogrus.Infof(\"update cb: %+v\", *cb)\n\t\tmatchersString := cb.GetAnnotations()[\"preSpec\"]\n\t\tif matchersString != \"\" {\n\t\t\tvar oldSpec v1alpha1.ChaosBladeSpec\n\t\t\terr := json.Unmarshal([]byte(matchersString), &oldSpec)\n\t\t\tif err != nil {\n\t\t\t\treqLogger.WithError(err).Errorf(\"unmarshal old spec failed, %s\", matchersString)\n\t\t\t\treturn forget, nil\n\t\t\t}\n\t\t\t// update annotation to cb\n\t\t\tif err = r.client.Update(ctx, cb); err != nil {\n\t\t\t\treqLogger.WithError(err).Errorln(\"add annotation to chaosblade failed\")\n\t\t\t}\n\t\t\tif cb.Status.ExpStatuses != nil {\n\t\t\t\tfor idx, expStatus := range cb.Status.ExpStatuses {\n\t\t\t\t\texperimentStatus := r.Executor.Destroy(cb.Name, oldSpec.Experiments[idx], expStatus)\n\t\t\t\t\tif !experimentStatus.Success {\n\t\t\t\t\t\tphase = v1alpha1.ClusterPhaseDestroying\n\t\t\t\t\t}\n\t\t\t\t\tcb.Status.ExpStatuses[idx] = experimentStatus\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Retry status update on conflict to avoid re-executing Destroy side effects\n\t\t\tupdatedExpStatuses := make([]v1alpha1.ExperimentStatus, len(cb.Status.ExpStatuses))\n\t\t\tcopy(updatedExpStatuses, cb.Status.ExpStatuses)\n\t\t\tcb.Status.Phase = phase\n\t\t\tif err := retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\t\t\tlatestCb := &v1alpha1.ChaosBlade{}\n\t\t\t\tif err := r.client.Get(ctx, request.NamespacedName, latestCb); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tlatestCb.Status.ExpStatuses = updatedExpStatuses\n\t\t\t\tlatestCb.Status.Phase = phase\n\t\t\t\treturn r.client.Status().Update(ctx, latestCb)\n\t\t\t}); err != nil {\n\t\t\t\treqLogger.WithError(err).Errorf(\"update phase from %s to %s failed\", originalPhase, phase)\n\t\t\t}\n\t\t\treturn forget, nil\n\t\t}\n\t\treqLogger.Errorln(\"can not found matchers in annotations field\")\n\t}\n\treturn forget, nil\n}\n\n// finalizeChaosBlade\nfunc (r *ReconcileChaosBlade) finalizeChaosBlade(ctx context.Context, reqLogger *logrus.Entry, cb *v1alpha1.ChaosBlade, namespacedName types.NamespacedName) error {\n\tphase := v1alpha1.ClusterPhaseDestroyed\n\treqLogger.Infoln(\"Finalize the chaosblade\")\n\tif cb.Status.ExpStatuses != nil &&\n\t\tlen(cb.Spec.Experiments) == len(cb.Status.ExpStatuses) {\n\t\tfor idx, exp := range cb.Spec.Experiments {\n\t\t\toldExpStatus := cb.Status.ExpStatuses[idx]\n\t\t\toldExpStatus = r.Executor.Destroy(cb.Name, exp, oldExpStatus)\n\t\t\tif !oldExpStatus.Success {\n\t\t\t\tphase = v1alpha1.ClusterPhaseDestroying\n\t\t\t}\n\t\t\tcb.Status.ExpStatuses[idx] = oldExpStatus\n\t\t}\n\t}\n\t// Retry status update on conflict to avoid re-executing Destroy side effects\n\tif err := retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\tlatestCb := &v1alpha1.ChaosBlade{}\n\t\tif err := r.client.Get(ctx, namespacedName, latestCb); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlatestCb.Status.ExpStatuses = cb.Status.ExpStatuses\n\t\tlatestCb.Status.Phase = phase\n\t\treturn r.client.Status().Update(ctx, latestCb)\n\t}); err != nil {\n\t\treturn fmt.Errorf(\"update chaosblade status failed in finalize phase, %v\", err)\n\t}\n\tif phase == v1alpha1.ClusterPhaseDestroying {\n\t\treturn fmt.Errorf(\"failed to destroy, please see the experiment status\")\n\t}\n\treqLogger.Info(\"Successfully finalized chaosblade\")\n\treturn nil\n}\n\nfunc contains(list []string, s string) bool {\n\tfor _, v := range list {\n\t\tif v == s {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc remove(list []string, s string) []string {\n\tfor i, v := range list {\n\t\tif v == s {\n\t\t\tlist = append(list[:i], list[i+1:]...)\n\t\t}\n\t}\n\treturn list\n}\n"
  },
  {
    "path": "pkg/controller/chaosblade/daemonset.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage chaosblade\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/operator-framework/operator-sdk/pkg/k8sutil\"\n\t\"github.com/sirupsen/logrus\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n)\n\n// Deploy the chaosblade tool with daemonset mode\nfunc deployChaosBladeTool(rcb *ReconcileChaosBlade) error {\n\treferences, err := createOwnerReferences(rcb)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdaemonSet := &appsv1.DaemonSet{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:            chaosblade.DaemonsetPodName,\n\t\t\tNamespace:       chaosblade.DaemonsetPodNamespace,\n\t\t\tLabels:          chaosblade.DaemonsetPodLabels,\n\t\t\tOwnerReferences: references,\n\t\t},\n\t\tSpec: createDaemonsetSpec(),\n\t}\n\n\tif err := rcb.client.Create(context.TODO(), daemonSet); err != nil {\n\t\tif apierrors.IsAlreadyExists(err) {\n\t\t\tlogrus.Info(\"chaosblade tool exits, skip to deploy\")\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc createOwnerReferences(rcb *ReconcileChaosBlade) ([]metav1.OwnerReference, error) {\n\t// get chaosblade operator deployment object\n\t// Using a unstructured object.\n\tu := &unstructured.Unstructured{}\n\tu.SetGroupVersionKind(schema.GroupVersionKind{\n\t\tGroup:   \"apps\",\n\t\tKind:    \"Deployment\",\n\t\tVersion: \"v1\",\n\t})\n\tnamespace, err := k8sutil.GetOperatorNamespace()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = rcb.client.Get(context.TODO(), types.NamespacedName{\n\t\tNamespace: namespace,\n\t\tName:      \"chaosblade-operator\",\n\t}, u)\n\tif err != nil {\n\t\tlogrus.WithError(err).Error(\"cannot get chaosblade-operator deployment from apps/v1\")\n\t\treturn nil, err\n\t}\n\ttrueVar := true\n\treturn []metav1.OwnerReference{\n\t\t{\n\t\t\tAPIVersion: u.GetAPIVersion(),\n\t\t\tKind:       u.GetKind(),\n\t\t\tName:       u.GetName(),\n\t\t\tUID:        u.GetUID(),\n\t\t\tController: &trueVar,\n\t\t},\n\t}, nil\n}\n\n// createDaemonsetSpec\nfunc createDaemonsetSpec() appsv1.DaemonSetSpec {\n\treturn appsv1.DaemonSetSpec{\n\t\tSelector:        &metav1.LabelSelector{MatchLabels: chaosblade.DaemonsetPodLabels},\n\t\tTemplate:        createPodTemplateSpec(),\n\t\tMinReadySeconds: 5,\n\t\tUpdateStrategy:  appsv1.DaemonSetUpdateStrategy{Type: appsv1.RollingUpdateDaemonSetStrategyType},\n\t}\n}\n\n// createPodTemplateSpec\nfunc createPodTemplateSpec() corev1.PodTemplateSpec {\n\treturn corev1.PodTemplateSpec{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:   chaosblade.DaemonsetPodName,\n\t\t\tLabels: chaosblade.DaemonsetPodLabels,\n\t\t},\n\t\tSpec: createPodSpec(),\n\t}\n}\n\nfunc createPodSpec() corev1.PodSpec {\n\tpathType := corev1.HostPathFileOrCreate\n\tperiodSeconds := int64(30)\n\treturn corev1.PodSpec{\n\t\tContainers:                    []corev1.Container{createContainer()},\n\t\tAffinity:                      createAffinity(),\n\t\tDNSPolicy:                     corev1.DNSClusterFirstWithHostNet,\n\t\tHostNetwork:                   true,\n\t\tHostPID:                       true,\n\t\tTolerations:                   []corev1.Toleration{{Effect: corev1.TaintEffectNoSchedule, Operator: corev1.TolerationOpExists}},\n\t\tTerminationGracePeriodSeconds: &periodSeconds,\n\t\tSchedulerName:                 corev1.DefaultSchedulerName,\n\t\tRestartPolicy:                 corev1.RestartPolicyAlways,\n\t\tVolumes: []corev1.Volume{\n\t\t\t{\n\t\t\t\tName:         \"docker-socket\",\n\t\t\t\tVolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: \"/var/run/docker.sock\"}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"chaosblade-db-volume\",\n\t\t\t\tVolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{\n\t\t\t\t\tPath: \"/var/run/chaosblade.dat\",\n\t\t\t\t\tType: &pathType,\n\t\t\t\t}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:         \"hosts\",\n\t\t\t\tVolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: \"/etc/hosts\"}},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createAffinity() *corev1.Affinity {\n\treturn &corev1.Affinity{\n\t\tNodeAffinity: &corev1.NodeAffinity{\n\t\t\tRequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{\n\t\t\t\tNodeSelectorTerms: []corev1.NodeSelectorTerm{\n\t\t\t\t\t{\n\t\t\t\t\t\tMatchExpressions: []corev1.NodeSelectorRequirement{{\n\t\t\t\t\t\t\tKey:      \"type\",\n\t\t\t\t\t\t\tOperator: corev1.NodeSelectorOpNotIn,\n\t\t\t\t\t\t\tValues:   []string{\"virtual-kubelet\"},\n\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createContainer() corev1.Container {\n\ttrueVar := true\n\treturn corev1.Container{\n\t\tName:            chaosblade.DaemonsetPodName,\n\t\tImage:           fmt.Sprintf(\"%s:%s\", chaosblade.Constant.ImageRepoFunc(), chaosblade.Version),\n\t\tImagePullPolicy: corev1.PullPolicy(chaosblade.PullPolicy),\n\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\t{Name: \"docker-socket\", MountPath: \"/var/run/docker.sock\"},\n\t\t\t{Name: \"chaosblade-db-volume\", MountPath: \"/opt/chaosblade/chaosblade.dat\"},\n\t\t\t{Name: \"hosts\", MountPath: \"/etc/hosts\"},\n\t\t},\n\t\tSecurityContext: &corev1.SecurityContext{Privileged: &trueVar},\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/chaosblade/predicate.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage chaosblade\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1\"\n)\n\ntype SpecUpdatedPredicateForRunningPhase struct{}\n\nfunc (sup *SpecUpdatedPredicateForRunningPhase) Create(e event.TypedCreateEvent[*v1alpha1.ChaosBlade]) bool {\n\tif e.Object == nil {\n\t\treturn false\n\t}\n\tobj := e.Object\n\tlogrus.Infof(\"trigger create event, name: %s\", obj.Name)\n\tlogrus.Debugf(\"creating obj: %+v\", obj)\n\tif obj.GetDeletionTimestamp() != nil {\n\t\tlogrus.Infof(\"unexpected phase for cb creating, name: %s, phase: %s\", obj.Name, obj.Status.Phase)\n\t\treturn false\n\t}\n\tif obj.Status.Phase == v1alpha1.ClusterPhaseInitial {\n\t\treturn true\n\t}\n\tlogrus.Infof(\"unexpected phase for cb creating, name: %s, phase: %s\", obj.Name, obj.Status.Phase)\n\treturn false\n}\n\nfunc (*SpecUpdatedPredicateForRunningPhase) Delete(e event.TypedDeleteEvent[*v1alpha1.ChaosBlade]) bool {\n\tif e.Object == nil {\n\t\treturn false\n\t}\n\tobj := e.Object\n\tlogrus.Infof(\"trigger delete event, name: %s\", obj.Name)\n\tlogrus.Debugf(\"deleting obj: %+v\", obj)\n\treturn contains(obj.GetFinalizers(), chaosbladeFinalizer)\n}\n\nfunc (*SpecUpdatedPredicateForRunningPhase) Update(e event.TypedUpdateEvent[*v1alpha1.ChaosBlade]) bool {\n\tif e.ObjectOld == nil {\n\t\treturn false\n\t}\n\toldObj := e.ObjectOld\n\tlogrus.Infof(\"trigger update event, name: %s\", oldObj.Name)\n\tnewObj := e.ObjectNew\n\tif newObj == nil {\n\t\treturn false\n\t}\n\tlogrus.Debugf(\"updating oldObj: %+v\", oldObj)\n\tlogrus.Debugf(\"updating newObj: %+v\", newObj)\n\tif !reflect.DeepEqual(newObj.Spec, oldObj.Spec) {\n\t\tbytes, err := json.Marshal(oldObj.Spec.DeepCopy())\n\t\tif err != nil {\n\t\t\tlogrus.Warningf(\"marshal old spec failed, %+v\", err)\n\t\t\treturn false\n\t\t}\n\t\tnewObj.SetAnnotations(map[string]string{\"preSpec\": string(bytes)})\n\t\treturn true\n\t}\n\n\tif newObj.Status.Phase == v1alpha1.ClusterPhaseInitial {\n\t\treturn true\n\t}\n\t// delete Error chaosblade\n\tif oldObj.GetDeletionTimestamp() == nil &&\n\t\tnewObj.GetDeletionTimestamp() != nil {\n\t\treturn true\n\t}\n\tif newObj.Status.Phase == v1alpha1.ClusterPhaseRunning ||\n\t\tnewObj.Status.Phase == v1alpha1.ClusterPhaseError ||\n\t\tnewObj.Status.Phase == v1alpha1.ClusterPhaseDestroying {\n\t\treturn false\n\t}\n\tif newObj.Status.Phase != oldObj.Status.Phase {\n\t\treturn true\n\t}\n\tif !reflect.DeepEqual(newObj.Status, oldObj.Status) {\n\t\treturn true\n\t}\n\tif newObj.GetDeletionTimestamp() != nil {\n\t\tif contains(newObj.GetFinalizers(), chaosbladeFinalizer) {\n\t\t\treturn true\n\t\t}\n\t\tlogrus.Infof(\"cannot find the %s finalizer, so skip the update event\", chaosbladeFinalizer)\n\t\treturn false\n\t}\n\tlogrus.Infof(\"spec not changed under %s phase, so skip the update event\", newObj.Status.Phase)\n\treturn false\n}\n\nfunc (*SpecUpdatedPredicateForRunningPhase) Generic(e event.TypedGenericEvent[*v1alpha1.ChaosBlade]) bool {\n\treturn false\n}\n"
  },
  {
    "path": "pkg/controller/controller.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage controller\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n)\n\n// AddToManagerFuncs is a list of functions to add all Controllers to the Manager\nvar AddToManagerFuncs []func(manager.Manager) error\n\n// AddToManager adds all Controllers to the Manager\nfunc AddToManager(m manager.Manager) error {\n\tfor _, f := range AddToManagerFuncs {\n\t\tif err := f(m); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/hookfs/client.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hookfs\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/chaosblade-io/chaosblade-spec-go/util\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype ChaosBladeHookClient struct {\n\tclient *http.Client\n\taddr   string\n}\n\nfunc NewChabladeHookClient(addr string) *ChaosBladeHookClient {\n\treturn &ChaosBladeHookClient{\n\t\taddr: addr,\n\t\tclient: &http.Client{\n\t\t\tTimeout: 30 * time.Second,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tDialContext: (&net.Dialer{\n\t\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t\t}).DialContext,\n\t\t\t\tDisableKeepAlives: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (c *ChaosBladeHookClient) InjectFault(ctx context.Context, injectMsg *InjectMessage) error {\n\turl := \"http://\" + c.addr + InjectPath\n\tbody, err := json.Marshal(injectMsg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogrus.WithField(\"injectMsg\", injectMsg).Infoln(\"Inject fault\")\n\tresult, err, code := util.PostCurl(url, body, \"application/json\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogrus.WithField(\"injectMsg\", injectMsg).Infof(\"Response is %s\", result)\n\tif code != http.StatusOK {\n\t\treturn errors.New(result)\n\t}\n\treturn nil\n}\n\nfunc (c *ChaosBladeHookClient) Revoke(ctx context.Context) error {\n\turl := \"http://\" + c.addr + RecoverPath\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := c.client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\tbytes, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresult := string(bytes)\n\tlogrus.Infof(\"Revoke fault, response is %s\", result)\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn errors.New(result)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/hookfs/hook.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hookfs\n\nimport (\n\t\"math/rand\"\n\t\"path\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/ethercflow/hookfs/hookfs\"\n\t\"github.com/hanwen/go-fuse/fuse\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype ChaosbladeHookContext struct{}\n\ntype ChaosbladeHook struct {\n\tMountPoint string\n}\n\nfunc (h *ChaosbladeHook) PreOpen(path string, flags uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"open\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostOpen(int32, hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreRead(path string, length int64, offset int64) ([]byte, bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"read\")\n\tif err != nil {\n\t\treturn nil, true, ctx, err\n\t}\n\treturn nil, false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostRead(realRetCode int32, realBuf []byte, prehookCtx hookfs.HookContext) ([]byte, bool, error) {\n\treturn nil, false, nil\n}\n\nfunc (h *ChaosbladeHook) PreWrite(path string, buf []byte, offset int64) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"write\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostWrite(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreMkdir(path string, mode uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"mkdir\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostMkdir(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreRmdir(path string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"rmdir\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostRmdir(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreOpenDir(path string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"opendir\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostOpenDir(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreFsync(path string, flags uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"fsync\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostFsync(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreFlush(path string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"flush\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostFlush(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreRelease(path string) (bool, hookfs.HookContext) {\n\tctx := &ChaosbladeHookContext{}\n\t_ = h.doInjectFault(path, \"release\")\n\treturn false, ctx\n}\n\nfunc (h *ChaosbladeHook) PostRelease(prehookCtx hookfs.HookContext) (hooked bool) {\n\treturn false\n}\n\nfunc (h *ChaosbladeHook) PreTruncate(path string, size uint64) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"truncate\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostTruncate(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreGetAttr(path string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"getattr\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostGetAttr(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreChown(path string, uid uint32, gid uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"chown\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostChown(realRetCode int32, prehookCtx hookfs.HookContext) (hooked bool, err error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreChmod(path string, perms uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"chmod\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostChmod(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreUtimens(path string, atime *time.Time, mtime *time.Time) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"utimens\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostUtimens(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreAllocate(path string, off uint64, size uint64, mode uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"allocate\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostAllocate(realRetCode int32, prehookCtx hookfs.HookContext) (hooked bool, err error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreGetLk(path string, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"getlk\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostGetLk(realRetCode int32, prehookCtx hookfs.HookContext) (hooked bool, err error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreSetLk(path string, owner uint64, lk *fuse.FileLock, flags uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"setlk\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostSetLk(realRetCode int32, prehookCtx hookfs.HookContext) (hooked bool, err error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreSetLkw(path string, owner uint64, lk *fuse.FileLock, flags uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"setlkw\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostSetLkw(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreStatFs(path string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(path, \"statfs\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostStatFs(prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreReadlink(name string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"readlink\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostReadlink(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreSymlink(value string, linkName string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(value, \"symlink\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\terr = h.doInjectFault(linkName, \"symlink\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostSymlink(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreCreate(name string, flags uint32, mode uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"create\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostCreate(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreAccess(name string, mode uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"access\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostAccess(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreLink(oldName string, newName string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(oldName, \"link\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\terr = h.doInjectFault(newName, \"link\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostLink(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreMknod(name string, mode uint32, dev uint32) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"mknod\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostMknod(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreRename(oldName string, newName string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(oldName, \"rename\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\terr = h.doInjectFault(newName, \"rename\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostRename(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreUnlink(name string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"unlink\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostUnlink(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreGetXAttr(name string, attribute string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"getxattr\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostGetXAttr(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreListXAttr(name string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"listxattr\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostListXAttr(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreRemoveXAttr(name string, attr string) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"removexattr\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostRemoveXAttr(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) PreSetXAttr(name string, attr string, data []byte, flags int) (bool, hookfs.HookContext, error) {\n\tctx := &ChaosbladeHookContext{}\n\terr := h.doInjectFault(name, \"setxattr\")\n\tif err != nil {\n\t\treturn true, ctx, err\n\t}\n\treturn false, ctx, nil\n}\n\nfunc (h *ChaosbladeHook) PostSetXAttr(realRetCode int32, prehookCtx hookfs.HookContext) (bool, error) {\n\treturn false, nil\n}\n\nfunc (h *ChaosbladeHook) doInjectFault(relativePath, method string) error {\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"method\":       method,\n\t\t\"relativePath\": relativePath,\n\t}).Infoln(\"do Inject fault\")\n\tval, ok := injectFaultCache.Load(method)\n\tif !ok || val == nil {\n\t\treturn nil\n\t}\n\tfaultMsg, ok := val.(*InjectMessage)\n\tif !ok {\n\t\tlogrus.Errorf(\"convert to InjectMessage failed, %+v\", val)\n\t\treturn nil\n\t}\n\tlogrus.WithField(\"faultMessage\", faultMsg).Infoln(\"do Inject fault with inject message\")\n\tif faultMsg.Path != \"\" {\n\t\tactualPath := path.Join(h.MountPoint, relativePath)\n\t\tif !strings.HasPrefix(actualPath, faultMsg.Path) {\n\t\t\tlogrus.WithFields(logrus.Fields{\n\t\t\t\t\"rulePath\":   faultMsg.Path,\n\t\t\t\t\"actualPath\": actualPath,\n\t\t\t}).Infoln(\"the rule path does not contain the actual path\")\n\t\t\treturn nil\n\t\t}\n\t}\n\tif faultMsg.Percent > 0 && !probab(faultMsg.Percent) {\n\t\treturn nil\n\t}\n\tvar err error = nil\n\tif faultMsg.Errno != 0 {\n\t\terr = syscall.Errno(faultMsg.Errno)\n\t} else if faultMsg.Random {\n\t\terr = randomErrno()\n\t}\n\tif faultMsg.Delay > 0 {\n\t\ttime.Sleep(time.Duration(faultMsg.Delay) * time.Millisecond)\n\t}\n\treturn err\n}\n\nfunc randomErrno() error {\n\t// from E2BIG to EXFULL, notice linux only\n\treturn syscall.Errno(rand.Intn(0x36-0x7) + 0x7)\n}\n\nfunc probab(percentage uint32) bool {\n\treturn rand.Intn(99) < int(percentage)\n}\n"
  },
  {
    "path": "pkg/hookfs/server.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hookfs\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar injectFaultCache sync.Map\n\nfunc init() {\n\tinjectFaultCache = sync.Map{}\n}\n\ntype InjectMessage struct {\n\tMethods []string `json:\"methods\"`\n\tPath    string   `json:\"path\"`\n\tDelay   uint32   `json:\"delay\"`\n\tPercent uint32   `json:\"percent\"`\n\tRandom  bool     `json:\"random\"`\n\tErrno   uint32   `json:\"errno\"`\n}\n\ntype ChaosbladeHookServer struct {\n\taddr string\n}\n\nfunc NewChaosbladeHookServer(addr string) *ChaosbladeHookServer {\n\treturn &ChaosbladeHookServer{\n\t\taddr: addr,\n\t}\n}\n\nfunc (s *ChaosbladeHookServer) Start(stop context.Context) error {\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(InjectPath, s.InjectHandler)\n\tmux.HandleFunc(RecoverPath, s.RecoverHandler)\n\terrCh := make(chan error)\n\tserver := &http.Server{\n\t\tAddr:    s.addr,\n\t\tHandler: mux,\n\t}\n\tgo func() {\n\t\terrCh <- server.ListenAndServe()\n\t}()\n\tfor {\n\t\tselect {\n\t\tcase <-stop.Done():\n\t\t\treturn server.Shutdown(context.Background())\n\t\tcase err := <-errCh:\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *ChaosbladeHookServer) InjectHandler(w http.ResponseWriter, r *http.Request) {\n\tvar injectMsg InjectMessage\n\tif err := json.NewDecoder(r.Body).Decode(&injectMsg); err != nil {\n\t\tlogrus.WithError(err).Errorf(\"Cannot Decode Request Message, %+v\", r)\n\t\thttp.Error(w, \"Cannot Decode Request Message\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tlogrus.WithField(\"injectMsg\", injectMsg).Infoln(\"Inject Fault\")\n\tfor _, method := range injectMsg.Methods {\n\t\tinjectFaultCache.Store(method, &injectMsg)\n\t}\n\tfmt.Fprintf(w, \"success\")\n}\n\nfunc (s *ChaosbladeHookServer) RecoverHandler(w http.ResponseWriter, r *http.Request) {\n\tlogrus.Infoln(\"recover all fault\")\n\tfor _, method := range defaultHookPoints {\n\t\tinjectFaultCache.Delete(method)\n\t}\n\tfmt.Fprintf(w, \"success\")\n}\n"
  },
  {
    "path": "pkg/hookfs/types.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage hookfs\n\nvar defaultHookPoints = []string{\n\t\"read\",\n\t\"write\",\n\t\"mkdir\",\n\t\"rmdir\",\n\t\"opendir\",\n\t\"fsync\",\n\t\"flush\",\n\t\"release\",\n\t\"truncate\",\n\t\"getattr\",\n\t\"chown\",\n\t\"utimens\",\n\t\"allocate\",\n\t\"getlk\",\n\t\"setlk\",\n\t\"setlkw\",\n\t\"statfs\",\n\t\"readlink\",\n\t\"symlink\",\n\t\"create\",\n\t\"access\",\n\t\"link\",\n\t\"mknod\",\n\t\"rename\",\n\t\"unlink\",\n\t\"getxattr\",\n\t\"listxattr\",\n\t\"removexattr\",\n\t\"setxattr\",\n}\n\nvar (\n\tInjectPath  = \"/inject\"\n\tRecoverPath = \"/recover\"\n)\n"
  },
  {
    "path": "pkg/runtime/chaosblade/chaosblade.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage chaosblade\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/version\"\n)\n\nvar (\n\tImageRepository     string\n\tVersion             string\n\tPullPolicy          string\n\tDaemonsetEnable     bool\n\tRemoveBladeInterval string\n\tDownloadUrl         string\n)\n\nconst (\n\tOperatorChaosBladePath  = \"/opt/chaosblade\"\n\tOperatorChaosBladeBin   = \"/opt/chaosblade/bin\"\n\tOperatorChaosBladeLib   = \"/opt/chaosblade/lib\"\n\tOperatorChaosBladeYaml  = \"/opt/chaosblade/yaml\"\n\tOperatorChaosBladeBlade = \"/opt/chaosblade/blade\"\n)\n\nconst (\n\tDaemonsetPodName           = \"chaosblade-tool\"\n\tDefaultRemoveBladeInterval = \"72h\"\n)\n\nvar DaemonsetPodLabels = map[string]string{\n\t\"app\": \"chaosblade-tool\",\n}\n\n// set in runtime\nvar (\n\tDaemonsetPodNamespace string\n\tDaemonsetPodNames     = map[string]string{}\n)\n\nvar Products = map[string]*ProductConstant{}\n\nvar Constant *ProductConstant\n\ntype ProductConstant struct {\n\tImageRepoFunc func() string\n}\n\nvar f *pflag.FlagSet\n\nfunc init() {\n\tf = pflag.NewFlagSet(\"chaosblade\", pflag.ExitOnError)\n\t// chaosblade config\n\tf.StringVar(&Version, \"chaosblade-version\", version.Version, \"Chaosblade tool version\")\n\tf.StringVar(&ImageRepository, \"chaosblade-image-repository\", \"chaosbladeio/chaosblade-tool\", \"Image repository of chaosblade tool\")\n\tf.StringVar(&PullPolicy, \"chaosblade-image-pull-policy\", \"IfNotPresent\", \"Pulling policy of chaosblade image, default value is IfNotPresent.\")\n\tf.BoolVar(&DaemonsetEnable, \"daemonset-enable\", false, \"Deploy chaosblade daemonset to resolve chaos experiment environment of network, default value is false.\")\n\tf.StringVar(&RemoveBladeInterval, \"remove-blade-interval\", DefaultRemoveBladeInterval, \"Periodically clean up blade state is destroying, default value is 24h.\")\n\tf.StringVar(&DownloadUrl, \"chaosblade-download-url\", \"\", \"The chaosblade downloaded address which works when the chaosblade is deployed in download mode.\")\n\tf.StringVar(&DaemonsetPodNamespace, \"chaosblade-namespace\", \"chaosblade\", \"The chaosblade deployment namespace\")\n}\n\nfunc FlagSet() *pflag.FlagSet {\n\treturn f\n}\n"
  },
  {
    "path": "pkg/runtime/product/aliyun/aliyun.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage aliyun\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n)\n\nvar (\n\t// flag\n\tRegionId    string\n\tEnvironment string\n)\n\nconst (\n\tAHAS         = \"ahas\"\n\tprodEnv      = \"prod\"\n\tpublicRegion = \"cn-public\"\n)\n\nvar f *pflag.FlagSet\n\nfunc init() {\n\tf = pflag.NewFlagSet(\"aliyun\", pflag.ExitOnError)\n\tf.StringVar(&RegionId, \"aliyun-region-id\", \"\", \"Region id for cloud provider\")\n\tf.StringVar(&Environment, \"aliyun-environment\", \"\", \"Environment for cloud provider\")\n\n\tchaosblade.Products[AHAS] = &chaosblade.ProductConstant{\n\t\tImageRepoFunc: ImageRepoForAliyun,\n\t}\n}\n\nvar ImageRepoForAliyun = func() string {\n\tif RegionId == publicRegion {\n\t\tif Environment == prodEnv {\n\t\t\treturn fmt.Sprintf(\"registry.cn-hangzhou.aliyuncs.com/ahascr-public/chaosblade-tool\")\n\t\t}\n\t\treturn fmt.Sprintf(\"registry.cn-hangzhou.aliyuncs.com/ahas-public/chaosblade-tool\")\n\t}\n\tif Environment == prodEnv {\n\t\treturn fmt.Sprintf(\"registry-vpc.%s.aliyuncs.com/ahascr/chaosblade-tool\", RegionId)\n\t}\n\treturn fmt.Sprintf(\"registry-vpc.%s.aliyuncs.com/ahas/chaosblade-tool\", RegionId)\n}\n\nfunc FlagSet() *pflag.FlagSet {\n\treturn f\n}\n"
  },
  {
    "path": "pkg/runtime/product/community/community.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage community\n\nimport (\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n)\n\nconst Community = \"community\"\n\nfunc init() {\n\tchaosblade.Products[Community] = &chaosblade.ProductConstant{\n\t\tImageRepoFunc: ImageRepoForCommunity,\n\t}\n}\n\nvar ImageRepoForCommunity = func() string {\n\treturn chaosblade.ImageRepository\n}\n"
  },
  {
    "path": "pkg/runtime/runtime.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage runtime\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/product/aliyun\"\n\t_ \"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/product/community\"\n\t\"github.com/chaosblade-io/chaosblade-operator/version\"\n)\n\nvar (\n\tflagSet                 *pflag.FlagSet\n\tLogLevel                string\n\tMaxConcurrentReconciles int\n\tQPS                     float32\n)\n\nfunc init() {\n\tflagSet = pflag.NewFlagSet(\"operator\", pflag.ExitOnError)\n\tflagSet.StringVar(&LogLevel, \"log-level\", \"info\", \"Log level, such as panic|fatal|error|warn|info|debug|trace\")\n\tflagSet.IntVar(&MaxConcurrentReconciles, \"reconcile-count\", 20, \"Max concurrent reconciles count, default value is 20\")\n\tflagSet.Float32Var(&QPS, \"qps\", 20, \"qps of kubernetes client\")\n\n\tflagSet.AddFlagSet(aliyun.FlagSet())\n\tflagSet.AddFlagSet(chaosblade.FlagSet())\n\n\tinitRuntimeData()\n}\n\nfunc initRuntimeData() {\n\tchaosblade.Constant = chaosblade.Products[version.Product]\n}\n\nfunc FlagSet() *pflag.FlagSet {\n\treturn flagSet\n}\n"
  },
  {
    "path": "pkg/webhook/pod/mutator.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"path\"\n\n\t\"github.com/sirupsen/logrus\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/webhook/admission\"\n\n\t\"github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade\"\n\t\"github.com/chaosblade-io/chaosblade-operator/version\"\n)\n\nvar (\n\tFuseServerPort int32\n\tSidecarImage   string\n)\n\nconst (\n\tSidecarName        = \"chaosblade-fuse\"\n\tFuseServerPortName = \"fuse-port\"\n)\n\n// PodMutator set default values for pod\ntype Mutator struct {\n\tclient  client.Client\n\tdecoder admission.Decoder\n}\n\nfunc (v *Mutator) Handle(ctx context.Context, req admission.Request) admission.Response {\n\tpod := &corev1.Pod{}\n\terr := v.decoder.Decode(req, pod)\n\tif err != nil {\n\t\treturn admission.Errored(http.StatusBadRequest, err)\n\t}\n\tpatchPod := pod.DeepCopy()\n\terr = v.mutatePodsFn(patchPod)\n\tif err != nil {\n\t\tlogrus.WithError(err).Errorln(\"mutate pod failed\")\n\t\treturn admission.Errored(http.StatusInternalServerError, err)\n\t}\n\toriginalBytes, err := json.Marshal(pod)\n\tif err != nil {\n\t\tlogrus.WithError(err).Errorln(\"Marshal original pod err\")\n\t\treturn admission.Allowed(\"\")\n\t}\n\texpectedBytes, err := json.Marshal(patchPod)\n\tif err != nil {\n\t\tlogrus.WithError(err).Errorln(\"Marshal patched pod err\")\n\t}\n\treturn admission.PatchResponseFromRaw(originalBytes, expectedBytes)\n}\n\n// PodMutator set default values for pod\nfunc (v *Mutator) mutatePodsFn(pod *corev1.Pod) error {\n\tif pod.Annotations == nil {\n\t\treturn nil\n\t}\n\tinjectVolumeName, ok := pod.Annotations[\"chaosblade/inject-volume\"]\n\tif !ok {\n\t\tlogrus.WithField(\"name\", pod.Name).Infoln(\"pod has no chaosblade/inject-volume annotation\")\n\t\treturn nil\n\t}\n\tinjectSubPath, ok := pod.Annotations[\"chaosblade/inject-volume-subpath\"]\n\tif !ok {\n\t\tlogrus.WithField(\"name\", pod.Name).Infoln(\"pod has no chaosblade/inject-volume annotation\")\n\t\treturn nil\n\t}\n\n\tfor _, container := range pod.Spec.Containers {\n\t\tif container.Name == SidecarName {\n\t\t\tlogrus.WithField(\"name\", pod.Name).Infoln(\"sidecar has been injected\")\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tvar targetVolumeMount corev1.VolumeMount\n\t// inject sidecar for the first container\n\tfor _, volumeMount := range pod.Spec.Containers[0].VolumeMounts {\n\t\tif volumeMount.Name == injectVolumeName {\n\t\t\tif volumeMount.MountPropagation == nil {\n\t\t\t\treturn fmt.Errorf(\"target volume mount propagation must be HostToContainer or Bidirectional\")\n\t\t\t}\n\t\t\tif *volumeMount.MountPropagation != corev1.MountPropagationHostToContainer &&\n\t\t\t\t*volumeMount.MountPropagation != corev1.MountPropagationBidirectional {\n\t\t\t\treturn fmt.Errorf(\"target volume mount propagation is not support\")\n\t\t\t}\n\t\t\ttargetVolumeMount = volumeMount\n\t\t\tmountPropagation := corev1.MountPropagationBidirectional\n\t\t\ttargetVolumeMount.MountPropagation = &mountPropagation\n\t\t}\n\t}\n\n\tif targetVolumeMount.Name == \"\" {\n\t\treturn fmt.Errorf(\"pod has no volume mount %s\", injectVolumeName)\n\t}\n\n\tprivileged := true\n\trunAsUser := int64(0) // root\n\tmountPoint := path.Join(targetVolumeMount.MountPath, injectSubPath)\n\toriginal := path.Join(targetVolumeMount.MountPath, fmt.Sprintf(\"fuse-%s\", injectSubPath))\n\tlogrus.WithFields(logrus.Fields{\n\t\t\"mountPoint\": mountPoint,\n\t\t\"mountPath\":  targetVolumeMount.MountPath,\n\t\t\"podName\":    pod.Name,\n\t}).Infof(\"Get matched pod\")\n\tif mountPoint == targetVolumeMount.MountPath {\n\t\toriginal = path.Join(path.Dir(targetVolumeMount.MountPath),\n\t\t\tfmt.Sprintf(\"fuse-%s\", path.Base(targetVolumeMount.MountPath)))\n\t}\n\tsidecar := corev1.Container{\n\t\tName:            SidecarName,\n\t\tImage:           GetSidecarImage(),\n\t\tImagePullPolicy: corev1.PullAlways,\n\t\tCommand: []string{\n\t\t\t\"/opt/chaosblade/bin/chaos_fuse\",\n\t\t},\n\n\t\tArgs: []string{\n\t\t\tfmt.Sprintf(\"--address=:%d\", FuseServerPort),\n\t\t\tfmt.Sprintf(\"--mountpoint=%s\", mountPoint),\n\t\t\tfmt.Sprintf(\"--original=%s\", original),\n\t\t},\n\t\tResources: corev1.ResourceRequirements{\n\t\t\tRequests: corev1.ResourceList{\n\t\t\t\tcorev1.ResourceCPU:    resource.MustParse(\"100m\"),\n\t\t\t\tcorev1.ResourceMemory: resource.MustParse(\"50Mi\"),\n\t\t\t},\n\t\t\tLimits: corev1.ResourceList{\n\t\t\t\tcorev1.ResourceCPU:    resource.MustParse(\"100m\"),\n\t\t\t\tcorev1.ResourceMemory: resource.MustParse(\"50Mi\"),\n\t\t\t},\n\t\t},\n\t\tPorts: []corev1.ContainerPort{\n\t\t\t{\n\t\t\t\tName:          FuseServerPortName,\n\t\t\t\tContainerPort: FuseServerPort,\n\t\t\t},\n\t\t},\n\t\tSecurityContext: &corev1.SecurityContext{\n\t\t\tPrivileged: &privileged,\n\t\t\tRunAsUser:  &runAsUser,\n\t\t},\n\t\tVolumeMounts: []corev1.VolumeMount{\n\t\t\ttargetVolumeMount,\n\t\t},\n\t}\n\tcontainers := []corev1.Container{}\n\tcontainers = append(containers, sidecar, pod.Spec.Containers[0])\n\tpod.Spec.Containers = containers\n\treturn nil\n}\n\n// InjectClient injects the client.\nfunc (v *Mutator) InjectClient(c client.Client) error {\n\tv.client = c\n\treturn nil\n}\n\n// InjectDecoder injects the decoder.\nfunc (v *Mutator) InjectDecoder(d admission.Decoder) error {\n\tv.decoder = d\n\treturn nil\n}\n\nfunc GetSidecarImage() string {\n\tif SidecarImage != \"\" {\n\t\treturn SidecarImage\n\t}\n\tif chaosblade.Constant != nil {\n\t\treturn fmt.Sprintf(\"%s:%s\", chaosblade.Constant.ImageRepoFunc(), version.Version)\n\t}\n\t// Fallback for testing when chaosblade.Constant is not initialized\n\treturn fmt.Sprintf(\"%s:%s\", chaosblade.ImageRepository, version.Version)\n}\n"
  },
  {
    "path": "pkg/webhook/pod/mutator_test.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage pod\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc Test_mutatePodsFn(t *testing.T) {\n\tbidirectional := v1.MountPropagationBidirectional\n\t// hostToContainer := v1.MountPropagationHostToContainer\n\tNone := v1.MountPropagationNone\n\ttests := []struct {\n\t\tpod *v1.Pod\n\t\terr error\n\t}{\n\t\t{\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: \"pod-0\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\"chaosblade/inject-volume\":         \"fuse-test\",\n\t\t\t\t\t\t\"chaosblade/inject-volume-subpath\": \"data\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  \"test-0\",\n\t\t\t\t\t\t\tImage: \"test-0\",\n\t\t\t\t\t\t\tVolumeMounts: []v1.VolumeMount{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:             \"fuse-test\",\n\t\t\t\t\t\t\t\t\tMountPath:        \"/data\",\n\t\t\t\t\t\t\t\t\tMountPropagation: &bidirectional,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\terr: nil,\n\t\t},\n\t\t{\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: \"pod-1\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\"chaosblade/inject-volume\":         \"fuse-test\",\n\t\t\t\t\t\t\"chaosblade/inject-volume-subpath\": \"/data\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  \"test-1\",\n\t\t\t\t\t\t\tImage: \"test-1\",\n\t\t\t\t\t\t\tVolumeMounts: []v1.VolumeMount{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:             \"data\",\n\t\t\t\t\t\t\t\t\tMountPath:        \"/data\",\n\t\t\t\t\t\t\t\t\tMountPropagation: &bidirectional,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\terr: fmt.Errorf(\"pod has no volume mount fuse-test\"),\n\t\t},\n\t\t{\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: \"pod-2\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\"chaosblade/inject-volume\":         \"data\",\n\t\t\t\t\t\t\"chaosblade/inject-volume-subpath\": \"/data\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  \"test-2\",\n\t\t\t\t\t\t\tImage: \"test-2\",\n\t\t\t\t\t\t\tVolumeMounts: []v1.VolumeMount{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:      \"data\",\n\t\t\t\t\t\t\t\t\tMountPath: \"/data\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\terr: fmt.Errorf(\"target volume mount propagation must be HostToContainer or Bidirectional\"),\n\t\t},\n\t\t{\n\t\t\tpod: &v1.Pod{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: \"pod-3\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\"chaosblade/inject-volume\":         \"data\",\n\t\t\t\t\t\t\"chaosblade/inject-volume-subpath\": \"/data\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  \"test-3\",\n\t\t\t\t\t\t\tImage: \"test-3\",\n\t\t\t\t\t\t\tVolumeMounts: []v1.VolumeMount{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tName:             \"data\",\n\t\t\t\t\t\t\t\t\tMountPath:        \"/data\",\n\t\t\t\t\t\t\t\t\tMountPropagation: &None,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\terr: fmt.Errorf(\"target volume mount propagation is not support\"),\n\t\t},\n\t}\n\n\tmutator := &Mutator{}\n\tfor _, test := range tests {\n\t\terr := mutator.mutatePodsFn(test.pod)\n\t\tif err != nil && err.Error() != test.err.Error() {\n\t\t\tt.Errorf(\"unexpected result %v, expected result: %v\", err, test.err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/webhook/webhook.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage webhook\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\tmutator \"github.com/chaosblade-io/chaosblade-operator/pkg/webhook/pod\"\n)\n\nvar (\n\tPort   int\n\tEnable bool\n)\n\nvar f *pflag.FlagSet\n\nfunc init() {\n\tf = pflag.NewFlagSet(\"webhook\", pflag.ExitOnError)\n\tf.StringVar(&mutator.SidecarImage, \"fuse-sidecar-image\", \"\", \"Fuse sidecar image\")\n\tf.Int32Var(&mutator.FuseServerPort, \"fuse-server-port\", 65534, \"Fuse server port\")\n\n\tf.IntVar(&Port, \"webhook-port\", 9443, \"The port on which to serve HTTPS.\")\n\tf.BoolVar(&Enable, \"webhook-enable\", false, \"Whether to enable webhook\")\n}\n\nfunc FlagSet() *pflag.FlagSet {\n\treturn f\n}\n"
  },
  {
    "path": "scripts/show-version.sh",
    "content": "#!/bin/bash\n# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\n# 版本信息展示脚本\n# 用于快速查看当前项目的版本信息\n\nset -e\n\necho \"=== ChaosBlade Operator 版本信息 ===\"\n\n# 获取Git标签版本\nGIT_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo \"无标签\")\nGIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo \"unknown\")\nGIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"unknown\")\nBUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')\nGO_VERSION=$(go version | awk '{print $3}')\n\necho \"Git标签: $GIT_TAG\"\necho \"Git提交: $GIT_COMMIT\"\necho \"Git分支: $GIT_BRANCH\"\necho \"构建时间: $BUILD_TIME\"\necho \"Go版本: $GO_VERSION\"\necho \"平台: $(go env GOOS)/$(go env GOARCH)\"\n\n# 如果存在构建产物，显示其版本信息\nif [ -f \"target/chaosblade-*/bin/chaosblade-operator\" ]; then\n    echo \"\"\n    echo \"=== 构建产物版本信息 ===\"\n    target/chaosblade-*/bin/chaosblade-operator --version 2>/dev/null || echo \"无法获取构建产物版本信息\"\nfi\n\necho \"\"\necho \"=== 构建命令示例 ===\"\necho \"显示版本信息: make show-version\"\necho \"构建二进制: make build_binary\"\necho \"构建Docker镜像: make docker-build\"\necho \"完整构建: make build_all\"\necho \"======================\"\n"
  },
  {
    "path": "scripts/version.sh",
    "content": "#!/bin/bash\n# Copyright 2025 The ChaosBlade Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\n# 获取版本信息的脚本\n# 用于在构建时注入版本信息到二进制文件中\n\nset -e\n\n# 获取Git标签版本\nget_git_version() {\n    # 优先使用最新的tag\n    local git_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo \"v0.0.0\")\n    # 移除v前缀\n    echo \"${git_tag#v}\"\n}\n\n# 获取Git提交哈希\nget_git_commit() {\n    git rev-parse --short HEAD 2>/dev/null || echo \"unknown\"\n}\n\n# 获取Git分支\nget_git_branch() {\n    git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"unknown\"\n}\n\n# 获取构建时间\nget_build_time() {\n    date -u '+%Y-%m-%dT%H:%M:%SZ'\n}\n\n# 获取Go版本\nget_go_version() {\n    go version | awk '{print $3}'\n}\n\n# 获取平台信息\nget_platform() {\n    echo \"$(go env GOOS)/$(go env GOARCH)\"\n}\n\n# 主函数\nmain() {\n    local version=$(get_git_version)\n    local commit=$(get_git_commit)\n    local branch=$(get_git_branch)\n    local build_time=$(get_build_time)\n    local go_version=$(get_go_version)\n    local platform=$(get_platform)\n    \n    # 输出版本信息，用于Makefile中的ldflags\n    echo \"VERSION=$version\"\n    echo \"GIT_COMMIT=$commit\"\n    echo \"GIT_BRANCH=$branch\"\n    echo \"BUILD_TIME=$build_time\"\n    echo \"GO_VERSION=$go_version\"\n    echo \"PLATFORM=$platform\"\n}\n\n# 如果直接运行此脚本，则输出所有版本信息\nif [[ \"${BASH_SOURCE[0]}\" == \"${0}\" ]]; then\n    main\nfi\n"
  },
  {
    "path": "version/version.go",
    "content": "/*\n * Copyright 2025 The ChaosBlade Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage version\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst criVersion = \"1.5.0\"\n\nvar (\n\t// 这些变量将在编译时通过 ldflags 注入\n\tVersion   = \"unknown\"\n\tProduct   = \"community\"\n\tBuildTime = \"unknown\"\n\tGitCommit = \"unknown\"\n\tGitBranch = \"unknown\"\n\tGoVersion = \"unknown\"\n\tPlatform  = \"unknown\"\n\n\t// Version#Product\n\tCombinedVersion = \"\"\n\tDelimiter       = \",\"\n)\n\nfunc init() {\n\tif CombinedVersion != \"\" {\n\t\tfields := strings.Split(CombinedVersion, Delimiter)\n\t\tif len(fields) > 0 {\n\t\t\tVersion = fields[0]\n\t\t}\n\t\tif len(fields) > 1 {\n\t\t\tProduct = fields[1]\n\t\t}\n\t}\n}\n\n// GetVersionInfo 返回完整的版本信息\nfunc GetVersionInfo() map[string]string {\n\treturn map[string]string{\n\t\t\"version\":   Version,\n\t\t\"product\":   Product,\n\t\t\"buildTime\": BuildTime,\n\t\t\"gitCommit\": GitCommit,\n\t\t\"gitBranch\": GitBranch,\n\t\t\"goVersion\": GoVersion,\n\t\t\"platform\":  Platform,\n\t}\n}\n\n// GetVersionString 返回格式化的版本字符串\nfunc GetVersionString() string {\n\treturn fmt.Sprintf(\"Version: %s, Product: %s, BuildTime: %s, GitCommit: %s, GitBranch: %s, GoVersion: %s, Platform: %s\",\n\t\tVersion, Product, BuildTime, GitCommit, GitBranch, GoVersion, Platform)\n}\n\n// GetShortVersion 返回简短版本信息\nfunc GetShortVersion() string {\n\treturn fmt.Sprintf(\"%s-%s\", Version, Product)\n}\n\nfunc CheckVerisonHaveCriCommand() bool {\n\tverisonA := strings.Split(Version, \".\")\n\tcriA := strings.Split(criVersion, \".\")\n\tif len(verisonA) != 3 {\n\t\treturn false\n\t}\n\n\tfor k, v := range verisonA {\n\t\tvi, err := strconv.Atoi(v)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tci, _ := strconv.Atoi(criA[k])\n\n\t\tif ci == vi {\n\t\t\tcontinue\n\t\t}\n\n\t\tif vi < ci {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\treturn true\n}\n"
  }
]