[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: github-actions\n  directory: /\n  schedule:\n    interval: weekly\n  commit-message:\n    prefix: chore\n    include: scope\n\n- package-ecosystem: gomod\n  directory: /\n  schedule:\n    interval: weekly\n  commit-message:\n    prefix: chore\n    include: scope\n  groups:\n    kubernetes:\n      patterns:\n        - \"k8s.io/*\"\n"
  },
  {
    "path": ".github/workflows/bash-frozen.yml",
    "content": "name: Bash scripts frozen\n\non:\n  pull_request:\n    paths:\n      - 'kubectx'\n      - 'kubens'\n\njobs:\n  comment:\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n    steps:\n      - name: Comment on PR if author is not ahmetb\n        if: github.event.pull_request.user.login != 'ahmetb'\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const body = [\n              '> [!WARNING]',\n              '> **This PR will not be merged.**',\n              '>',\n              '> The bash implementation of `kubectx` and `kubens` is **frozen** and is provided only for convenience.',\n              '> We are not accepting any improvements to the bash scripts.',\n              '>',\n              '> Please propose your improvements to the **Go implementation** instead.',\n            ].join('\\n');\n\n            await github.rest.issues.createComment({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issue_number: context.issue.number,\n              body: body\n            });\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "# Copyright 2021 Google LLC\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: Go implementation (CI)\non:\n  push:\n  pull_request:\njobs:\n  ci:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@master\n    - name: Setup Go\n      uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n    - id: go-cache-paths\n      run: |\n        echo \"::set-output name=go-build::$(go env GOCACHE)\"\n        echo \"::set-output name=go-mod::$(go env GOMODCACHE)\"\n    - name: Go Build Cache\n      uses: actions/cache@v5\n      with:\n        path: ${{ steps.go-cache-paths.outputs.go-build }}\n        key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}\n    - name: Go Mod Cache\n      uses: actions/cache@v5\n      with:\n        path: ${{ steps.go-cache-paths.outputs.go-mod }}\n        key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}\n    - name: Ensure gofmt\n      run: test -z \"$(gofmt -s -d .)\"\n    - name: Ensure go.mod is already tidied\n      run: go mod tidy && git diff --exit-code\n    - name: Run unit tests\n      run: go test ./...\n    - name: Build with Goreleaser\n      uses: goreleaser/goreleaser-action@v7\n      with:\n        version: latest\n        args: release --snapshot --skip publish,snapcraft --clean\n    - name: Setup BATS framework\n      run: sudo npm install -g bats\n    - name: kubectx (Go) integration tests\n      run: COMMAND=./dist/kubectx_linux_amd64_v1/kubectx bats test/kubectx.bats\n    - name: kubens (Go) integration tests\n      run: COMMAND=./dist/kubens_linux_amd64_v1/kubens bats test/kubens.bats\n"
  },
  {
    "path": ".github/workflows/dependabot.yml",
    "content": "name: Dependabot\n\non:\n  pull_request:\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  auto-merge:\n    runs-on: ubuntu-latest\n    if: ${{ github.actor == 'dependabot[bot]' }}\n    steps:\n    - name: Dependabot metadata\n      id: metadata\n      uses: dependabot/fetch-metadata@v2\n\n    - name: Enable auto-merge for Dependabot PRs\n      if: ${{ steps.metadata.outputs.update-type != 'version-update:semver-major' }}\n      run: gh pr merge --auto --merge \"$PR_URL\"\n      env:\n        PR_URL: ${{ github.event.pull_request.html_url }}\n        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# Copyright 2021 Google LLC\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\non:\n  push:\n    tags:\n    - 'v*.*.*'\njobs:\n  goreleaser:\n    permissions:\n      contents: write\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v4\n    - run: git fetch --tags\n    - name: Setup Go\n      uses: actions/setup-go@v6\n      with:\n        go-version: '1.25'\n    - name: Install Snapcraft\n      uses: samuelmeuli/action-snapcraft@v3\n    - name: Setup Snapcraft\n      run: |\n        # https://github.com/goreleaser/goreleaser/issues/1715\n        mkdir -p $HOME/.cache/snapcraft/download\n        mkdir -p $HOME/.cache/snapcraft/stage-packages\n    - name: GoReleaser\n      uses: goreleaser/goreleaser-action@v7\n      with:\n        version: latest\n        args: release --clean\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n    - name: Update new version for plugin 'ctx' in krew-index\n      uses: rajatjindal/krew-release-bot@v0.0.51\n      with:\n        krew_template_file: .krew/ctx.yaml\n    - name: Update new version for plugin 'ns' in krew-index\n      uses: rajatjindal/krew-release-bot@v0.0.51\n      with:\n        krew_template_file: .krew/ns.yaml\n    - name: Publish Snaps to the Snap Store (stable channel)\n      run: for snap in $(ls dist/*.snap); do snapcraft upload --release=stable $snap; done\n      env:\n        SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "# yaml-language-server: $schema=https://goreleaser.com/static/schema.json\n\n# Copyright 2021 Google LLC\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# This is an example goreleaser.yaml file with some sane defaults.\n# Make sure to check the documentation at https://goreleaser.com\n\nversion: 2\nbefore:\n  hooks:\n    - go mod download\nbuilds:\n- id: kubectx\n  main: ./cmd/kubectx\n  binary: kubectx\n  env:\n  - CGO_ENABLED=0\n  goos:\n    - linux\n    - darwin\n    - windows\n  goarch:\n    - amd64\n    - arm\n    - arm64\n    - ppc64le\n    - s390x\n  goarm: [6, 7]\n- id: kubens\n  main: ./cmd/kubens\n  binary: kubens\n  env:\n  - CGO_ENABLED=0\n  goos:\n    - linux\n    - darwin\n    - windows\n  goarch:\n    - amd64\n    - arm\n    - arm64\n    - ppc64le\n    - s390x\n  goarm: [6, 7]\narchives:\n- id: kubectx-archive\n  name_template: |-\n    kubectx_{{ .Tag }}_{{ .Os }}_\n    {{- with .Arch -}}\n      {{- if (eq . \"386\") -}}i386\n      {{- else if (eq . \"amd64\") -}}x86_64\n      {{- else -}}{{- . -}}\n      {{- end -}} \n    {{ end }}\n    {{- with .Arm -}}\n      {{- if (eq . \"6\") -}}hf\n      {{- else -}}v{{- . -}}\n      {{- end -}}\n    {{- end -}}\n  ids:\n    - kubectx\n  format_overrides:\n    - goos: windows\n      formats: [zip]\n  files: [\"LICENSE\"]\n- id: kubens-archive\n  name_template: |-\n    kubens_{{ .Tag }}_{{ .Os }}_\n    {{- with .Arch -}}\n      {{- if (eq . \"386\") -}}i386\n      {{- else if (eq . \"amd64\") -}}x86_64\n      {{- else -}}{{- . -}}\n      {{- end -}}\n    {{ end }}\n    {{- with .Arm -}}\n      {{- if (eq . \"6\") -}}hf\n      {{- else -}}v{{- . -}}\n      {{- end -}}\n    {{- end -}}\n  ids:\n    - kubens\n  format_overrides:\n    - goos: windows\n      formats: [zip]\n  files: [\"LICENSE\"]\nchecksum:\n  name_template: \"checksums.txt\"\n  algorithm: sha256\nrelease:\n  extra_files:\n    - glob: ./kubens\n    - glob: ./kubectx\nsnapcrafts:\n  - id: kubectx\n    name: kubectx\n    summary: 'kubectx + kubens: Power tools for kubectl'\n    description: |\n      kubectx is a tool to switch between contexts (clusters) on kubectl faster.\n      kubens is a tool to switch between Kubernetes namespaces (and configure them for kubectl) easily.\n    grade: stable\n    confinement: classic\n    base: core24\n    apps:\n      kubectx:\n        command: kubectx\n        completer: completion/kubectx.bash\n      kubens:\n        command: kubens\n        completer: completion/kubens.bash\n"
  },
  {
    "path": ".krew/ctx.yaml",
    "content": "apiVersion: krew.googlecontainertools.github.com/v1alpha2\nkind: Plugin\nmetadata:\n  name: ctx\nspec:\n  homepage: https://github.com/ahmetb/kubectx\n  shortDescription: Switch between contexts in your kubeconfig\n  version: {{ .TagName }}\n  description: |\n    Also known as \"kubectx\", a utility to switch between context entries in\n    your kubeconfig file efficiently.\n  caveats: |\n    If fzf is installed on your machine, you can interactively choose\n    between the entries using the arrow keys, or by fuzzy searching\n    as you type.\n    See https://github.com/ahmetb/kubectx for customization and details.\n  platforms:\n  - selector:\n      matchExpressions:\n      - key: os\n        operator: In\n        values:\n        - darwin\n        - linux\n    {{addURIAndSha \"https://github.com/ahmetb/kubectx/archive/{{ .TagName }}.tar.gz\" .TagName }}\n    bin: kubectx\n    files:\n    - from: kubectx-*/kubectx\n      to: .\n    - from: kubectx-*/LICENSE\n      to: .\n"
  },
  {
    "path": ".krew/ns.yaml",
    "content": "apiVersion: krew.googlecontainertools.github.com/v1alpha2\nkind: Plugin\nmetadata:\n  name: ns\nspec:\n  homepage: https://github.com/ahmetb/kubectx\n  shortDescription: Switch between Kubernetes namespaces\n  version: {{ .TagName }}\n  description: |\n    Also known as \"kubens\", a utility to set your current namespace and switch\n    between them.\n  caveats: |\n    If fzf is installed on your machine, you can interactively choose\n    between the entries using the arrow keys, or by fuzzy searching\n    as you type.\n  platforms:\n  - selector:\n      matchExpressions:\n      - key: os\n        operator: In\n        values:\n        - darwin\n        - linux\n    {{addURIAndSha \"https://github.com/ahmetb/kubectx/archive/{{ .TagName }}.tar.gz\" .TagName }}\n    bin: kubens\n    files:\n    - from: kubectx-*/kubens\n      to: .\n    - from: kubectx-*/LICENSE\n      to: .\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guidelines you need to follow.\n\n## Contributor License Agreement\n\nContributions to this project must be accompanied by a Contributor License\nAgreement. You (or your employer) retain the copyright to your contribution,\nthis simply gives us permission to use and redistribute your contributions as\npart of the project. Head over to <https://cla.developers.google.com/> to see\nyour current agreements on file or to sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\n## Code reviews\n\nAll submissions, including submissions by project members, require review. We\nuse GitHub pull requests for this purpose. Consult\n[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more\ninformation on using pull requests."
  },
  {
    "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 [yyyy] [name of copyright owner]\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": "README.md",
    "content": "# `kubectx` + `kubens`: Power tools for kubectl\n\n![Latest GitHub release](https://img.shields.io/github/release/ahmetb/kubectx.svg)\n![GitHub stars](https://img.shields.io/github/stars/ahmetb/kubectx.svg?label=github%20stars)\n![Homebrew downloads](https://img.shields.io/homebrew/installs/dy/kubectx?label=macOS%20installs)\n[![Go implementation (CI)](https://github.com/ahmetb/kubectx/workflows/Go%20implementation%20(CI)/badge.svg)](https://github.com/ahmetb/kubectx/actions?query=workflow%3A\"Go+implementation+(CI)\")\n![Proudly written in Bash](https://img.shields.io/badge/written%20in-bash-ff69b4.svg)\n\nThis repository provides both `kubectx` and `kubens` tools.\n[Install &rarr;](#installation)\n\n## What are `kubectx` and `kubens`?\n\n**kubectx** is a tool to switch between contexts (clusters) on kubectl\nfaster.<br/>\n**kubens** is a tool to switch between Kubernetes namespaces (and\nconfigure them for kubectl) easily.\n\nHere's a **`kubectx`** demo:\n![kubectx demo GIF](img/kubectx-demo.gif)\n\n...and here's a **`kubens`** demo:\n![kubens demo GIF](img/kubens-demo.gif)\n\n### Examples\n\n```sh\n# switch to another cluster that's in kubeconfig\n$ kubectx minikube\nSwitched to context \"minikube\".\n\n# switch back to previous cluster\n$ kubectx -\nSwitched to context \"oregon\".\n\n# start an \"isolated shell\" that only has a single context\n$ kubectx -s minikube\n\n# rename context\n$ kubectx dublin=gke_ahmetb_europe-west1-b_dublin\nContext \"gke_ahmetb_europe-west1-b_dublin\" renamed to \"dublin\".\n\n# change the active namespace on kubectl\n$ kubens kube-system\nContext \"test\" set.\nActive namespace is \"kube-system\".\n\n# go back to the previous namespace\n$ kubens -\nContext \"test\" set.\nActive namespace is \"default\".\n\n# change the active namespace even if it doesn't exist\n$ kubens not-found-namespace --force\nContext \"test\" set.\nActive namespace is \"not-found-namespace\".\n---\n$ kubens not-found-namespace -f\nContext \"test\" set.\nActive namespace is \"not-found-namespace\".\n```\n\nIf you have [`fzf`](https://github.com/junegunn/fzf) installed, you can also\n**interactively** select a context or cluster, or fuzzy-search by typing a few\ncharacters. To learn more, read [interactive mode &rarr;](#interactive-mode)\n\nBoth `kubectx` and `kubens` support <kbd>Tab</kbd> completion on bash/zsh/fish\nshells to help with long context names. You don't have to remember full context\nnames anymore.\n\n-----\n\n## Installation\n\n| Package manager | Command |\n|---|---|\n| [Homebrew](https://brew.sh/) (macOS & Linux) | `brew install kubectx` |\n| [MacPorts](https://www.macports.org) (macOS) | `sudo port install kubectx` |\n| apt (Debian/Ubuntu) | `sudo apt install kubectx` |\n| pacman (Arch Linux) | `sudo pacman -S kubectx` |\n| [Chocolatey](https://chocolatey.org/) (Windows) | `choco install kubens kubectx` |\n| [Scoop](https://scoop.sh/) (Windows) | `scoop bucket add main && scoop install main/kubens main/kubectx` |\n| [winget](https://learn.microsoft.com/en-us/windows/package-manager/) (Windows) | `winget install --id ahmetb.kubectx && winget install --id ahmetb.kubens` |\n| [Krew](https://github.com/kubernetes-sigs/krew/) (kubectl plugin) | `kubectl krew install ctx && kubectl krew install ns` |\n\nAlternatively, download binaries from the [**Releases page &rarr;**](https://github.com/ahmetb/kubectx/releases) and add them to somewhere in your `PATH`.\n\n<details>\n<summary>Shell completion scripts</summary>\n\n#### zsh (with [antibody](https://getantibody.github.io))\n\nAdd this line to your [Plugins File](https://getantibody.github.io/usage/) (e.g.\n`~/.zsh_plugins.txt`):\n\n```\nahmetb/kubectx path:completion kind:fpath\n```\n\nDepending on your setup, you might or might not need to call `compinit` or\n`autoload -U compinit && compinit` in your `~/.zshrc` after you load the Plugins\nfile. If you use [oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh), load the\ncompletions before you load `oh-my-zsh` because `oh-my-zsh` will call\n`compinit`.\n\n#### zsh (plain)\n\nThe completion scripts have to be in a path that belongs to `$fpath`. Either\nlink or copy them to an existing folder.\n\nExample with [`oh-my-zsh`](https://github.com/ohmyzsh/ohmyzsh):\n\n```bash\nmkdir -p ~/.oh-my-zsh/custom/completions\nchmod -R 755 ~/.oh-my-zsh/custom/completions\nln -s /opt/kubectx/completion/_kubectx.zsh ~/.oh-my-zsh/custom/completions/_kubectx.zsh\nln -s /opt/kubectx/completion/_kubens.zsh ~/.oh-my-zsh/custom/completions/_kubens.zsh\necho \"fpath=($ZSH/custom/completions $fpath)\" >> ~/.zshrc\n```\n\nIf completion doesn't work, add `autoload -U compinit && compinit` to your\n`.zshrc` (similar to\n[`zsh-completions`](https://github.com/zsh-users/zsh-completions/blob/master/README.md#oh-my-zsh)).\n\nIf you are not using [`oh-my-zsh`](https://github.com/ohmyzsh/ohmyzsh), you\ncould link to `/usr/share/zsh/functions/Completion` (might require sudo),\ndepending on the `$fpath` of your zsh installation.\n\nIn case of errors, calling `compaudit` might help.\n\n#### bash\n\n```bash\ngit clone https://github.com/ahmetb/kubectx.git ~/.kubectx\nCOMPDIR=$(pkg-config --variable=completionsdir bash-completion)\nln -sf ~/.kubectx/completion/kubens.bash $COMPDIR/kubens\nln -sf ~/.kubectx/completion/kubectx.bash $COMPDIR/kubectx\ncat << EOF >> ~/.bashrc\n\n\n#kubectx and kubens\nexport PATH=~/.kubectx:\\$PATH\nEOF\n```\n\n#### fish\n\n```fish\nmkdir -p ~/.config/fish/completions\nln -s /opt/kubectx/completion/kubectx.fish ~/.config/fish/completions/\nln -s /opt/kubectx/completion/kubens.fish ~/.config/fish/completions/\n```\n\n</details>\n\n> [!NOTE]\n> Tip: Show context/namespace in your shell prompt with [oh-my-posh](https://ohmyposh.dev/) or\n> simply with [kube-ps1](https://github.com/jonmosco/kube-ps1).\n\n-----\n\n### Interactive mode\n\nIf you want `kubectx` and `kubens` commands to present you an interactive menu\nwith fuzzy searching, you just need to [install\n`fzf`](https://github.com/junegunn/fzf) in your `$PATH`.\n\n![kubectx interactive search with fzf](img/kubectx-interactive.gif)\n\nCaveats:\n- If you have `fzf` installed, but want to opt out of using this feature, set the\n  environment variable `KUBECTX_IGNORE_FZF=1`.\n- If you want to keep `fzf` interactive mode but need the default behavior of the\n  command, you can do it by piping the output to another command (e.g. `kubectx |\n  cat `).\n\n-----\n\n### Customizing colors\n\nIf you like to customize the colors indicating the current namespace or context,\nset the environment variables `KUBECTX_CURRENT_FGCOLOR` and\n`KUBECTX_CURRENT_BGCOLOR` (refer color codes\n[here](https://linux.101hacks.com/ps1-examples/prompt-color-using-tput/)):\n\n```sh\nexport KUBECTX_CURRENT_FGCOLOR=$(tput setaf 6) # blue text\nexport KUBECTX_CURRENT_BGCOLOR=$(tput setab 7) # white background\n```\n\nColors in the output can be disabled by setting the\n[`NO_COLOR`](https://no-color.org/) environment variable.\n\n-----\n\nIf you liked `kubectx`, you may like my\n[`kubectl-aliases`](https://github.com/ahmetb/kubectl-aliases) project, too. I\nrecommend pairing kubectx and kubens with [fzf](#interactive-mode) and\n[kube-ps1](https://github.com/jonmosco/kube-ps1).\n\n#### Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/ahmetb/kubectx.svg)](https://starchart.cc/ahmetb/kubectx)\n![Google Analytics](https://ga-beacon.appspot.com/UA-2609286-17/kubectx/README?pixel) <!-- TODO broken since Aug 2021 as igrigorik left Google -->\n"
  },
  {
    "path": "cmd/kubectx/current.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n)\n\n// CurrentOp prints the current context\ntype CurrentOp struct{}\n\nfunc (_op CurrentOp) Run(stdout, _ io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tv, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\tif v == \"\" {\n\t\treturn errors.New(\"current-context is not set\")\n\t}\n\tif _, err := fmt.Fprintln(stdout, v); err != nil {\n\t\treturn fmt.Errorf(\"write error: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/kubectx/delete.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\n// DeleteOp indicates intention to delete contexts.\ntype DeleteOp struct {\n\tContexts []string // NAME or '.' to indicate current-context.\n}\n\n// deleteContexts deletes context entries one by one.\nfunc (op DeleteOp) Run(_, stderr io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\tfor _, ctx := range op.Contexts {\n\t\t// TODO inefficiency here. we open/write/close the same file many times.\n\t\tdeletedName, wasActiveContext, err := deleteContext(ctx)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error deleting context \\\"%s\\\": %w\", deletedName, err)\n\t\t}\n\t\tif wasActiveContext {\n\t\t\tprinter.Warning(stderr, \"You deleted the current context. Use \\\"%s\\\" to select a new context.\",\n\t\t\t\tselfName())\n\t\t}\n\n\t\t_ = printer.Success(stderr, `Deleted context %s.`, printer.SuccessColor.Sprint(deletedName))\n\t}\n\treturn nil\n}\n\n// deleteContext deletes a context entry by NAME or current-context\n// indicated by \".\".\nfunc deleteContext(name string) (deleteName string, wasActiveContext bool, err error) {\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn deleteName, false, fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tcur, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn deleteName, false, fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\t// resolve \".\" to a real name\n\tif name == \".\" {\n\t\tif cur == \"\" {\n\t\t\treturn deleteName, false, errors.New(\"can't use '.' as the no active context is set\")\n\t\t}\n\t\twasActiveContext = true\n\t\tname = cur\n\t}\n\n\texists, err := kc.ContextExists(name)\n\tif err != nil {\n\t\treturn name, false, fmt.Errorf(\"failed to check context: %w\", err)\n\t}\n\tif !exists {\n\t\treturn name, false, errors.New(\"context does not exist\")\n\t}\n\n\tif err := kc.DeleteContextEntry(name); err != nil {\n\t\treturn name, false, fmt.Errorf(\"failed to modify yaml doc: %w\", err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\treturn name, wasActiveContext, fmt.Errorf(\"failed to save modified kubeconfig file: %w\", err)\n\t}\n\treturn name, wasActiveContext, nil\n}\n"
  },
  {
    "path": "cmd/kubectx/env.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n"
  },
  {
    "path": "cmd/kubectx/flags.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n)\n\n// UnsupportedOp indicates an unsupported flag.\ntype UnsupportedOp struct{ Err error }\n\nfunc (op UnsupportedOp) Run(_, _ io.Writer) error {\n\treturn op.Err\n}\n\n// parseArgs looks at flags (excl. executable name, i.e. argv[0])\n// and decides which operation should be taken.\nfunc parseArgs(argv []string) Op {\n\tif len(argv) == 0 {\n\t\tif cmdutil.IsInteractiveMode(os.Stdout) {\n\t\t\treturn InteractiveSwitchOp{SelfCmd: os.Args[0]}\n\t\t}\n\t\treturn ListOp{}\n\t}\n\n\tif argv[0] == \"--shell\" || argv[0] == \"-s\" {\n\t\tif len(argv) != 2 {\n\t\t\treturn UnsupportedOp{Err: fmt.Errorf(\"'%s' requires exactly one context name argument\", argv[0])}\n\t\t}\n\t\treturn ShellOp{Target: argv[1]}\n\t}\n\n\tif argv[0] == \"-d\" {\n\t\tif len(argv) == 1 {\n\t\t\tif cmdutil.IsInteractiveMode(os.Stdout) {\n\t\t\t\treturn InteractiveDeleteOp{SelfCmd: os.Args[0]}\n\t\t\t} else {\n\t\t\t\treturn UnsupportedOp{Err: fmt.Errorf(\"'-d' needs arguments\")}\n\t\t\t}\n\t\t}\n\t\treturn DeleteOp{Contexts: argv[1:]}\n\t}\n\n\tif len(argv) == 1 {\n\t\tv := argv[0]\n\t\tif v == \"--help\" || v == \"-h\" {\n\t\t\treturn HelpOp{}\n\t\t}\n\t\tif v == \"--version\" || v == \"-V\" {\n\t\t\treturn VersionOp{}\n\t\t}\n\t\tif v == \"--current\" || v == \"-c\" {\n\t\t\treturn CurrentOp{}\n\t\t}\n\t\tif v == \"--unset\" || v == \"-u\" {\n\t\t\treturn UnsetOp{}\n\t\t}\n\n\t\tif new, old, ok := parseRenameSyntax(v); ok {\n\t\t\treturn RenameOp{New: new, Old: old}\n\t\t}\n\n\t\tif strings.HasPrefix(v, \"-\") && v != \"-\" {\n\t\t\treturn UnsupportedOp{Err: fmt.Errorf(\"unsupported option '%s'\", v)}\n\t\t}\n\t\treturn SwitchOp{Target: argv[0]}\n\t}\n\treturn UnsupportedOp{Err: fmt.Errorf(\"too many arguments\")}\n}\n"
  },
  {
    "path": "cmd/kubectx/flags_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc Test_parseArgs_new(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targs []string\n\t\twant Op\n\t}{\n\t\t{name: \"nil Args\",\n\t\t\targs: nil,\n\t\t\twant: ListOp{}},\n\t\t{name: \"empty Args\",\n\t\t\targs: []string{},\n\t\t\twant: ListOp{}},\n\t\t{name: \"help shorthand\",\n\t\t\targs: []string{\"-h\"},\n\t\t\twant: HelpOp{}},\n\t\t{name: \"help long form\",\n\t\t\targs: []string{\"--help\"},\n\t\t\twant: HelpOp{}},\n\t\t{name: \"current shorthand\",\n\t\t\targs: []string{\"-c\"},\n\t\t\twant: CurrentOp{}},\n\t\t{name: \"current long form\",\n\t\t\targs: []string{\"--current\"},\n\t\t\twant: CurrentOp{}},\n\t\t{name: \"unset shorthand\",\n\t\t\targs: []string{\"-u\"},\n\t\t\twant: UnsetOp{}},\n\t\t{name: \"unset long form\",\n\t\t\targs: []string{\"--unset\"},\n\t\t\twant: UnsetOp{}},\n\t\t{name: \"switch by name\",\n\t\t\targs: []string{\"foo\"},\n\t\t\twant: SwitchOp{Target: \"foo\"}},\n\t\t{name: \"switch by swap\",\n\t\t\targs: []string{\"-\"},\n\t\t\twant: SwitchOp{Target: \"-\"}},\n\t\t{name: \"delete - without contexts\",\n\t\t\targs: []string{\"-d\"},\n\t\t\twant: UnsupportedOp{fmt.Errorf(\"'-d' needs arguments\")}},\n\t\t{name: \"delete - current context\",\n\t\t\targs: []string{\"-d\", \".\"},\n\t\t\twant: DeleteOp{[]string{\".\"}}},\n\t\t{name: \"delete - multiple contexts\",\n\t\t\targs: []string{\"-d\", \".\", \"a\", \"b\"},\n\t\t\twant: DeleteOp{[]string{\".\", \"a\", \"b\"}}},\n\t\t{name: \"rename context\",\n\t\t\targs: []string{\"a=b\"},\n\t\t\twant: RenameOp{\"a\", \"b\"}},\n\t\t{name: \"rename context with old=current\",\n\t\t\targs: []string{\"a=.\"},\n\t\t\twant: RenameOp{\"a\", \".\"}},\n\t\t{name: \"shell shorthand\",\n\t\t\targs: []string{\"-s\", \"prod\"},\n\t\t\twant: ShellOp{Target: \"prod\"}},\n\t\t{name: \"shell long form\",\n\t\t\targs: []string{\"--shell\", \"prod\"},\n\t\t\twant: ShellOp{Target: \"prod\"}},\n\t\t{name: \"shell without context name\",\n\t\t\targs: []string{\"-s\"},\n\t\t\twant: UnsupportedOp{Err: fmt.Errorf(\"'-s' requires exactly one context name argument\")}},\n\t\t{name: \"shell with too many args\",\n\t\t\targs: []string{\"--shell\", \"a\", \"b\"},\n\t\t\twant: UnsupportedOp{Err: fmt.Errorf(\"'--shell' requires exactly one context name argument\")}},\n\t\t{name: \"unrecognized flag\",\n\t\t\targs: []string{\"-x\"},\n\t\t\twant: UnsupportedOp{Err: fmt.Errorf(\"unsupported option '-x'\")}},\n\t\t{name: \"too many args\",\n\t\t\targs: []string{\"a\", \"b\", \"c\"},\n\t\t\twant: UnsupportedOp{Err: fmt.Errorf(\"too many arguments\")}},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := parseArgs(tt.args)\n\n\t\t\tvar opts cmp.Options\n\t\t\tif _, ok := tt.want.(UnsupportedOp); ok {\n\t\t\t\topts = append(opts, cmp.Comparer(func(x, y UnsupportedOp) bool {\n\t\t\t\t\treturn (x.Err == nil && y.Err == nil) || (x.Err.Error() == y.Err.Error())\n\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(got, tt.want, opts...); diff != \"\" {\n\t\t\t\tt.Errorf(\"parseArgs(%#v) diff: %s\", tt.args, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/kubectx/fzf.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n\t\"github.com/ahmetb/kubectx/internal/env\"\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\ntype InteractiveSwitchOp struct {\n\tSelfCmd string\n}\n\ntype InteractiveDeleteOp struct {\n\tSelfCmd string\n}\n\nfunc (op InteractiveSwitchOp) Run(_, stderr io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\t// parse kubeconfig just to see if it can be loaded\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\tif cmdutil.IsNotFoundErr(err) {\n\t\t\tprinter.Warning(stderr, \"kubeconfig file not found\")\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tctxNames, err := kc.ContextNames()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get context names: %w\", err)\n\t}\n\tif len(ctxNames) == 0 {\n\t\treturn errors.New(\"no contexts found in the kubeconfig file\")\n\t}\n\n\tcmd := exec.Command(\"fzf\", \"--ansi\", \"--no-preview\")\n\tvar out bytes.Buffer\n\tcmd.Stdin = os.Stdin\n\tcmd.Stderr = stderr\n\tcmd.Stdout = &out\n\n\tcmd.Env = append(os.Environ(),\n\t\tfmt.Sprintf(\"FZF_DEFAULT_COMMAND=%s\", op.SelfCmd),\n\t\tfmt.Sprintf(\"%s=1\", env.EnvForceColor))\n\tif err := cmd.Run(); err != nil {\n\t\tvar exitErr *exec.ExitError\n\t\tif !errors.As(err, &exitErr) {\n\t\t\treturn err\n\t\t}\n\t}\n\tchoice := strings.TrimSpace(out.String())\n\tif choice == \"\" {\n\t\treturn errors.New(\"you did not choose any of the options\")\n\t}\n\tname, err := switchContext(choice)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to switch context: %w\", err)\n\t}\n\t_ = printer.Success(stderr, \"Switched to context \\\"%s\\\".\", printer.SuccessColor.Sprint(name))\n\treturn nil\n}\n\nfunc (op InteractiveDeleteOp) Run(_, stderr io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\t// parse kubeconfig just to see if it can be loaded\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\tif cmdutil.IsNotFoundErr(err) {\n\t\t\tprinter.Warning(stderr, \"kubeconfig file not found\")\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tctxNames, err := kc.ContextNames()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get context names: %w\", err)\n\t}\n\tif len(ctxNames) == 0 {\n\t\treturn errors.New(\"no contexts found in config\")\n\t}\n\n\tcmd := exec.Command(\"fzf\", \"--ansi\", \"--no-preview\")\n\tvar out bytes.Buffer\n\tcmd.Stdin = os.Stdin\n\tcmd.Stderr = stderr\n\tcmd.Stdout = &out\n\n\tcmd.Env = append(os.Environ(),\n\t\tfmt.Sprintf(\"FZF_DEFAULT_COMMAND=%s\", op.SelfCmd),\n\t\tfmt.Sprintf(\"%s=1\", env.EnvForceColor))\n\tif err := cmd.Run(); err != nil {\n\t\tvar exitErr *exec.ExitError\n\t\tif !errors.As(err, &exitErr) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tchoice := strings.TrimSpace(out.String())\n\tif choice == \"\" {\n\t\treturn errors.New(\"you did not choose any of the options\")\n\t}\n\n\tname, wasActiveContext, err := deleteContext(choice)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete context: %w\", err)\n\t}\n\n\tif wasActiveContext {\n\t\tprinter.Warning(stderr, \"You deleted the current context. Use \\\"%s\\\" to select a new context.\",\n\t\t\tselfName())\n\t}\n\n\t_ = printer.Success(stderr, `Deleted context %s.`, printer.SuccessColor.Sprint(name))\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/kubectx/help.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// HelpOp describes printing help.\ntype HelpOp struct{}\n\nfunc (_ HelpOp) Run(stdout, _ io.Writer) error {\n\treturn printUsage(stdout)\n}\n\nfunc printUsage(out io.Writer) error {\n\thelp := `USAGE:\n  %PROG%                       : list the contexts\n  %PROG% <NAME>                : switch to context <NAME>\n  %PROG% -                     : switch to the previous context\n  %PROG% -c, --current         : show the current context name\n  %PROG% <NEW_NAME>=<NAME>     : rename context <NAME> to <NEW_NAME>\n  %PROG% <NEW_NAME>=.          : rename current-context to <NEW_NAME>\n  %PROG% -u, --unset           : unset the current context\n  %PROG% -d <NAME> [<NAME...>] : delete context <NAME> ('.' for current-context)\n  %SPAC%                         (this command won't delete the user/cluster entry\n  %SPAC%                          referenced by the context entry)\n  %PROG% -s, --shell <NAME>    : start a shell scoped to context <NAME>\n  %PROG% -h,--help             : show this message\n  %PROG% -V,--version          : show version`\n\thelp = strings.ReplaceAll(help, \"%PROG%\", selfName())\n\thelp = strings.ReplaceAll(help, \"%SPAC%\", strings.Repeat(\" \", len(selfName())))\n\n\t_, err := fmt.Fprintf(out, \"%s\\n\", help)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write error: %w\", err)\n\t}\n\treturn nil\n}\n\n// selfName guesses how the user invoked the program.\nfunc selfName() string {\n\tme := filepath.Base(os.Args[0])\n\tpluginPrefix := \"kubectl-\"\n\tif strings.HasPrefix(me, pluginPrefix) {\n\t\treturn \"kubectl \" + strings.TrimPrefix(me, pluginPrefix)\n\t}\n\treturn \"kubectx\"\n}\n"
  },
  {
    "path": "cmd/kubectx/help_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPrintHelp(t *testing.T) {\n\tvar buf bytes.Buffer\n\tif err := (&HelpOp{}).Run(&buf, &buf); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tout := buf.String()\n\tif !strings.Contains(out, \"USAGE:\") {\n\t\tt.Errorf(\"help string doesn't contain USAGE: ; output=\\\"%s\\\"\", out)\n\t}\n\n\tif !strings.HasSuffix(out, \"\\n\") {\n\t\tt.Errorf(\"does not end with New line; output=\\\"%s\\\"\", out)\n\t}\n}\n"
  },
  {
    "path": "cmd/kubectx/isolated_shell_guard.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/ahmetb/kubectx/internal/env\"\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n)\n\nfunc checkIsolatedMode() error {\n\tif os.Getenv(env.EnvIsolatedShell) != \"1\" {\n\t\treturn nil\n\t}\n\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"you are in a locked single-context shell, use 'exit' to leave\")\n\t}\n\n\tcur, _ := kc.GetCurrentContext()\n\treturn fmt.Errorf(\"you are in a locked single-context shell (\\\"%s\\\"), use 'exit' to leave\", cur)\n}\n"
  },
  {
    "path": "cmd/kubectx/list.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"facette.io/natsort\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\n// ListOp describes listing contexts.\ntype ListOp struct{}\n\nfunc (_ ListOp) Run(stdout, stderr io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\tif cmdutil.IsNotFoundErr(err) {\n\t\t\tprinter.Warning(stderr, \"kubeconfig file not found\")\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tctxs, err := kc.ContextNames()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get context names: %w\", err)\n\t}\n\tnatsort.Sort(ctxs)\n\n\tcur, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\tfor _, c := range ctxs {\n\t\ts := c\n\t\tif c == cur {\n\t\t\ts = printer.ActiveItemColor.Sprint(c)\n\t\t}\n\t\tfmt.Fprintf(stdout, \"%s\\n\", s)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/kubectx/main.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n\t\"github.com/ahmetb/kubectx/internal/env\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n\t\"github.com/fatih/color\"\n)\n\ntype Op interface {\n\tRun(stdout, stderr io.Writer) error\n}\n\nfunc main() {\n\tcmdutil.PrintDeprecatedEnvWarnings(color.Error, os.Environ())\n\n\top := parseArgs(os.Args[1:])\n\tif err := op.Run(color.Output, color.Error); err != nil {\n\t\tprinter.Error(color.Error, \"%s\", err)\n\n\t\tif _, ok := os.LookupEnv(env.EnvDebug); ok {\n\t\t\t// print stack trace in verbose mode\n\t\t\tfmt.Fprintf(color.Error, \"[DEBUG] error: %+v\\n\", err)\n\t\t}\n\t\tdefer os.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cmd/kubectx/rename.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\n// RenameOp indicates intention to rename contexts.\ntype RenameOp struct {\n\tNew string // NAME of New context\n\tOld string // NAME of Old context (or '.' for current-context)\n}\n\n// parseRenameSyntax parses A=B form into [A,B] and returns\n// whether it is parsed correctly.\nfunc parseRenameSyntax(v string) (string, string, bool) {\n\tnew, old, ok := strings.Cut(v, \"=\")\n\tif !ok || new == \"\" || old == \"\" {\n\t\treturn \"\", \"\", false\n\t}\n\treturn new, old, true\n}\n\n// rename changes the old (NAME or '.' for current-context)\n// to the \"new\" value. If the old refers to the current-context,\n// current-context preference is also updated.\nfunc (op RenameOp) Run(_, stderr io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tcur, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\tif op.Old == \".\" {\n\t\top.Old = cur\n\t}\n\n\toldExists, err := kc.ContextExists(op.Old)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check context: %w\", err)\n\t}\n\tif !oldExists {\n\t\treturn fmt.Errorf(\"context \\\"%s\\\" not found, can't rename it\", op.Old)\n\t}\n\n\tnewExists, err := kc.ContextExists(op.New)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check context: %w\", err)\n\t}\n\tif newExists {\n\t\tprinter.Warning(stderr, \"context \\\"%s\\\" exists, overwriting it.\", op.New)\n\t\tif err := kc.DeleteContextEntry(op.New); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to delete new context to overwrite it: %w\", err)\n\t\t}\n\t}\n\n\tif err := kc.ModifyContextName(op.Old, op.New); err != nil {\n\t\treturn fmt.Errorf(\"failed to change context name: %w\", err)\n\t}\n\tif op.Old == cur {\n\t\tif err := kc.ModifyCurrentContext(op.New); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set current-context to new name: %w\", err)\n\t\t}\n\t}\n\tif err := kc.Save(); err != nil {\n\t\treturn fmt.Errorf(\"failed to save modified kubeconfig: %w\", err)\n\t}\n\t_ = printer.Success(stderr, \"Context %s renamed to %s.\",\n\t\tprinter.SuccessColor.Sprint(op.Old),\n\t\tprinter.SuccessColor.Sprint(op.New))\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/kubectx/rename_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc Test_parseRenameSyntax(t *testing.T) {\n\n\ttype out struct {\n\t\tNew string\n\t\tOld string\n\t\tOK  bool\n\t}\n\ttests := []struct {\n\t\tname string\n\t\tin   string\n\t\twant out\n\t}{\n\t\t{\n\t\t\tname: \"no equals sign\",\n\t\t\tin:   \"foo\",\n\t\t\twant: out{OK: false},\n\t\t},\n\t\t{\n\t\t\tname: \"no left side\",\n\t\t\tin:   \"=a\",\n\t\t\twant: out{OK: false},\n\t\t},\n\t\t{\n\t\t\tname: \"no right side\",\n\t\t\tin:   \"a=\",\n\t\t\twant: out{OK: false},\n\t\t},\n\t\t{\n\t\t\tname: \"correct format\",\n\t\t\tin:   \"a=b\",\n\t\t\twant: out{\n\t\t\t\tNew: \"a\",\n\t\t\t\tOld: \"b\",\n\t\t\t\tOK:  true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"correct format with current context\",\n\t\t\tin:   \"NEW_NAME=.\",\n\t\t\twant: out{\n\t\t\t\tNew: \"NEW_NAME\",\n\t\t\t\tOld: \".\",\n\t\t\t\tOK:  true,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tnew, old, ok := parseRenameSyntax(tt.in)\n\t\t\tgot := out{\n\t\t\t\tNew: new,\n\t\t\t\tOld: old,\n\t\t\t\tOK:  ok,\n\t\t\t}\n\t\t\tdiff := cmp.Diff(tt.want, got)\n\t\t\tif diff != \"\" {\n\t\t\t\tt.Errorf(\"parseRenameSyntax() diff=%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/kubectx/shell.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/ahmetb/kubectx/internal/env\"\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\n// ShellOp indicates intention to start a scoped sub-shell for a context.\ntype ShellOp struct {\n\tTarget string\n}\n\nfunc (op ShellOp) Run(_, stderr io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\n\tkubectlPath, err := resolveKubectl()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Verify context exists and get current context for exit message\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\texists, err := kc.ContextExists(op.Target)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check context: %w\", err)\n\t}\n\tif !exists {\n\t\treturn fmt.Errorf(\"no context exists with the name: \\\"%s\\\"\", op.Target)\n\t}\n\tpreviousCtx, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\n\t// Extract minimal kubeconfig using kubectl\n\tdata, err := extractMinimalKubeconfig(kubectlPath, op.Target)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to extract kubeconfig for context: %w\", err)\n\t}\n\n\t// Write to temp file\n\ttmpFile, err := os.CreateTemp(\"\", \"kubectx-shell-*.yaml\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temp kubeconfig file: %w\", err)\n\t}\n\ttmpPath := tmpFile.Name()\n\tdefer os.Remove(tmpPath)\n\n\tif _, err := tmpFile.Write(data); err != nil {\n\t\ttmpFile.Close()\n\t\treturn fmt.Errorf(\"failed to write temp kubeconfig: %w\", err)\n\t}\n\ttmpFile.Close()\n\n\t// Print entry message\n\tbadgeColor := color.New(color.BgRed, color.FgWhite, color.Bold)\n\tprinter.EnableOrDisableColor(badgeColor)\n\tfmt.Fprintf(stderr, \"%s kubectl context is %s in this shell — type 'exit' to leave.\\n\",\n\t\tbadgeColor.Sprint(\"[ISOLATED SHELL]\"), printer.WarningColor.Sprint(op.Target))\n\n\t// Detect and start shell\n\tshellBin := detectShell()\n\tcmd := exec.Command(shellBin)\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tcmd.Env = append(os.Environ(),\n\t\t\"KUBECONFIG=\"+tmpPath,\n\t\tenv.EnvIsolatedShell+\"=1\",\n\t)\n\n\t_ = cmd.Run()\n\n\t// Print exit message\n\tfmt.Fprintf(stderr, \"%s kubectl context is now %s.\\n\",\n\t\tbadgeColor.Sprint(\"[ISOLATED SHELL EXITED]\"), printer.WarningColor.Sprint(previousCtx))\n\n\treturn nil\n}\n\nfunc resolveKubectl() (string, error) {\n\tif v := os.Getenv(\"KUBECTL\"); v != \"\" {\n\t\treturn v, nil\n\t}\n\tpath, err := exec.LookPath(\"kubectl\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"kubectl is required for --shell but was not found in PATH\")\n\t}\n\treturn path, nil\n}\n\nfunc extractMinimalKubeconfig(kubectlPath, contextName string) ([]byte, error) {\n\tcmd := exec.Command(kubectlPath, \"config\", \"view\", \"--minify\", \"--flatten\",\n\t\t\"--context\", contextName)\n\tcmd.Env = os.Environ()\n\tdata, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kubectl config view failed: %w\", err)\n\t}\n\treturn data, nil\n}\n\nfunc detectShell() string {\n\tif runtime.GOOS == \"windows\" {\n\t\t// cmd.exe always sets the PROMPT env var, so if it is present\n\t\t// we can reliably assume we are running inside cmd.exe.\n\t\tif os.Getenv(\"PROMPT\") != \"\" {\n\t\t\treturn \"cmd.exe\"\n\t\t}\n\t\t// Otherwise assume PowerShell. PSModulePath is always set on\n\t\t// Windows regardless of the shell, so it cannot be used as a\n\t\t// discriminator; however the absence of PROMPT is a strong\n\t\t// enough signal that we are in a PowerShell session.\n\t\tif pwsh, err := exec.LookPath(\"pwsh\"); err == nil {\n\t\t\treturn pwsh\n\t\t}\n\t\tif powershell, err := exec.LookPath(\"powershell\"); err == nil {\n\t\t\treturn powershell\n\t\t}\n\t\treturn \"cmd.exe\"\n\t}\n\tif v := os.Getenv(\"SHELL\"); v != \"\" {\n\t\treturn v\n\t}\n\treturn \"/bin/sh\"\n}\n"
  },
  {
    "path": "cmd/kubectx/shell_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/ahmetb/kubectx/internal/env\"\n)\n\nfunc Test_detectShell_unix(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"skipping unix shell detection test on windows\")\n\t}\n\n\ttests := []struct {\n\t\tname     string\n\t\tshellEnv string\n\t\twant     string\n\t}{\n\t\t{\n\t\t\tname:     \"SHELL env set\",\n\t\t\tshellEnv: \"/bin/zsh\",\n\t\t\twant:     \"/bin/zsh\",\n\t\t},\n\t\t{\n\t\t\tname:     \"SHELL env empty, falls back to /bin/sh\",\n\t\t\tshellEnv: \"\",\n\t\t\twant:     \"/bin/sh\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Setenv(\"SHELL\", tt.shellEnv)\n\n\t\t\tgot := detectShell()\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"detectShell() = %q, want %q\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_ShellOp_blockedWhenNested(t *testing.T) {\n\t// Simulate being inside an isolated shell\n\tt.Setenv(env.EnvIsolatedShell, \"1\")\n\n\top := ShellOp{Target: \"some-context\"}\n\tvar stdout, stderr bytes.Buffer\n\terr := op.Run(&stdout, &stderr)\n\n\tif err == nil {\n\t\tt.Fatal(\"expected error when running ShellOp inside isolated shell, got nil\")\n\t}\n\n\twant := \"locked single-context shell to\"\n\tif !bytes.Contains([]byte(err.Error()), []byte(want)) {\n\t\t// The error may not contain the context name if kubeconfig is not available,\n\t\t// but it should still be blocked\n\t\twant2 := \"locked single-context shell\"\n\t\tif !bytes.Contains([]byte(err.Error()), []byte(want2)) {\n\t\t\tt.Errorf(\"error message %q does not contain %q\", err.Error(), want2)\n\t\t}\n\t}\n}\n\nfunc Test_resolveKubectl_envVar(t *testing.T) {\n\tt.Setenv(\"KUBECTL\", \"/custom/path/kubectl\")\n\tgot, err := resolveKubectl()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif got != \"/custom/path/kubectl\" {\n\t\tt.Errorf(\"resolveKubectl() = %q, want %q\", got, \"/custom/path/kubectl\")\n\t}\n}\n\nfunc Test_resolveKubectl_inPath(t *testing.T) {\n\tt.Setenv(\"KUBECTL\", \"\")\n\n\t// kubectl should be findable in PATH on most dev machines\n\tgot, err := resolveKubectl()\n\tif err != nil {\n\t\tt.Skip(\"kubectl not in PATH, skipping\")\n\t}\n\tif got == \"\" {\n\t\tt.Error(\"resolveKubectl() returned empty string\")\n\t}\n}\n\nfunc Test_checkIsolatedMode_notSet(t *testing.T) {\n\tt.Setenv(env.EnvIsolatedShell, \"\")\n\n\terr := checkIsolatedMode()\n\tif err != nil {\n\t\tt.Errorf(\"expected nil error when not in isolated mode, got: %v\", err)\n\t}\n}\n\nfunc Test_checkIsolatedMode_set(t *testing.T) {\n\tt.Setenv(env.EnvIsolatedShell, \"1\")\n\n\terr := checkIsolatedMode()\n\tif err == nil {\n\t\tt.Fatal(\"expected error when in isolated mode, got nil\")\n\t}\n\n\twant := \"locked single-context shell\"\n\tif !bytes.Contains([]byte(err.Error()), []byte(want)) {\n\t\tt.Errorf(\"error message %q does not contain %q\", err.Error(), want)\n\t}\n}\n"
  },
  {
    "path": "cmd/kubectx/state.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n)\n\nfunc kubectxPrevCtxFile() (string, error) {\n\tdir := cmdutil.CacheDir()\n\tif dir == \"\" {\n\t\treturn \"\", errors.New(\"HOME or USERPROFILE environment variable not set\")\n\t}\n\treturn filepath.Join(dir, \"kubectx\"), nil\n}\n\n// readLastContext returns the saved previous context\n// if the state file exists, otherwise returns \"\".\nfunc readLastContext(path string) (string, error) {\n\tb, err := os.ReadFile(path)\n\tif os.IsNotExist(err) {\n\t\treturn \"\", nil\n\t}\n\treturn string(b), err\n}\n\n// writeLastContext saves the specified value to the state file.\n// It creates missing parent directories.\nfunc writeLastContext(path, value string) error {\n\tdir := filepath.Dir(path)\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create parent directories: %w\", err)\n\t}\n\treturn os.WriteFile(path, []byte(value), 0644)\n}\n"
  },
  {
    "path": "cmd/kubectx/state_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc Test_readLastContext_nonExistingFile(t *testing.T) {\n\ts, err := readLastContext(filepath.FromSlash(\"/non/existing/file\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif s != \"\" {\n\t\tt.Fatalf(\"expected empty string; got=\\\"%s\\\"\", s)\n\t}\n}\n\nfunc Test_readLastContext(t *testing.T) {\n\tdir := t.TempDir()\n\tpath := filepath.Join(dir, \"testfile\")\n\tif err := os.WriteFile(path, []byte(\"foo\"), 0644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ts, err := readLastContext(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif expected := \"foo\"; s != expected {\n\t\tt.Fatalf(\"expected=\\\"%s\\\"; got=\\\"%s\\\"\", expected, s)\n\t}\n}\n\nfunc Test_writeLastContext_err(t *testing.T) {\n\tpath := filepath.Join(os.DevNull, \"foo\", \"bar\")\n\terr := writeLastContext(path, \"foo\")\n\tif err == nil {\n\t\tt.Fatal(\"got empty error\")\n\t}\n}\n\nfunc Test_writeLastContext(t *testing.T) {\n\tdir := t.TempDir()\n\tpath := filepath.Join(dir, \"foo\", \"bar\")\n\n\tif err := writeLastContext(path, \"ctx1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tv, err := readLastContext(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif expected := \"ctx1\"; v != expected {\n\t\tt.Fatalf(\"read wrong value=\\\"%s\\\"; expected=\\\"%s\\\"\", v, expected)\n\t}\n}\n\nfunc Test_kubectxFilePath(t *testing.T) {\n\tt.Setenv(\"HOME\", filepath.FromSlash(\"/foo/bar\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", \"\")\n\n\texpected := filepath.Join(filepath.FromSlash(\"/foo/bar\"), \".kube\", \"kubectx\")\n\tv, err := kubectxPrevCtxFile()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif v != expected {\n\t\tt.Fatalf(\"expected=\\\"%s\\\" got=\\\"%s\\\"\", expected, v)\n\t}\n}\n\nfunc Test_kubectxFilePath_xdgCacheHome(t *testing.T) {\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.FromSlash(\"/tmp/xdg-cache\"))\n\n\texpected := filepath.Join(filepath.FromSlash(\"/tmp/xdg-cache\"), \"kubectx\")\n\tv, err := kubectxPrevCtxFile()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif v != expected {\n\t\tt.Fatalf(\"expected=\\\"%s\\\" got=\\\"%s\\\"\", expected, v)\n\t}\n}\n\nfunc Test_kubectxFilePath_error(t *testing.T) {\n\tt.Setenv(\"HOME\", \"\")\n\tt.Setenv(\"USERPROFILE\", \"\")\n\n\t_, err := kubectxPrevCtxFile()\n\tif err == nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "cmd/kubectx/switch.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\n// SwitchOp indicates intention to switch contexts.\ntype SwitchOp struct {\n\tTarget string // '-' for back and forth, or NAME\n}\n\nfunc (op SwitchOp) Run(_, stderr io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\tvar newCtx string\n\tvar err error\n\tif op.Target == \"-\" {\n\t\tnewCtx, err = swapContext()\n\t} else {\n\t\tnewCtx, err = switchContext(op.Target)\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to switch context: %w\", err)\n\t}\n\tif err = printer.Success(stderr, \"Switched to context \\\"%s\\\".\", printer.SuccessColor.Sprint(newCtx)); err != nil {\n\t\treturn fmt.Errorf(\"print error: %w\", err)\n\t}\n\treturn nil\n}\n\n// switchContext switches to specified context name.\nfunc switchContext(name string) (string, error) {\n\tprevCtxFile, err := kubectxPrevCtxFile()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to determine state file: %w\", err)\n\t}\n\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tprev, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\texists, err := kc.ContextExists(name)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to check context: %w\", err)\n\t}\n\tif !exists {\n\t\treturn \"\", fmt.Errorf(\"no context exists with the name: \\\"%s\\\"\", name)\n\t}\n\tif err := kc.ModifyCurrentContext(name); err != nil {\n\t\treturn \"\", err\n\t}\n\tif err := kc.Save(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to save kubeconfig: %w\", err)\n\t}\n\n\tif prev != name {\n\t\tif err := writeLastContext(prevCtxFile, prev); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to save previous context name: %w\", err)\n\t\t}\n\t}\n\treturn name, nil\n}\n\n// swapContext switches to previously switch context.\nfunc swapContext() (string, error) {\n\tprevCtxFile, err := kubectxPrevCtxFile()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to determine state file: %w\", err)\n\t}\n\tprev, err := readLastContext(prevCtxFile)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to read previous context file: %w\", err)\n\t}\n\tif prev == \"\" {\n\t\treturn \"\", errors.New(\"no previous context found\")\n\t}\n\treturn switchContext(prev)\n}\n"
  },
  {
    "path": "cmd/kubectx/unset.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\n// UnsetOp indicates intention to remove current-context preference.\ntype UnsetOp struct{}\n\nfunc (_ UnsetOp) Run(_, stderr io.Writer) error {\n\tif err := checkIsolatedMode(); err != nil {\n\t\treturn err\n\t}\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tif err := kc.UnsetCurrentContext(); err != nil {\n\t\treturn fmt.Errorf(\"error while modifying current-context: %w\", err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\treturn fmt.Errorf(\"failed to save kubeconfig file after modification: %w\", err)\n\t}\n\n\terr := printer.Success(stderr, \"Active context unset for kubectl.\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write error: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/kubectx/version.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\nvar (\n\tversion = \"v0.0.0+unknown\" // populated by goreleaser\n)\n\n// VersionOp describes printing version string.\ntype VersionOp struct{}\n\nfunc (_ VersionOp) Run(stdout, _ io.Writer) error {\n\t_, err := fmt.Fprintf(stdout, \"%s\\n\", version)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write error: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/kubens/current.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n)\n\ntype CurrentOp struct{}\n\nfunc (c CurrentOp) Run(stdout, _ io.Writer) error {\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tctx, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\tif ctx == \"\" {\n\t\treturn errors.New(\"current-context is not set\")\n\t}\n\tns, err := kc.NamespaceOfContext(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read namespace of \\\"%s\\\": %w\", ctx, err)\n\t}\n\t_, err = fmt.Fprintln(stdout, ns)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write error: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/kubens/flags.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n)\n\n// UnsupportedOp indicates an unsupported flag.\ntype UnsupportedOp struct{ Err error }\n\nfunc (op UnsupportedOp) Run(_, _ io.Writer) error {\n\treturn op.Err\n}\n\n// parseArgs looks at flags (excl. executable name, i.e. argv[0])\n// and decides which operation should be taken.\nfunc parseArgs(argv []string) Op {\n\tn := len(argv)\n\n\tif n == 0 {\n\t\tif cmdutil.IsInteractiveMode(os.Stdout) {\n\t\t\treturn InteractiveSwitchOp{SelfCmd: os.Args[0]}\n\t\t}\n\t\treturn ListOp{}\n\t}\n\n\tif n == 1 {\n\t\tv := argv[0]\n\t\tswitch v {\n\t\tcase \"--help\", \"-h\":\n\t\t\treturn HelpOp{}\n\t\tcase \"--version\", \"-V\":\n\t\t\treturn VersionOp{}\n\t\tcase \"--current\", \"-c\":\n\t\t\treturn CurrentOp{}\n\t\tcase \"--unset\", \"-u\":\n\t\t\treturn UnsetOp{}\n\t\tdefault:\n\t\t\treturn getSwitchOp(v, false)\n\t\t}\n\t} else if n == 2 {\n\t\t// {namespace} -f|--force\n\t\tname := argv[0]\n\t\tforce := slices.Contains([]string{\"-f\", \"--force\"}, argv[1])\n\n\t\tif !force {\n\t\t\tif !slices.Contains([]string{\"-f\", \"--force\"}, argv[0]) {\n\t\t\t\treturn UnsupportedOp{Err: fmt.Errorf(\"unsupported arguments %q\", argv)}\n\t\t\t}\n\n\t\t\t// -f|--force {namespace}\n\t\t\tforce = true\n\t\t\tname = argv[1]\n\t\t}\n\n\t\treturn getSwitchOp(name, force)\n\t}\n\n\treturn UnsupportedOp{Err: fmt.Errorf(\"too many arguments\")}\n}\n\nfunc getSwitchOp(v string, force bool) Op {\n\tif strings.HasPrefix(v, \"-\") && v != \"-\" {\n\t\treturn UnsupportedOp{Err: fmt.Errorf(\"unsupported option %q\", v)}\n\t}\n\treturn SwitchOp{Target: v, Force: force}\n}\n"
  },
  {
    "path": "cmd/kubens/flags_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc Test_parseArgs_new(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targs []string\n\t\twant Op\n\t}{\n\t\t{name: \"nil Args\",\n\t\t\targs: nil,\n\t\t\twant: ListOp{}},\n\t\t{name: \"empty Args\",\n\t\t\targs: []string{},\n\t\t\twant: ListOp{}},\n\t\t{name: \"help shorthand\",\n\t\t\targs: []string{\"-h\"},\n\t\t\twant: HelpOp{}},\n\t\t{name: \"help long form\",\n\t\t\targs: []string{\"--help\"},\n\t\t\twant: HelpOp{}},\n\t\t{name: \"current shorthand\",\n\t\t\targs: []string{\"-c\"},\n\t\t\twant: CurrentOp{}},\n\t\t{name: \"current long form\",\n\t\t\targs: []string{\"--current\"},\n\t\t\twant: CurrentOp{}},\n\t\t{name: \"unset shorthand\",\n\t\t\targs: []string{\"-u\"},\n\t\t\twant: UnsetOp{}},\n\t\t{name: \"unset long form\",\n\t\t\targs: []string{\"--unset\"},\n\t\t\twant: UnsetOp{}},\n\t\t{name: \"switch by name\",\n\t\t\targs: []string{\"foo\"},\n\t\t\twant: SwitchOp{Target: \"foo\"}},\n\t\t{name: \"switch by name force short flag\",\n\t\t\targs: []string{\"foo\", \"-f\"},\n\t\t\twant: SwitchOp{Target: \"foo\", Force: true}},\n\t\t{name: \"switch by name force long flag\",\n\t\t\targs: []string{\"foo\", \"--force\"},\n\t\t\twant: SwitchOp{Target: \"foo\", Force: true}},\n\t\t{name: \"switch by name force short flag before name\",\n\t\t\targs: []string{\"-f\", \"foo\"},\n\t\t\twant: SwitchOp{Target: \"foo\", Force: true}},\n\t\t{name: \"switch by name force long flag before name\",\n\t\t\targs: []string{\"--force\", \"foo\"},\n\t\t\twant: SwitchOp{Target: \"foo\", Force: true}},\n\t\t{name: \"switch by name unknown arguments\",\n\t\t\targs: []string{\"foo\", \"-x\"},\n\t\t\twant: UnsupportedOp{Err: fmt.Errorf(\"unsupported arguments %q\", []string{\"foo\", \"-x\"})}},\n\t\t{name: \"switch by name unknown arguments\",\n\t\t\targs: []string{\"-x\", \"foo\"},\n\t\t\twant: UnsupportedOp{Err: fmt.Errorf(\"unsupported arguments %q\", []string{\"-x\", \"foo\"})}},\n\t\t{name: \"switch by swap\",\n\t\t\targs: []string{\"-\"},\n\t\t\twant: SwitchOp{Target: \"-\"}},\n\t\t{name: \"unrecognized flag\",\n\t\t\targs: []string{\"-x\"},\n\t\t\twant: UnsupportedOp{Err: fmt.Errorf(\"unsupported option %q\", \"-x\")}},\n\t\t{name: \"too many args\",\n\t\t\targs: []string{\"a\", \"b\", \"c\"},\n\t\t\twant: UnsupportedOp{Err: fmt.Errorf(\"too many arguments\")}},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := parseArgs(tt.args)\n\n\t\t\tvar opts cmp.Options\n\t\t\tif _, ok := tt.want.(UnsupportedOp); ok {\n\t\t\t\topts = append(opts, cmp.Comparer(func(x, y UnsupportedOp) bool {\n\t\t\t\t\treturn (x.Err == nil && y.Err == nil) || (x.Err.Error() == y.Err.Error())\n\t\t\t\t}))\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(got, tt.want, opts...); diff != \"\" {\n\t\t\t\tt.Errorf(\"parseArgs(%#v) diff: %s\", tt.args, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/kubens/fzf.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n\t\"github.com/ahmetb/kubectx/internal/env\"\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\ntype InteractiveSwitchOp struct {\n\tSelfCmd string\n}\n\n// TODO(ahmetb) This method is heavily repetitive vs kubectx/fzf.go.\nfunc (op InteractiveSwitchOp) Run(_, stderr io.Writer) error {\n\t// parse kubeconfig just to see if it can be loaded\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\tif cmdutil.IsNotFoundErr(err) {\n\t\t\tprinter.Warning(stderr, \"kubeconfig file not found\")\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tctxNames, err := kc.ContextNames()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get context names: %w\", err)\n\t}\n\tif len(ctxNames) == 0 {\n\t\treturn errors.New(\"no contexts found in the kubeconfig file\")\n\t}\n\n\tcmd := exec.Command(\"fzf\", \"--ansi\", \"--no-preview\")\n\tvar out bytes.Buffer\n\tcmd.Stdin = os.Stdin\n\tcmd.Stderr = stderr\n\tcmd.Stdout = &out\n\n\tcmd.Env = append(os.Environ(),\n\t\tfmt.Sprintf(\"FZF_DEFAULT_COMMAND=%s\", op.SelfCmd),\n\t\tfmt.Sprintf(\"%s=1\", env.EnvForceColor))\n\tif err := cmd.Run(); err != nil {\n\t\tvar exitErr *exec.ExitError\n\t\tif !errors.As(err, &exitErr) {\n\t\t\treturn err\n\t\t}\n\t}\n\tchoice := strings.TrimSpace(out.String())\n\tif choice == \"\" {\n\t\treturn errors.New(\"you did not choose any of the options\")\n\t}\n\tname, err := switchNamespace(kc, choice, false)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to switch namespace: %w\", err)\n\t}\n\t_ = printer.Success(stderr, \"Active namespace is \\\"%s\\\".\", printer.SuccessColor.Sprint(name))\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/kubens/help.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n// HelpOp describes printing help.\ntype HelpOp struct{}\n\nfunc (_ HelpOp) Run(stdout, _ io.Writer) error {\n\treturn printUsage(stdout)\n}\n\nfunc printUsage(out io.Writer) error {\n\thelp := `USAGE:\n  %PROG%                    : list the namespaces in the current context\n  %PROG% <NAME>             : change the active namespace of current context\n  %PROG% <NAME> --force/-f  : force change the active namespace of current context (even if it doesn't exist)\n  %PROG% -                  : switch to the previous namespace in this context\n  %PROG% -c, --current      : show the current namespace\n  %PROG% -h,--help          : show this message\n  %PROG% -u,--unset         : unset the namespace choice (set to 'default')\n  %PROG% -V,--version       : show version`\n\n\t// TODO this replace logic is duplicated between this and kubectx\n\thelp = strings.ReplaceAll(help, \"%PROG%\", selfName())\n\n\t_, err := fmt.Fprintf(out, \"%s\\n\", help)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write error: %w\", err)\n\t}\n\treturn nil\n}\n\n// selfName guesses how the user invoked the program.\nfunc selfName() string {\n\t// TODO this method is duplicated between this and kubectx\n\tme := filepath.Base(os.Args[0])\n\tpluginPrefix := \"kubectl-\"\n\tif strings.HasPrefix(me, pluginPrefix) {\n\t\treturn \"kubectl \" + strings.TrimPrefix(me, pluginPrefix)\n\t}\n\treturn \"kubens\"\n}\n"
  },
  {
    "path": "cmd/kubens/list.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"slices\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\ntype ListOp struct{}\n\nfunc (op ListOp) Run(stdout, stderr io.Writer) error {\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tctx, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\tif ctx == \"\" {\n\t\treturn errors.New(\"current-context is not set\")\n\t}\n\tcurNs, err := kc.NamespaceOfContext(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot read current namespace: %w\", err)\n\t}\n\n\tns, err := queryNamespaces(kc)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not list namespaces (is the cluster accessible?): %w\", err)\n\t}\n\n\tfor _, c := range ns {\n\t\ts := c\n\t\tif c == curNs {\n\t\t\ts = printer.ActiveItemColor.Sprint(c)\n\t\t}\n\t\tfmt.Fprintf(stdout, \"%s\\n\", s)\n\t}\n\treturn nil\n}\n\nfunc queryNamespaces(kc *kubeconfig.Kubeconfig) ([]string, error) {\n\tif os.Getenv(\"_MOCK_NAMESPACES\") != \"\" {\n\t\treturn []string{\"ns1\", \"ns2\"}, nil\n\t}\n\n\tclientset, err := newKubernetesClientSet(kc)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to initialize k8s REST client: %w\", err)\n\t}\n\n\tvar out []string\n\tvar next string\n\tfor {\n\t\tlist, err := clientset.CoreV1().Namespaces().List(\n\t\t\tcontext.Background(),\n\t\t\tmetav1.ListOptions{\n\t\t\t\tLimit:    500,\n\t\t\t\tContinue: next,\n\t\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to list namespaces from k8s API: %w\", err)\n\t\t}\n\t\tnext = list.Continue\n\t\tout = slices.Grow(out, len(list.Items))\n\t\tfor _, it := range list.Items {\n\t\t\tout = append(out, it.Name)\n\t\t}\n\t\tif next == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn out, nil\n}\n\nfunc newKubernetesClientSet(kc *kubeconfig.Kubeconfig) (*kubernetes.Clientset, error) {\n\tb, err := kc.Bytes()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert in-memory kubeconfig to yaml: %w\", err)\n\t}\n\tcfg, err := clientcmd.RESTConfigFromKubeConfig(b)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to initialize config: %w\", err)\n\t}\n\treturn kubernetes.NewForConfig(cfg)\n}\n"
  },
  {
    "path": "cmd/kubens/main.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n\t\"github.com/ahmetb/kubectx/internal/env\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n\t\"github.com/fatih/color\"\n)\n\ntype Op interface {\n\tRun(stdout, stderr io.Writer) error\n}\n\nfunc main() {\n\tcmdutil.PrintDeprecatedEnvWarnings(color.Error, os.Environ())\n\top := parseArgs(os.Args[1:])\n\tif err := op.Run(color.Output, color.Error); err != nil {\n\t\tprinter.Error(color.Error, \"%s\", err)\n\n\t\tif _, ok := os.LookupEnv(env.EnvDebug); ok {\n\t\t\t// print stack trace in verbose mode\n\t\t\tfmt.Fprintf(color.Error, \"[DEBUG] error: %+v\\n\", err)\n\t\t}\n\t\tdefer os.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cmd/kubens/statefile.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n)\n\nvar defaultDir = filepath.Join(cmdutil.CacheDir(), \"kubens\")\n\ntype NSFile struct {\n\tdir string\n\tctx string\n}\n\nfunc NewNSFile(ctx string) NSFile { return NSFile{dir: defaultDir, ctx: ctx} }\n\nfunc (f NSFile) path() string {\n\tfn := f.ctx\n\tif isWindows() {\n\t\t// bug 230: eks clusters contain ':' in ctx name, not a valid file name for win32\n\t\tfn = strings.ReplaceAll(fn, \":\", \"__\")\n\t}\n\treturn filepath.Join(f.dir, fn)\n}\n\n// Load reads the previous namespace setting, or returns empty if not exists.\nfunc (f NSFile) Load() (string, error) {\n\tb, err := os.ReadFile(f.path())\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn \"\", nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\treturn string(bytes.TrimSpace(b)), nil\n}\n\n// Save stores the previous namespace information in the file.\nfunc (f NSFile) Save(value string) error {\n\td := filepath.Dir(f.path())\n\tif err := os.MkdirAll(d, 0755); err != nil {\n\t\treturn err\n\t}\n\treturn os.WriteFile(f.path(), []byte(value), 0644)\n}\n\n// isWindows determines if the process is running on windows OS.\nfunc isWindows() bool {\n\tif os.Getenv(\"_FORCE_GOOS\") == \"windows\" { // for testing\n\t\treturn true\n\t}\n\treturn runtime.GOOS == \"windows\"\n}\n"
  },
  {
    "path": "cmd/kubens/statefile_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestNSFile(t *testing.T) {\n\ttd := t.TempDir()\n\n\tf := NewNSFile(\"foo\")\n\tf.dir = td\n\tv, err := f.Load()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif v != \"\" {\n\t\tt.Fatalf(\"Load() expected empty; got=%v\", err)\n\t}\n\n\terr = f.Save(\"bar\")\n\tif err != nil {\n\t\tt.Fatalf(\"Save() err=%v\", err)\n\t}\n\n\tv, err = f.Load()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif expected := \"bar\"; v != expected {\n\t\tt.Fatalf(\"Load()=\\\"%s\\\"; expected=\\\"%s\\\"\", v, expected)\n\t}\n}\n\nfunc TestNSFile_path_windows(t *testing.T) {\n\tt.Setenv(\"_FORCE_GOOS\", \"windows\")\n\tfp := NewNSFile(\"a:b:c\").path()\n\n\tif expected := \"a__b__c\"; !strings.HasSuffix(fp, expected) {\n\t\tt.Fatalf(\"file did not have expected ending %q: %s\", expected, fp)\n\t}\n}\n\nfunc Test_isWindows(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"won't test this case on windows\")\n\t}\n\n\tgot := isWindows()\n\tif got {\n\t\tt.Fatalf(\"isWindows() returned true for %s\", runtime.GOOS)\n\t}\n\n\tt.Setenv(\"_FORCE_GOOS\", \"windows\")\n\tif !isWindows() {\n\t\tt.Fatalf(\"isWindows() failed to detect windows with env override.\")\n\t}\n}\n"
  },
  {
    "path": "cmd/kubens/switch.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\terrors2 \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\ntype SwitchOp struct {\n\tTarget string // '-' for back and forth, or NAME\n\tForce  bool   // force switch even if the namespace doesn't exist\n}\n\nfunc (s SwitchOp) Run(_, stderr io.Writer) error {\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\ttoNS, err := switchNamespace(kc, s.Target, s.Force)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = printer.Success(stderr, \"Active namespace is \\\"%s\\\"\", printer.SuccessColor.Sprint(toNS))\n\treturn err\n}\n\nfunc switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (string, error) {\n\tctx, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\tif ctx == \"\" {\n\t\treturn \"\", errors.New(\"current-context is not set\")\n\t}\n\tcurNS, err := kc.NamespaceOfContext(ctx)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get current namespace: %w\", err)\n\t}\n\n\tf := NewNSFile(ctx)\n\tprev, err := f.Load()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to load previous namespace from file: %w\", err)\n\t}\n\n\tif ns == \"-\" {\n\t\tif prev == \"\" {\n\t\t\treturn \"\", fmt.Errorf(\"No previous namespace found for current context (%s)\", ctx)\n\t\t}\n\t\tns = prev\n\t}\n\n\tif !force {\n\t\tok, err := namespaceExists(kc, ns)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to query if namespace exists (is cluster accessible?): %w\", err)\n\t\t}\n\t\tif !ok {\n\t\t\treturn \"\", fmt.Errorf(\"no namespace exists with name \\\"%s\\\"\", ns)\n\t\t}\n\t}\n\n\tif err := kc.SetNamespace(ctx, ns); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to change to namespace \\\"%s\\\": %w\", ns, err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to save kubeconfig file: %w\", err)\n\t}\n\tif curNS != ns {\n\t\tif err := f.Save(curNS); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to save the previous namespace to file: %w\", err)\n\t\t}\n\t}\n\treturn ns, nil\n}\n\nfunc namespaceExists(kc *kubeconfig.Kubeconfig, ns string) (bool, error) {\n\t// for tests\n\tif os.Getenv(\"_MOCK_NAMESPACES\") != \"\" {\n\t\treturn ns == \"ns1\" || ns == \"ns2\", nil\n\t}\n\n\tclientset, err := newKubernetesClientSet(kc)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to initialize k8s REST client: %w\", err)\n\t}\n\n\tnamespace, err := clientset.CoreV1().Namespaces().Get(context.Background(), ns, metav1.GetOptions{})\n\tif errors2.IsNotFound(err) {\n\t\treturn false, nil\n\t}\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to query namespace %q from k8s API: %w\", ns, err)\n\t}\n\treturn namespace != nil, nil\n}\n"
  },
  {
    "path": "cmd/kubens/unset.go",
    "content": "// Copyright 2021 Google LLC\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\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/ahmetb/kubectx/internal/kubeconfig\"\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\n// UnsetOp indicates intention to remove current namespace preference.\ntype UnsetOp struct{}\n\nfunc (_ UnsetOp) Run(_, stderr io.Writer) error {\n\tkc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\treturn fmt.Errorf(\"kubeconfig error: %w\", err)\n\t}\n\n\tns, err := clearNamespace(kc)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = printer.Success(stderr, \"Active namespace is \\\"%s\\\".\", printer.SuccessColor.Sprint(ns))\n\treturn err\n}\n\nfunc clearNamespace(kc *kubeconfig.Kubeconfig) (string, error) {\n\tctx, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get current context: %w\", err)\n\t}\n\tns := \"default\"\n\tif ctx == \"\" {\n\t\treturn \"\", errors.New(\"current-context is not set\")\n\t}\n\n\tif err := kc.SetNamespace(ctx, ns); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to clear namespace: %w\", err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to save kubeconfig file: %w\", err)\n\t}\n\treturn ns, nil\n}\n"
  },
  {
    "path": "cmd/kubens/version.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\nvar (\n\tversion = \"v0.0.0+unknown\" // populated by goreleaser\n)\n\n// VersionOp describes printing version string.\ntype VersionOp struct{}\n\nfunc (_ VersionOp) Run(stdout, _ io.Writer) error {\n\t_, err := fmt.Fprintf(stdout, \"%s\\n\", version)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"write error: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "completion/_kubectx.zsh",
    "content": "#compdef kubectx kctx=kubectx\n\nlocal KUBECTX=\"${HOME}/.kube/kubectx\"\nPREV=\"\"\n\nlocal context_array=(\"${(@f)$(kubectl config get-contexts --output='name')}\")\nlocal all_contexts=(\\'${^context_array}\\')\n\nif [ -f \"$KUBECTX\" ]; then\n    # show '-' only if there's a saved previous context\n    local PREV=$(cat \"${KUBECTX}\")\n\n    _arguments \\\n      \"-d:*: :(${all_contexts})\" \\\n      \"(- *): :(- ${all_contexts})\"\nelse\n    _arguments \\\n      \"-d:*: :(${all_contexts})\" \\\n      \"(- *): :(${all_contexts})\"\nfi\n"
  },
  {
    "path": "completion/_kubens.zsh",
    "content": "#compdef kubens kns=kubens\n_arguments \"1: :(- $(kubectl get namespaces -o=jsonpath='{range .items[*].metadata.name}{@}{\"\\n\"}{end}'))\"\n"
  },
  {
    "path": "completion/kubectx.bash",
    "content": "_kube_contexts()\n{\n  local curr_arg;\n  curr_arg=${COMP_WORDS[COMP_CWORD]}\n  COMPREPLY=( $(compgen -W \"- $(kubectl config get-contexts --output='name')\" -- $curr_arg ) );\n}\n\ncomplete -F _kube_contexts kubectx kctx\n"
  },
  {
    "path": "completion/kubectx.fish",
    "content": "# kubectx\n\nfunction __fish_kubectx_arg_number -a number\n    set -l cmd (commandline -opc)\n    test (count $cmd) -eq $number\nend\n\ncomplete -f -c kubectx\ncomplete -f -x -c kubectx -n '__fish_kubectx_arg_number 1' -a \"(kubectl config get-contexts --output='name')\"\ncomplete -f -x -c kubectx -n '__fish_kubectx_arg_number 1' -a \"-\" -d \"switch to the previous namespace in this context\"\n"
  },
  {
    "path": "completion/kubens.bash",
    "content": "_kube_namespaces()\n{\n  local curr_arg;\n  curr_arg=${COMP_WORDS[COMP_CWORD]}\n  COMPREPLY=( $(compgen -W \"- $(kubectl get namespaces -o=jsonpath='{range .items[*].metadata.name}{@}{\"\\n\"}{end}')\" -- $curr_arg ) );\n}\n\ncomplete -F _kube_namespaces kubens kns\n"
  },
  {
    "path": "completion/kubens.fish",
    "content": "# kubens\n\nfunction __fish_kubens_arg_number -a number\n    set -l cmd (commandline -opc)\n    test (count $cmd) -eq $number\nend\n\ncomplete -f -c kubens\ncomplete -f -x -c kubens -n '__fish_kubens_arg_number 1' -a \"(kubectl get ns -o=custom-columns=NAME:.metadata.name --no-headers)\"\ncomplete -f -x -c kubens -n '__fish_kubens_arg_number 1' -a \"-\" -d \"switch to the previous namespace in this context\"\ncomplete -f -x -c kubens -n '__fish_kubens_arg_number 1' -s c -l current -d \"show the current namespace\"\ncomplete -f -x -c kubens -n '__fish_kubens_arg_number 1' -s h -l help -d \"show the help message\"\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/ahmetb/kubectx\n\ngo 1.25.0\n\nrequire (\n\tfacette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb\n\tgithub.com/fatih/color v1.18.0\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/mattn/go-isatty v0.0.20\n\tk8s.io/apimachinery v0.35.2\n\tk8s.io/client-go v0.35.2\n\tsigs.k8s.io/kustomize/kyaml v0.21.1\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-errors/errors v1.4.2 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/term v0.37.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/time v0.9.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/api v0.35.2 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:1pSweJFeR3Pqx7uoelppkzeegfUBXL6I2FFAbfXw570=\nfacette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:npRYmtaITVom7rcSo+pRURltHSG2r4TQM1cdqJ2dUB0=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=\ngithub.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\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-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\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/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\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/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=\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/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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\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/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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\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 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\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/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=\ngithub.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=\ngithub.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=\ngithub.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=\ngolang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\ngolang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=\ngolang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=\ngolang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=\ngolang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=\ngolang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=\nk8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=\nk8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=\nk8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=\nk8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=\nk8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=\nsigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "internal/cmdutil/deprecated.go",
    "content": "// Copyright 2021 Google LLC\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\npackage cmdutil\n\nimport (\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/ahmetb/kubectx/internal/printer\"\n)\n\nfunc PrintDeprecatedEnvWarnings(out io.Writer, vars []string) {\n\tfor _, vv := range vars {\n\t\tparts := strings.SplitN(vv, \"=\", 2)\n\t\tif len(parts) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tkey := parts[0]\n\n\t\tif key == `KUBECTX_CURRENT_FGCOLOR` || key == `KUBECTX_CURRENT_BGCOLOR` {\n\t\t\tprinter.Warning(out, \"%s environment variable is now deprecated\", key)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/cmdutil/deprecated_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage cmdutil\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPrintDeprecatedEnvWarnings_noDeprecatedVars(t *testing.T) {\n\tvar out bytes.Buffer\n\tPrintDeprecatedEnvWarnings(&out, []string{\n\t\t\"A=B\",\n\t\t\"PATH=/foo:/bar:/bin\",\n\t})\n\tif v := out.String(); len(v) > 0 {\n\t\tt.Fatalf(\"something written to buf: %v\", v)\n\t}\n}\n\nfunc TestPrintDeprecatedEnvWarnings_bgColors(t *testing.T) {\n\tvar out bytes.Buffer\n\n\tPrintDeprecatedEnvWarnings(&out, []string{\n\t\t\"KUBECTX_CURRENT_FGCOLOR=1\",\n\t\t\"KUBECTX_CURRENT_BGCOLOR=2\",\n\t})\n\tv := out.String()\n\tif !strings.Contains(v, \"KUBECTX_CURRENT_FGCOLOR\") {\n\t\tt.Fatalf(\"output doesn't contain 'KUBECTX_CURRENT_FGCOLOR': \\\"%s\\\"\", v)\n\t}\n\tif !strings.Contains(v, \"KUBECTX_CURRENT_BGCOLOR\") {\n\t\tt.Fatalf(\"output doesn't contain 'KUBECTX_CURRENT_BGCOLOR': \\\"%s\\\"\", v)\n\t}\n}\n"
  },
  {
    "path": "internal/cmdutil/interactive.go",
    "content": "// Copyright 2021 Google LLC\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\npackage cmdutil\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\n\t\"github.com/mattn/go-isatty\"\n\n\t\"github.com/ahmetb/kubectx/internal/env\"\n)\n\n// isTerminal determines if given fd is a TTY.\nfunc isTerminal(fd *os.File) bool {\n\treturn isatty.IsTerminal(fd.Fd())\n}\n\n// fzfInstalled determines if fzf(1) is in PATH.\nfunc fzfInstalled() bool {\n\tv, _ := exec.LookPath(\"fzf\")\n\tif v != \"\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// IsInteractiveMode determines if we can do choosing with fzf.\nfunc IsInteractiveMode(stdout *os.File) bool {\n\tv := os.Getenv(env.EnvFZFIgnore)\n\treturn v == \"\" && isTerminal(stdout) && fzfInstalled()\n}\n"
  },
  {
    "path": "internal/cmdutil/util.go",
    "content": "// Copyright 2021 Google LLC\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\npackage cmdutil\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nfunc HomeDir() string {\n\thome := os.Getenv(\"HOME\")\n\tif home == \"\" {\n\t\thome = os.Getenv(\"USERPROFILE\") // windows\n\t}\n\treturn home\n}\n\n// CacheDir returns XDG_CACHE_HOME if set, otherwise $HOME/.kube,\n// matching the bash scripts' behavior: ${XDG_CACHE_HOME:-$HOME/.kube}.\nfunc CacheDir() string {\n\tif xdg := os.Getenv(\"XDG_CACHE_HOME\"); xdg != \"\" {\n\t\treturn xdg\n\t}\n\thome := HomeDir()\n\tif home == \"\" {\n\t\treturn \"\"\n\t}\n\treturn filepath.Join(home, \".kube\")\n}\n\n// IsNotFoundErr determines if the underlying error is os.IsNotExist.\nfunc IsNotFoundErr(err error) bool {\n\tfor e := err; e != nil; e = errors.Unwrap(e) {\n\t\tif os.IsNotExist(e) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/cmdutil/util_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage cmdutil\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc Test_homeDir(t *testing.T) {\n\ttype env struct{ k, v string }\n\tcases := []struct {\n\t\tname string\n\t\tenvs []env\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"don't use XDG_CACHE_HOME as homedir\",\n\t\t\tenvs: []env{\n\t\t\t\t{\"XDG_CACHE_HOME\", \"xdg\"},\n\t\t\t\t{\"HOME\", \"home\"},\n\t\t\t},\n\t\t\twant: \"home\",\n\t\t},\n\t\t{\n\t\t\tname: \"HOME over USERPROFILE\",\n\t\t\tenvs: []env{\n\t\t\t\t{\"HOME\", \"home\"},\n\t\t\t\t{\"USERPROFILE\", \"up\"},\n\t\t\t},\n\t\t\twant: \"home\",\n\t\t},\n\t\t{\n\t\t\tname: \"only USERPROFILE available\",\n\t\t\tenvs: []env{\n\t\t\t\t{\"HOME\", \"\"},\n\t\t\t\t{\"USERPROFILE\", \"up\"},\n\t\t\t},\n\t\t\twant: \"up\",\n\t\t},\n\t\t{\n\t\t\tname: \"none available\",\n\t\t\tenvs: []env{\n\t\t\t\t{\"HOME\", \"\"},\n\t\t\t\t{\"USERPROFILE\", \"\"},\n\t\t\t},\n\t\t\twant: \"\",\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(tt *testing.T) {\n\t\t\tfor _, e := range c.envs {\n\t\t\t\ttt.Setenv(e.k, e.v)\n\t\t\t}\n\n\t\t\tgot := HomeDir()\n\t\t\tif got != c.want {\n\t\t\t\tt.Errorf(\"expected:%q got:%q\", c.want, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCacheDir(t *testing.T) {\n\tt.Run(\"XDG_CACHE_HOME set\", func(t *testing.T) {\n\t\tt.Setenv(\"XDG_CACHE_HOME\", \"/tmp/xdg-cache\")\n\t\tt.Setenv(\"HOME\", \"/home/user\")\n\t\tif got := CacheDir(); got != \"/tmp/xdg-cache\" {\n\t\t\tt.Errorf(\"expected:%q got:%q\", \"/tmp/xdg-cache\", got)\n\t\t}\n\t})\n\tt.Run(\"XDG_CACHE_HOME unset, falls back to HOME/.kube\", func(t *testing.T) {\n\t\tt.Setenv(\"XDG_CACHE_HOME\", \"\")\n\t\tt.Setenv(\"HOME\", \"/home/user\")\n\t\twant := filepath.Join(\"/home/user\", \".kube\")\n\t\tif got := CacheDir(); got != want {\n\t\t\tt.Errorf(\"expected:%q got:%q\", want, got)\n\t\t}\n\t})\n\tt.Run(\"neither set\", func(t *testing.T) {\n\t\tt.Setenv(\"XDG_CACHE_HOME\", \"\")\n\t\tt.Setenv(\"HOME\", \"\")\n\t\tt.Setenv(\"USERPROFILE\", \"\")\n\t\tif got := CacheDir(); got != \"\" {\n\t\t\tt.Errorf(\"expected:%q got:%q\", \"\", got)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "internal/env/constants.go",
    "content": "// Copyright 2021 Google LLC\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\npackage env\n\nconst (\n\t// EnvFZFIgnore describes the environment variable to set to disable\n\t// interactive context selection when fzf is installed.\n\tEnvFZFIgnore = \"KUBECTX_IGNORE_FZF\"\n\n\t// EnvNoColor describes the environment variable to disable color usage\n\t// when printing current context in a list.\n\tEnvNoColor = `NO_COLOR`\n\n\t// EnvForceColor describes the \"internal\" environment variable to force\n\t// color usage to show current context in a list.\n\tEnvForceColor = `_KUBECTX_FORCE_COLOR`\n\n\t// EnvDebug describes the internal environment variable for more verbose logging.\n\tEnvDebug = `DEBUG`\n\n\tEnvIsolatedShell = \"KUBECTX_ISOLATED_SHELL\"\n)\n"
  },
  {
    "path": "internal/kubeconfig/contextmodify.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"errors\"\n\n\t\"sigs.k8s.io/kustomize/kyaml/yaml\"\n)\n\nfunc (k *Kubeconfig) DeleteContextEntry(deleteName string) error {\n\tcontexts, err := k.contextsNode()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := contexts.PipeE(\n\t\tyaml.ElementSetter{\n\t\t\tKeys:   []string{\"name\"},\n\t\t\tValues: []string{deleteName},\n\t\t},\n\t); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (k *Kubeconfig) ModifyCurrentContext(name string) error {\n\tif err := k.config.PipeE(yaml.SetField(\"current-context\", yaml.NewScalarRNode(name))); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (k *Kubeconfig) ModifyContextName(old, new string) error {\n\tcontext, err := k.config.Pipe(yaml.Lookup(\"contexts\", \"[name=\"+old+\"]\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif context == nil {\n\t\treturn errors.New(\"\\\"contexts\\\" entry is nil\")\n\t}\n\tif err := context.PipeE(yaml.SetField(\"name\", yaml.NewScalarRNode(new))); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/kubeconfig/contextmodify_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/ahmetb/kubectx/internal/testutil\"\n)\n\nfunc TestKubeconfig_DeleteContextEntry_errors(t *testing.T) {\n\tkc := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`[1, 2, 3]`))\n\t_ = kc.Parse()\n\terr := kc.DeleteContextEntry(\"foo\")\n\tif err == nil {\n\t\tt.Fatal(\"supposed to fail on non-mapping nodes\")\n\t}\n\n\tkc = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`a: b`))\n\t_ = kc.Parse()\n\terr = kc.DeleteContextEntry(\"foo\")\n\tif err == nil {\n\t\tt.Fatal(\"supposed to fail if contexts key does not exist\")\n\t}\n\n\tkc = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`contexts: \"some string\"`))\n\t_ = kc.Parse()\n\terr = kc.DeleteContextEntry(\"foo\")\n\tif err == nil {\n\t\tt.Fatal(\"supposed to fail if contexts key is not an array\")\n\t}\n}\n\nfunc TestKubeconfig_DeleteContextEntry(t *testing.T) {\n\ttest := WithMockKubeconfigLoader(\n\t\ttestutil.KC().WithCtxs(\n\t\t\ttestutil.Ctx(\"c1\"),\n\t\t\ttestutil.Ctx(\"c2\"),\n\t\t\ttestutil.Ctx(\"c3\")).ToYAML(t))\n\tkc := new(Kubeconfig).WithLoader(test)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.DeleteContextEntry(\"c1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := testutil.KC().WithCtxs(\n\t\ttestutil.Ctx(\"c2\"),\n\t\ttestutil.Ctx(\"c3\")).ToYAML(t)\n\tout := test.Output()\n\tif diff := cmp.Diff(expected, out); diff != \"\" {\n\t\tt.Fatalf(\"diff: %s\", diff)\n\t}\n}\n\nfunc TestKubeconfig_ModifyCurrentContext_fieldExists(t *testing.T) {\n\ttest := WithMockKubeconfigLoader(\n\t\ttestutil.KC().WithCurrentCtx(\"abc\").Set(\"field1\", \"value1\").ToYAML(t))\n\tkc := new(Kubeconfig).WithLoader(test)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.ModifyCurrentContext(\"foo\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := testutil.KC().WithCurrentCtx(\"foo\").Set(\"field1\", \"value1\").ToYAML(t)\n\tout := test.Output()\n\tif diff := cmp.Diff(expected, out); diff != \"\" {\n\t\tt.Fatalf(\"diff: %s\", diff)\n\t}\n}\n\nfunc TestKubeconfig_ModifyCurrentContext_fieldMissing(t *testing.T) {\n\ttest := WithMockKubeconfigLoader(`f1: v1`)\n\tkc := new(Kubeconfig).WithLoader(test)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.ModifyCurrentContext(\"foo\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := `f1: v1\ncurrent-context: foo\n`\n\tout := test.Output()\n\tif diff := cmp.Diff(expected, out); diff != \"\" {\n\t\tt.Fatalf(\"diff: %s\", diff)\n\t}\n}\n\nfunc TestKubeconfig_ModifyContextName_noContextsEntryError(t *testing.T) {\n\t// no context entries\n\ttest := WithMockKubeconfigLoader(`a: b`)\n\tkc := new(Kubeconfig).WithLoader(test)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.ModifyContextName(\"c1\", \"c2\"); err == nil {\n\t\tt.Fatal(\"was expecting error for no 'contexts' entry; got nil\")\n\t}\n}\n\nfunc TestKubeconfig_ModifyContextName_contextsEntryNotSequenceError(t *testing.T) {\n\t// no context entries\n\ttest := WithMockKubeconfigLoader(\n\t\t`contexts: \"hello\"`)\n\tkc := new(Kubeconfig).WithLoader(test)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.ModifyContextName(\"c1\", \"c2\"); err == nil {\n\t\tt.Fatal(\"was expecting error for 'context entry not a sequence'; got nil\")\n\t}\n}\n\nfunc TestKubeconfig_ModifyContextName_noChange(t *testing.T) {\n\ttest := WithMockKubeconfigLoader(testutil.KC().WithCtxs(\n\t\ttestutil.Ctx(\"c1\"),\n\t\ttestutil.Ctx(\"c2\"),\n\t\ttestutil.Ctx(\"c3\")).ToYAML(t))\n\tkc := new(Kubeconfig).WithLoader(test)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.ModifyContextName(\"c5\", \"c6\"); err == nil {\n\t\tt.Fatal(\"was expecting error for 'no changes made'\")\n\t}\n}\n\nfunc TestKubeconfig_ModifyContextName(t *testing.T) {\n\ttest := WithMockKubeconfigLoader(testutil.KC().WithCtxs(\n\t\ttestutil.Ctx(\"c1\"),\n\t\ttestutil.Ctx(\"c2\"),\n\t\ttestutil.Ctx(\"c3\")).ToYAML(t))\n\tkc := new(Kubeconfig).WithLoader(test)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.ModifyContextName(\"c1\", \"ccc\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := testutil.KC().WithCtxs(\n\t\ttestutil.Ctx(\"ccc\"),\n\t\ttestutil.Ctx(\"c2\"),\n\t\ttestutil.Ctx(\"c3\")).ToYAML(t)\n\tout := test.Output()\n\tif diff := cmp.Diff(expected, out); diff != \"\" {\n\t\tt.Fatalf(\"diff: %s\", diff)\n\t}\n}\n"
  },
  {
    "path": "internal/kubeconfig/contexts.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\n\t\"sigs.k8s.io/kustomize/kyaml/yaml\"\n)\n\nfunc (k *Kubeconfig) contextsNode() (*yaml.RNode, error) {\n\tcontexts, err := k.config.Pipe(yaml.Get(\"contexts\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif contexts == nil {\n\t\treturn nil, errors.New(\"\\\"contexts\\\" entry is nil\")\n\t} else if contexts.YNode().Kind != yaml.SequenceNode {\n\t\treturn nil, errors.New(\"\\\"contexts\\\" is not a sequence node\")\n\t}\n\treturn contexts, nil\n}\n\nfunc (k *Kubeconfig) contextNode(name string) (*yaml.RNode, error) {\n\tcontexts, err := k.contextsNode()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcontext, err := contexts.Pipe(yaml.Lookup(\"[name=\" + name + \"]\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif context == nil {\n\t\treturn nil, fmt.Errorf(\"context with name \\\"%s\\\" not found\", name)\n\t}\n\treturn context, nil\n}\n\nfunc (k *Kubeconfig) ContextNames() ([]string, error) {\n\tcontexts, err := k.config.Pipe(yaml.Get(\"contexts\"))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get contexts: %w\", err)\n\t}\n\tif contexts == nil {\n\t\treturn nil, nil\n\t}\n\tnames, err := contexts.ElementValues(\"name\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get context names: %w\", err)\n\t}\n\treturn names, nil\n}\n\nfunc (k *Kubeconfig) ContextExists(name string) (bool, error) {\n\tnames, err := k.ContextNames()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn slices.Contains(names, name), nil\n}\n"
  },
  {
    "path": "internal/kubeconfig/contexts_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/ahmetb/kubectx/internal/testutil\"\n)\n\nfunc TestKubeconfig_ContextNames(t *testing.T) {\n\ttl := WithMockKubeconfigLoader(\n\t\ttestutil.KC().WithCtxs(\n\t\t\ttestutil.Ctx(\"abc\"),\n\t\t\ttestutil.Ctx(\"def\"),\n\t\t\ttestutil.Ctx(\"ghi\")).Set(\"field1\", map[string]string{\"bar\": \"zoo\"}).ToYAML(t))\n\tkc := new(Kubeconfig).WithLoader(tl)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tctx, err := kc.ContextNames()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpected := []string{\"abc\", \"def\", \"ghi\"}\n\tif diff := cmp.Diff(expected, ctx); diff != \"\" {\n\t\tt.Fatalf(\"%s\", diff)\n\t}\n}\n\nfunc TestKubeconfig_ContextNames_noContextsEntry(t *testing.T) {\n\ttl := WithMockKubeconfigLoader(`a: b`)\n\tkc := new(Kubeconfig).WithLoader(tl)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tctx, err := kc.ContextNames()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar expected []string = nil\n\tif diff := cmp.Diff(expected, ctx); diff != \"\" {\n\t\tt.Fatalf(\"%s\", diff)\n\t}\n}\n\nfunc TestKubeconfig_ContextNames_nonArrayContextsEntry(t *testing.T) {\n\ttl := WithMockKubeconfigLoader(`contexts: \"hello\"`)\n\tkc := new(Kubeconfig).WithLoader(tl)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, err := kc.ContextNames()\n\tif err == nil {\n\t\tt.Fatal(\"expected error for non-array contexts entry\")\n\t}\n}\n\nfunc TestKubeconfig_CheckContextExists(t *testing.T) {\n\ttl := WithMockKubeconfigLoader(\n\t\ttestutil.KC().WithCtxs(\n\t\t\ttestutil.Ctx(\"c1\"),\n\t\t\ttestutil.Ctx(\"c2\")).ToYAML(t))\n\n\tkc := new(Kubeconfig).WithLoader(tl)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif exists, err := kc.ContextExists(\"c1\"); err != nil || !exists {\n\t\tt.Fatal(\"c1 actually exists; reported false\")\n\t}\n\tif exists, err := kc.ContextExists(\"c2\"); err != nil || !exists {\n\t\tt.Fatal(\"c2 actually exists; reported false\")\n\t}\n\tif exists, err := kc.ContextExists(\"c3\"); err != nil {\n\t\tt.Fatal(err)\n\t} else if exists {\n\t\tt.Fatal(\"c3 does not exist; but reported true\")\n\t}\n}\n"
  },
  {
    "path": "internal/kubeconfig/currentcontext.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"fmt\"\n\n\t\"sigs.k8s.io/kustomize/kyaml/yaml\"\n)\n\n// GetCurrentContext returns \"current-context\" value in given\n// kubeconfig object Node, or returns (\"\", nil) if not found.\nfunc (k *Kubeconfig) GetCurrentContext() (string, error) {\n\tv, err := k.config.Pipe(yaml.Get(\"current-context\"))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to read current-context: %w\", err)\n\t}\n\treturn yaml.GetValue(v), nil\n}\n\nfunc (k *Kubeconfig) UnsetCurrentContext() error {\n\treturn k.config.PipeE(yaml.SetField(\"current-context\", yaml.NewStringRNode(\"\")))\n}\n"
  },
  {
    "path": "internal/kubeconfig/currentcontext_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"testing\"\n\n\t\"github.com/ahmetb/kubectx/internal/testutil\"\n)\n\nfunc TestKubeconfig_GetCurrentContext(t *testing.T) {\n\ttl := WithMockKubeconfigLoader(`current-context: foo`)\n\tkc := new(Kubeconfig).WithLoader(tl)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tv, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := \"foo\"\n\tif v != expected {\n\t\tt.Fatalf(\"expected=\\\"%s\\\"; got=\\\"%s\\\"\", expected, v)\n\t}\n}\n\nfunc TestKubeconfig_GetCurrentContext_missingField(t *testing.T) {\n\ttl := WithMockKubeconfigLoader(`abc: def`)\n\tkc := new(Kubeconfig).WithLoader(tl)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tv, err := kc.GetCurrentContext()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := \"\"\n\tif v != expected {\n\t\tt.Fatalf(\"expected=\\\"%s\\\"; got=\\\"%s\\\"\", expected, v)\n\t}\n}\n\nfunc TestKubeconfig_UnsetCurrentContext(t *testing.T) {\n\ttl := WithMockKubeconfigLoader(testutil.KC().WithCurrentCtx(\"foo\").ToYAML(t))\n\tkc := new(Kubeconfig).WithLoader(tl)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.UnsetCurrentContext(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tout := tl.Output()\n\texpected := testutil.KC().WithCurrentCtx(\"\").ToYAML(t)\n\tif out != expected {\n\t\tt.Fatalf(\"expected=\\\"%s\\\"; got=\\\"%s\\\"\", expected, out)\n\t}\n}\n"
  },
  {
    "path": "internal/kubeconfig/helper_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n)\n\ntype MockKubeconfigLoader struct {\n\tin  io.Reader\n\tout bytes.Buffer\n}\n\nfunc (t *MockKubeconfigLoader) Read(p []byte) (n int, err error)  { return t.in.Read(p) }\nfunc (t *MockKubeconfigLoader) Write(p []byte) (n int, err error) { return t.out.Write(p) }\nfunc (t *MockKubeconfigLoader) Close() error                      { return nil }\nfunc (t *MockKubeconfigLoader) Reset() error                      { return nil }\nfunc (t *MockKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) {\n\treturn []ReadWriteResetCloser{ReadWriteResetCloser(t)}, nil\n}\nfunc (t *MockKubeconfigLoader) Output() string { return t.out.String() }\n\nfunc WithMockKubeconfigLoader(kubecfg string) *MockKubeconfigLoader {\n\treturn &MockKubeconfigLoader{in: strings.NewReader(kubecfg)}\n}\n"
  },
  {
    "path": "internal/kubeconfig/kubeconfig.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"sigs.k8s.io/kustomize/kyaml/yaml\"\n)\n\ntype ReadWriteResetCloser interface {\n\tio.ReadWriteCloser\n\n\t// Reset truncates the file and seeks to the beginning of the file.\n\tReset() error\n}\n\ntype Loader interface {\n\tLoad() ([]ReadWriteResetCloser, error)\n}\n\ntype Kubeconfig struct {\n\tloader Loader\n\n\tf      ReadWriteResetCloser\n\tconfig *yaml.RNode\n}\n\nfunc (k *Kubeconfig) WithLoader(l Loader) *Kubeconfig {\n\tk.loader = l\n\treturn k\n}\n\nfunc (k *Kubeconfig) Close() error {\n\tif k.f == nil {\n\t\treturn nil\n\t}\n\treturn k.f.Close()\n}\n\nfunc (k *Kubeconfig) Parse() error {\n\tfiles, err := k.loader.Load()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load: %w\", err)\n\t}\n\n\t// TODO since we don't support multiple kubeconfig files at the moment, there's just 1 file\n\tf := files[0]\n\n\tk.f = f\n\tvar v yaml.Node\n\tif err := yaml.NewDecoder(f).Decode(&v); err != nil {\n\t\treturn fmt.Errorf(\"failed to decode: %w\", err)\n\t}\n\tk.config = yaml.NewRNode(&v)\n\tif k.config.YNode().Kind != yaml.MappingNode {\n\t\treturn errors.New(\"kubeconfig file is not a map document\")\n\t}\n\treturn nil\n}\n\nfunc (k *Kubeconfig) Bytes() ([]byte, error) {\n\tstr, err := k.config.String()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []byte(str), nil\n}\n\nfunc (k *Kubeconfig) Save() error {\n\tif err := k.f.Reset(); err != nil {\n\t\treturn fmt.Errorf(\"failed to reset file: %w\", err)\n\t}\n\tenc := yaml.NewEncoder(k.f)\n\tenc.SetIndent(0)\n\tif err := enc.Encode(k.config.YNode()); err != nil {\n\t\treturn err\n\t}\n\treturn enc.Close()\n}\n"
  },
  {
    "path": "internal/kubeconfig/kubeconfig_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/ahmetb/kubectx/internal/testutil\"\n)\n\nfunc TestParse(t *testing.T) {\n\terr := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`a: [1, 2`)).Parse()\n\tif err == nil {\n\t\tt.Fatal(\"expected error from bad yaml\")\n\t}\n\n\terr = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`[1, 2, 3]`)).Parse()\n\tif err == nil {\n\t\tt.Fatal(\"expected error from not-mapping root node\")\n\t}\n\n\terr = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`current-context: foo`)).Parse()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(testutil.KC().\n\t\tWithCurrentCtx(\"foo\").\n\t\tWithCtxs().ToYAML(t))).Parse()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestSave(t *testing.T) {\n\tin := \"a: [1, 2, 3]\\n\"\n\ttest := WithMockKubeconfigLoader(in)\n\tkc := new(Kubeconfig).WithLoader(test)\n\tdefer kc.Close()\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.ModifyCurrentContext(\"hello\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpected := \"a: [1, 2, 3]\\ncurrent-context: hello\\n\"\n\tif diff := cmp.Diff(expected, test.Output()); diff != \"\" {\n\t\tt.Fatal(diff)\n\t}\n}\n"
  },
  {
    "path": "internal/kubeconfig/kubeconfigloader.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n)\n\nvar (\n\tDefaultLoader Loader = new(StandardKubeconfigLoader)\n)\n\ntype StandardKubeconfigLoader struct{}\n\ntype kubeconfigFile struct{ *os.File }\n\nfunc (*StandardKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) {\n\tcfgPath, err := kubeconfigPath()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot determine kubeconfig path: %w\", err)\n\t}\n\n\tf, err := os.OpenFile(cfgPath, os.O_RDWR, 0)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil, fmt.Errorf(\"kubeconfig file not found: %w\", err)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to open file: %w\", err)\n\t}\n\n\t// TODO we'll return all kubeconfig files when we start implementing multiple kubeconfig support\n\treturn []ReadWriteResetCloser{ReadWriteResetCloser(&kubeconfigFile{f})}, nil\n}\n\nfunc (kf *kubeconfigFile) Reset() error {\n\tif err := kf.Truncate(0); err != nil {\n\t\treturn fmt.Errorf(\"failed to truncate file: %w\", err)\n\t}\n\tif _, err := kf.Seek(0, 0); err != nil {\n\t\treturn fmt.Errorf(\"failed to seek in file: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc kubeconfigPath() (string, error) {\n\t// KUBECONFIG env var\n\tif v := os.Getenv(\"KUBECONFIG\"); v != \"\" {\n\t\tlist := filepath.SplitList(v)\n\t\tif len(list) > 1 {\n\t\t\t// TODO KUBECONFIG=file1:file2 currently not supported\n\t\t\treturn \"\", errors.New(\"multiple files in KUBECONFIG are currently not supported\")\n\t\t}\n\t\treturn v, nil\n\t}\n\n\t// default path\n\thome := cmdutil.HomeDir()\n\tif home == \"\" {\n\t\treturn \"\", errors.New(\"HOME or USERPROFILE environment variable not set\")\n\t}\n\treturn filepath.Join(home, \".kube\", \"config\"), nil\n}\n"
  },
  {
    "path": "internal/kubeconfig/kubeconfigloader_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/ahmetb/kubectx/internal/cmdutil\"\n)\n\nfunc Test_kubeconfigPath(t *testing.T) {\n\tt.Setenv(\"HOME\", \"/x/y/z\")\n\n\texpected := filepath.FromSlash(\"/x/y/z/.kube/config\")\n\tgot, err := kubeconfigPath()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got != expected {\n\t\tt.Fatalf(\"got=%q expected=%q\", got, expected)\n\t}\n}\n\nfunc Test_kubeconfigPath_noEnvVars(t *testing.T) {\n\tt.Setenv(\"XDG_CACHE_HOME\", \"\")\n\tt.Setenv(\"HOME\", \"\")\n\tt.Setenv(\"USERPROFILE\", \"\")\n\n\t_, err := kubeconfigPath()\n\tif err == nil {\n\t\tt.Fatalf(\"expected error\")\n\t}\n}\n\nfunc Test_kubeconfigPath_envOvveride(t *testing.T) {\n\tt.Setenv(\"KUBECONFIG\", \"foo\")\n\n\tv, err := kubeconfigPath()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif expected := \"foo\"; v != expected {\n\t\tt.Fatalf(\"expected=%q, got=%q\", expected, v)\n\t}\n}\n\nfunc Test_kubeconfigPath_envOvverideDoesNotSupportPathSeparator(t *testing.T) {\n\tpath := strings.Join([]string{\"file1\", \"file2\"}, string(os.PathListSeparator))\n\tt.Setenv(\"KUBECONFIG\", path)\n\n\t_, err := kubeconfigPath()\n\tif err == nil {\n\t\tt.Fatal(\"expected error\")\n\t}\n}\n\nfunc TestStandardKubeconfigLoader_returnsNotFoundErr(t *testing.T) {\n\tt.Setenv(\"KUBECONFIG\", \"foo\")\n\tkc := new(Kubeconfig).WithLoader(DefaultLoader)\n\terr := kc.Parse()\n\tif err == nil {\n\t\tt.Fatal(\"expected err\")\n\t}\n\tif !cmdutil.IsNotFoundErr(err) {\n\t\tt.Fatalf(\"expected ENOENT error; got=%v\", err)\n\t}\n}\n"
  },
  {
    "path": "internal/kubeconfig/namespace.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"sigs.k8s.io/kustomize/kyaml/yaml\"\n)\n\nconst (\n\tdefaultNamespace = \"default\"\n)\n\nfunc (k *Kubeconfig) NamespaceOfContext(contextName string) (string, error) {\n\tctx, err := k.contextNode(contextName)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tnamespace, err := ctx.Pipe(yaml.Lookup(\"context\", \"namespace\"))\n\tif namespace == nil || err != nil {\n\t\treturn defaultNamespace, err\n\t}\n\treturn yaml.GetValue(namespace), nil\n}\n\nfunc (k *Kubeconfig) SetNamespace(ctxName string, ns string) error {\n\tctx, err := k.contextNode(ctxName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := ctx.PipeE(\n\t\tyaml.LookupCreate(yaml.MappingNode, \"context\"),\n\t\tyaml.SetField(\"namespace\", yaml.NewStringRNode(ns)),\n\t); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/kubeconfig/namespace_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage kubeconfig\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/ahmetb/kubectx/internal/testutil\"\n)\n\nfunc TestKubeconfig_NamespaceOfContext_ctxNotFound(t *testing.T) {\n\tkc := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(testutil.KC().\n\t\tWithCtxs(testutil.Ctx(\"c1\")).ToYAML(t)))\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err := kc.NamespaceOfContext(\"c2\")\n\tif err == nil {\n\t\tt.Fatal(\"expected err\")\n\t}\n}\n\nfunc TestKubeconfig_NamespaceOfContext(t *testing.T) {\n\tkc := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(testutil.KC().\n\t\tWithCtxs(\n\t\t\ttestutil.Ctx(\"c1\"),\n\t\t\ttestutil.Ctx(\"c2\").Ns(\"c2n1\")).ToYAML(t)))\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tv1, err := kc.NamespaceOfContext(\"c1\")\n\tif err != nil {\n\t\tt.Fatal(\"expected err\")\n\t}\n\tif expected := `default`; v1 != expected {\n\t\tt.Fatalf(\"c1: expected=\\\"%s\\\" got=\\\"%s\\\"\", expected, v1)\n\t}\n\n\tv2, err := kc.NamespaceOfContext(\"c2\")\n\tif err != nil {\n\t\tt.Fatal(\"expected err\")\n\t}\n\tif expected := `c2n1`; v2 != expected {\n\t\tt.Fatalf(\"c2: expected=\\\"%s\\\" got=\\\"%s\\\"\", expected, v2)\n\t}\n}\n\nfunc TestKubeconfig_SetNamespace(t *testing.T) {\n\tl := WithMockKubeconfigLoader(testutil.KC().\n\t\tWithCtxs(\n\t\t\ttestutil.Ctx(\"c1\"),\n\t\t\ttestutil.Ctx(\"c2\").Ns(\"c2n1\")).ToYAML(t))\n\tkc := new(Kubeconfig).WithLoader(l)\n\tif err := kc.Parse(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := kc.SetNamespace(\"c3\", \"foo\"); err == nil {\n\t\tt.Fatalf(\"expected error for non-existing ctx\")\n\t}\n\n\tif err := kc.SetNamespace(\"c1\", \"c1n1\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.SetNamespace(\"c2\", \"c2n2\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := kc.Save(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := testutil.KC().WithCtxs(\n\t\ttestutil.Ctx(\"c1\").Ns(\"c1n1\"),\n\t\ttestutil.Ctx(\"c2\").Ns(\"c2n2\")).ToYAML(t)\n\tif diff := cmp.Diff(l.Output(), expected); diff != \"\" {\n\t\tt.Fatal(diff)\n\t}\n}\n"
  },
  {
    "path": "internal/printer/color.go",
    "content": "// Copyright 2021 Google LLC\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\npackage printer\n\nimport (\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/ahmetb/kubectx/internal/env\"\n)\n\nvar (\n\tActiveItemColor = color.New(color.FgGreen, color.Bold)\n)\n\nfunc init() {\n\tEnableOrDisableColor(ActiveItemColor)\n}\n\n// useColors returns true if colors are force-enabled,\n// false if colors are disabled, or nil for default behavior\n// which is determined based on factors like if stdout is tty.\nfunc useColors() *bool {\n\ttr, fa := true, false\n\tif os.Getenv(env.EnvForceColor) != \"\" {\n\t\treturn &tr\n\t} else if os.Getenv(env.EnvNoColor) != \"\" {\n\t\treturn &fa\n\t}\n\treturn nil\n}\n\n// EnableOrDisableColor determines if color should be force-enabled or force-disabled\n// or left untouched based on environment configuration.\nfunc EnableOrDisableColor(c *color.Color) {\n\tif v := useColors(); v != nil && *v {\n\t\tc.EnableColor()\n\t} else if v != nil && !*v {\n\t\tc.DisableColor()\n\t}\n}\n"
  },
  {
    "path": "internal/printer/color_test.go",
    "content": "// Copyright 2021 Google LLC\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\npackage printer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nvar (\n\ttr, fa = true, false\n)\n\nfunc Test_useColors_forceColors(t *testing.T) {\n\tt.Setenv(\"_KUBECTX_FORCE_COLOR\", \"1\")\n\tt.Setenv(\"NO_COLOR\", \"1\")\n\n\tif v := useColors(); !cmp.Equal(v, &tr) {\n\t\tt.Fatalf(\"expected useColors() = true; got = %v\", v)\n\t}\n}\n\nfunc Test_useColors_disableColors(t *testing.T) {\n\tt.Setenv(\"NO_COLOR\", \"1\")\n\n\tif v := useColors(); !cmp.Equal(v, &fa) {\n\t\tt.Fatalf(\"expected useColors() = false; got = %v\", v)\n\t}\n}\n\nfunc Test_useColors_default(t *testing.T) {\n\tt.Setenv(\"NO_COLOR\", \"\")\n\tt.Setenv(\"_KUBECTX_FORCE_COLOR\", \"\")\n\n\tif v := useColors(); v != nil {\n\t\tt.Fatalf(\"expected useColors() = nil; got=%v\", *v)\n\t}\n}\n"
  },
  {
    "path": "internal/printer/printer.go",
    "content": "// Copyright 2021 Google LLC\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\npackage printer\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fatih/color\"\n)\n\nvar (\n\tErrorColor   = color.New(color.FgRed, color.Bold)\n\tWarningColor = color.New(color.FgYellow, color.Bold)\n\tSuccessColor = color.New(color.FgGreen)\n)\n\nfunc init() {\n\tcolors := useColors()\n\tif colors == nil {\n\t\treturn\n\t}\n\tif *colors {\n\t\tErrorColor.EnableColor()\n\t\tWarningColor.EnableColor()\n\t\tSuccessColor.EnableColor()\n\t} else {\n\t\tErrorColor.DisableColor()\n\t\tWarningColor.DisableColor()\n\t\tSuccessColor.DisableColor()\n\t}\n}\n\nfunc Error(w io.Writer, format string, args ...any) error {\n\t_, err := io.WriteString(w, ErrorColor.Sprint(\"error: \")+fmt.Sprintf(format, args...)+\"\\n\")\n\treturn err\n}\n\nfunc Warning(w io.Writer, format string, args ...any) error {\n\t_, err := io.WriteString(w, WarningColor.Sprint(\"warning: \")+fmt.Sprintf(format, args...)+\"\\n\")\n\treturn err\n}\n\nfunc Success(w io.Writer, format string, args ...any) error {\n\t_, err := io.WriteString(w, SuccessColor.Sprint(\"✔ \")+fmt.Sprintf(format, args...)+\"\\n\")\n\treturn err\n}\n"
  },
  {
    "path": "internal/testutil/kubeconfigbuilder.go",
    "content": "// Copyright 2021 Google LLC\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\npackage testutil\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"sigs.k8s.io/kustomize/kyaml/yaml\"\n)\n\ntype Context struct {\n\tName    string `yaml:\"name,omitempty\"`\n\tContext struct {\n\t\tNamespace string `yaml:\"namespace,omitempty\"`\n\t} `yaml:\"context,omitempty\"`\n}\n\nfunc Ctx(name string) *Context           { return &Context{Name: name} }\nfunc (c *Context) Ns(ns string) *Context { c.Context.Namespace = ns; return c }\n\ntype Kubeconfig map[string]any\n\nfunc KC() *Kubeconfig {\n\treturn &Kubeconfig{\n\t\t\"apiVersion\": \"v1\",\n\t\t\"kind\":       \"Config\"}\n}\n\nfunc (k *Kubeconfig) Set(key string, v any) *Kubeconfig   { (*k)[key] = v; return k }\nfunc (k *Kubeconfig) WithCurrentCtx(s string) *Kubeconfig { (*k)[\"current-context\"] = s; return k }\nfunc (k *Kubeconfig) WithCtxs(c ...*Context) *Kubeconfig  { (*k)[\"contexts\"] = c; return k }\n\nfunc (k *Kubeconfig) ToYAML(t *testing.T) string {\n\tt.Helper()\n\tvar v strings.Builder\n\tenc := yaml.NewEncoder(&v)\n\tenc.SetIndent(0)\n\tif err := enc.Encode(*k); err != nil {\n\t\tt.Fatalf(\"failed to encode mock kubeconfig: %v\", err)\n\t}\n\treturn v.String()\n}\n"
  },
  {
    "path": "kubectx",
    "content": "#!/usr/bin/env bash\n#\n# kubectx(1) is a utility to manage and switch between kubectl contexts.\n\n# Copyright 2017 Google Inc.\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 $DEBUG ]] && set -x\n\nset -eou pipefail\nIFS=$'\\n\\t'\n\nSELF_CMD=\"$0\"\n\nKUBECTX=\"${XDG_CACHE_HOME:-$HOME/.kube}/kubectx\"\n\nusage() {\n  local SELF\n  SELF=\"kubectx\"\n  if [[ \"$(basename \"$0\")\" == kubectl-* ]]; then # invoked as plugin\n    SELF=\"kubectl ctx\"\n  fi\n\n  cat <<EOF\nManage and switch between kubectl contexts.\n\nUSAGE:\n  $SELF                       : list the contexts\n  $SELF <NAME>                : switch to context <NAME>\n  $SELF -                     : switch to the previous context\n  $SELF -c, --current         : show the current context name\n  $SELF <NEW_NAME>=<NAME>     : rename context <NAME> to <NEW_NAME>\n  $SELF <NEW_NAME>=.          : rename current-context to <NEW_NAME>\n  $SELF -d <NAME> [<NAME...>] : delete context <NAME> ('.' for current-context)\n                                  (this command won't delete the user/cluster entry\n                                  that is used by the context)\n  $SELF -u, --unset           : unset the current context\n\n  $SELF -h,--help             : show this message\n\n  (This executable is the legacy bash-based implementation, consider upgrading to Go-based implementation.)\nEOF\n}\n\nexit_err() {\n   echo >&2 \"${1}\"\n   exit 1\n}\n\ncurrent_context() {\n  $KUBECTL config view -o=jsonpath='{.current-context}'\n}\n\nget_contexts() {\n  $KUBECTL config get-contexts -o=name | sort -n\n}\n\nlist_contexts() {\n  set -u pipefail\n  local cur ctx_list\n  cur=\"$(current_context)\" || exit_err \"error getting current context\"\n  ctx_list=$(get_contexts) || exit_err \"error getting context list\"\n\n  local yellow darkbg normal\n  yellow=$(tput setaf 3 || true)\n  darkbg=$(tput setab 0 || true)\n  normal=$(tput sgr0 || true)\n\n  local cur_ctx_fg cur_ctx_bg\n  cur_ctx_fg=${KUBECTX_CURRENT_FGCOLOR:-$yellow}\n  cur_ctx_bg=${KUBECTX_CURRENT_BGCOLOR:-$darkbg}\n\n  for c in $ctx_list; do\n  if [[ -n \"${_KUBECTX_FORCE_COLOR:-}\" || \\\n       -t 1 && -z \"${NO_COLOR:-}\" ]]; then\n    # colored output mode\n    if [[ \"${c}\" = \"${cur}\" ]]; then\n      echo \"${cur_ctx_bg}${cur_ctx_fg}${c}${normal}\"\n    else\n      echo \"${c}\"\n    fi\n  else\n    echo \"${c}\"\n  fi\n  done\n}\n\nread_context() {\n  if [[ -f \"${KUBECTX}\" ]]; then\n    cat \"${KUBECTX}\"\n  fi\n}\n\nsave_context() {\n  local saved\n  saved=\"$(read_context)\"\n\n  if [[ \"${saved}\" != \"${1}\" ]]; then\n    printf %s \"${1}\" > \"${KUBECTX}\"\n  fi\n}\n\nswitch_context() {\n  $KUBECTL config use-context \"${1}\"\n}\n\nchoose_context_interactive() {\n  local choice\n  choice=\"$(_KUBECTX_FORCE_COLOR=1 \\\n    FZF_DEFAULT_COMMAND=\"${SELF_CMD}\" \\\n    fzf --ansi --no-preview || true)\"\n  if [[ -z \"${choice}\" ]]; then\n    echo 2>&1 \"error: you did not choose any of the options\"\n    exit 1\n  else\n    set_context \"${choice}\"\n  fi\n}\n\nset_context() {\n  local prev\n  prev=\"$(current_context)\" || exit_err \"error getting current context\"\n\n  switch_context \"${1}\"\n\n  if [[ \"${prev}\" != \"${1}\" ]]; then\n    save_context \"${prev}\"\n  fi\n}\n\nswap_context() {\n  local ctx\n  ctx=\"$(read_context)\"\n  if [[ -z \"${ctx}\" ]]; then\n    echo \"error: No previous context found.\" >&2\n    exit 1\n  fi\n  set_context \"${ctx}\"\n}\n\ncontext_exists() {\n  grep -q ^\"${1}\"\\$ <($KUBECTL config get-contexts -o=name)\n}\n\nrename_context() {\n  local old_name=\"${1}\"\n  local new_name=\"${2}\"\n\n  if [[ \"${old_name}\" == \".\" ]]; then\n    old_name=\"$(current_context)\"\n  fi\n\n  if ! context_exists \"${old_name}\"; then\n    echo \"error: Context \\\"${old_name}\\\" not found, can't rename it.\" >&2\n    exit 1\n  fi\n\n  if context_exists \"${new_name}\"; then\n    echo \"Context \\\"${new_name}\\\" exists, deleting...\" >&2\n    $KUBECTL config delete-context \"${new_name}\" 1>/dev/null 2>&1\n  fi\n\n  $KUBECTL config rename-context \"${old_name}\" \"${new_name}\"\n}\n\ndelete_contexts() {\n  for i in \"${@}\"; do\n    delete_context \"${i}\"\n  done\n}\n\ndelete_context() {\n  local ctx\n  ctx=\"${1}\"\n  if [[ \"${ctx}\" == \".\" ]]; then\n    ctx=\"$(current_context)\" || exit_err \"error getting current context\"\n  fi\n  echo \"Deleting context \\\"${ctx}\\\"...\" >&2\n  $KUBECTL config delete-context \"${ctx}\"\n}\n\nunset_context() {\n  echo \"Unsetting current context.\" >&2\n  $KUBECTL config unset current-context\n}\n\nmain() {\n  if [[ -z \"${KUBECTL:-}\" ]]; then\n    if hash kubectl 2>/dev/null; then\n      KUBECTL=kubectl\n    elif hash kubectl.exe  2>/dev/null; then\n      KUBECTL=kubectl.exe\n    else\n      echo >&2 \"kubectl is not installed\"\n      exit 1\n    fi\n  fi\n\n  if [[ \"$#\" -eq 0 ]]; then\n    if [[ -t 1 &&  -z \"${KUBECTX_IGNORE_FZF:-}\" && \"$(type fzf &>/dev/null; echo $?)\" -eq 0 ]]; then\n      choose_context_interactive\n    else\n      list_contexts\n    fi\n  elif [[ \"${1}\" == \"-d\" ]]; then\n    if [[ \"$#\" -lt 2 ]]; then\n      echo \"error: missing context NAME\" >&2\n      usage\n      exit 1\n    fi\n    delete_contexts \"${@:2}\"\n  elif [[ \"$#\" -gt 1 ]]; then\n    echo \"error: too many arguments\" >&2\n    usage\n    exit 1\n  elif [[ \"$#\" -eq 1 ]]; then\n    if [[ \"${1}\" == \"-\" ]]; then\n      swap_context\n    elif [[ \"${1}\" == '-c' || \"${1}\" == '--current' ]]; then\n      # we don't call current_context here for two reasons:\n      # - it does not fail when current-context property is not set\n      # - it does not return a trailing newline\n      $KUBECTL config current-context\n    elif [[ \"${1}\" == '-u' || \"${1}\" == '--unset' ]]; then\n      unset_context\n    elif [[ \"${1}\" == '-h' || \"${1}\" == '--help' ]]; then\n      usage\n    elif [[ \"${1}\" =~ ^-(.*) ]]; then\n      echo \"error: unrecognized flag \\\"${1}\\\"\" >&2\n      usage\n      exit 1\n    elif [[ \"${1}\" =~ (.+)=(.+) ]]; then\n      rename_context \"${BASH_REMATCH[2]}\" \"${BASH_REMATCH[1]}\"\n    else\n      set_context \"${1}\"\n    fi\n  else\n    usage\n    exit 1\n  fi\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "kubens",
    "content": "#!/usr/bin/env bash\n#\n# kubens(1) is a utility to switch between Kubernetes namespaces.\n\n# Copyright 2017 Google Inc.\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 $DEBUG ]] && set -x\n\nset -eou pipefail\nIFS=$'\\n\\t'\n\nSELF_CMD=\"$0\"\n\nKUBENS_DIR=\"${XDG_CACHE_HOME:-$HOME/.kube}/kubens\"\n\nusage() {\n  local SELF\n  SELF=\"kubens\"\n  if [[ \"$(basename \"$0\")\" == kubectl-* ]]; then # invoked as plugin\n    SELF=\"kubectl ns\"\n  fi\n\n  cat <<EOF\nSwitch between Kubernetes namespaces.\n\nUSAGE:\n  $SELF                    : list the namespaces in the current context\n  $SELF <NAME>             : change the active namespace of current context\n  $SELF -                  : switch to the previous namespace in this context\n  $SELF -c, --current      : show the current namespace\n  $SELF -h,--help          : show this message\n\n  (This executable is the legacy bash-based implementation, consider upgrading to Go-based implementation.)\nEOF\n}\n\nexit_err() {\n   echo >&2 \"${1}\"\n   exit 1\n}\n\ncurrent_namespace() {\n  local cur_ctx\n\n  cur_ctx=\"$(current_context)\" || exit_err \"error getting current context\"\n  ns=\"$($KUBECTL config view -o=jsonpath=\"{.contexts[?(@.name==\\\"${cur_ctx}\\\")].context.namespace}\")\" \\\n     || exit_err \"error getting current namespace\"\n\n  if [[ -z \"${ns}\" ]]; then\n    echo \"default\"\n  else\n    echo \"${ns}\"\n  fi\n}\n\ncurrent_context() {\n  $KUBECTL config current-context\n}\n\nget_namespaces() {\n  $KUBECTL get namespaces -o=jsonpath='{range .items[*].metadata.name}{@}{\"\\n\"}{end}'\n}\n\nescape_context_name() {\n  echo \"${1//\\//-}\"\n}\n\nnamespace_file() {\n  local ctx\n\n  ctx=\"$(escape_context_name \"${1}\")\"\n  echo \"${KUBENS_DIR}/${ctx}\"\n}\n\nread_namespace() {\n  local f\n  f=\"$(namespace_file \"${1}\")\"\n  [[ -f \"${f}\" ]] && cat \"${f}\"\n  return 0\n}\n\nsave_namespace() {\n  mkdir -p \"${KUBENS_DIR}\"\n  local f saved\n  f=\"$(namespace_file \"${1}\")\"\n  saved=\"$(read_namespace \"${1}\")\"\n\n  if [[ \"${saved}\" != \"${2}\" ]]; then\n    printf %s \"${2}\" > \"${f}\"\n  fi\n}\n\nswitch_namespace() {\n  local ctx=\"${1}\"\n  $KUBECTL config set-context \"${ctx}\" --namespace=\"${2}\"\n  echo \"Active namespace is \\\"${2}\\\".\">&2\n}\n\nchoose_namespace_interactive() {\n  # directly calling kubens via fzf might fail with a cryptic error like\n  # \"$FZF_DEFAULT_COMMAND failed\", so try to see if we can list namespaces\n  # locally first\n  if [[ -z \"$(list_namespaces)\" ]]; then\n    echo >&2 \"error: could not list namespaces (is the cluster accessible?)\"\n    exit 1\n  fi\n\n  local choice\n  choice=\"$(_KUBECTX_FORCE_COLOR=1 \\\n    FZF_DEFAULT_COMMAND=\"${SELF_CMD}\" \\\n    fzf --ansi --no-preview || true)\"\n  if [[ -z \"${choice}\" ]]; then\n    echo 2>&1 \"error: you did not choose any of the options\"\n    exit 1\n  else\n    set_namespace \"${choice}\"\n  fi\n}\n\nset_namespace() {\n  local ctx prev\n  ctx=\"$(current_context)\" || exit_err \"error getting current context\"\n  prev=\"$(current_namespace)\" || exit_error \"error getting current namespace\"\n\n  if grep -q ^\"${1}\"\\$ <(get_namespaces); then\n    switch_namespace \"${ctx}\" \"${1}\"\n\n    if [[ \"${prev}\" != \"${1}\" ]]; then\n      save_namespace \"${ctx}\" \"${prev}\"\n    fi\n  else\n    echo \"error: no namespace exists with name \\\"${1}\\\".\">&2\n    exit 1\n  fi\n}\n\nlist_namespaces() {\n  local yellow darkbg normal\n  yellow=$(tput setaf 3 || true)\n  darkbg=$(tput setab 0 || true)\n  normal=$(tput sgr0 || true)\n\n  local cur_ctx_fg cur_ctx_bg\n  cur_ctx_fg=${KUBECTX_CURRENT_FGCOLOR:-$yellow}\n  cur_ctx_bg=${KUBECTX_CURRENT_BGCOLOR:-$darkbg}\n\n  local cur ns_list\n  cur=\"$(current_namespace)\" || exit_err \"error getting current namespace\"\n  ns_list=$(get_namespaces) || exit_err \"error getting namespace list\"\n\n  for c in $ns_list; do\n  if [[ -n \"${_KUBECTX_FORCE_COLOR:-}\" || \\\n       -t 1 && -z \"${NO_COLOR:-}\" ]]; then\n    # colored output mode\n    if [[ \"${c}\" = \"${cur}\" ]]; then\n      echo \"${cur_ctx_bg}${cur_ctx_fg}${c}${normal}\"\n    else\n      echo \"${c}\"\n    fi\n  else\n    echo \"${c}\"\n  fi\n  done\n}\n\nswap_namespace() {\n  local ctx ns\n  ctx=\"$(current_context)\" || exit_err \"error getting current context\"\n  ns=\"$(read_namespace \"${ctx}\")\"\n  if [[ -z \"${ns}\" ]]; then\n    echo \"error: No previous namespace found for current context.\" >&2\n    exit 1\n  fi\n  set_namespace \"${ns}\"\n}\n\nmain() {\n  if [[ -z \"${KUBECTL:-}\" ]]; then\n    if hash kubectl 2>/dev/null; then\n      KUBECTL=kubectl\n    elif hash kubectl.exe  2>/dev/null; then\n      KUBECTL=kubectl.exe\n    else\n      echo >&2 \"kubectl is not installed\"\n      exit 1\n    fi\n  fi\n\n  if [[ \"$#\" -eq 0 ]]; then\n    if [[ -t 1 && -z ${KUBECTX_IGNORE_FZF:-} && \"$(type fzf &>/dev/null; echo $?)\" -eq 0 ]]; then\n      choose_namespace_interactive\n    else\n      list_namespaces\n    fi\n  elif [[ \"$#\" -eq 1 ]]; then\n    if [[ \"${1}\" == '-h' || \"${1}\" == '--help' ]]; then\n      usage\n    elif [[ \"${1}\" == \"-\" ]]; then\n      swap_namespace\n    elif [[ \"${1}\" == '-c' || \"${1}\" == '--current' ]]; then\n      current_namespace\n    elif [[ \"${1}\" =~ ^-(.*) ]]; then\n      echo \"error: unrecognized flag \\\"${1}\\\"\" >&2\n      usage\n      exit 1\n    elif [[ \"${1}\" =~ (.+)=(.+) ]]; then\n      alias_context \"${BASH_REMATCH[2]}\" \"${BASH_REMATCH[1]}\"\n    else\n      set_namespace \"${1}\"\n    fi\n  else\n    echo \"error: too many flags\" >&2\n    usage\n    exit 1\n  fi\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "test/common.bash",
    "content": "#!/usr/bin/env bats\n\n# bats setup function\nsetup() {\n  TEMP_HOME=\"$(mktemp -d)\"\n  export TEMP_HOME\n  export HOME=$TEMP_HOME\n  export KUBECONFIG=\"${TEMP_HOME}/config\"\n}\n\n# bats teardown function\nteardown() {\n  rm -rf \"$TEMP_HOME\"\n}\n\nuse_config() {\n  cp \"$BATS_TEST_DIRNAME/testdata/$1\" $KUBECONFIG\n}\n\n# wrappers around \"kubectl config\" command\n\nget_namespace() {\n  kubectl config view -o=jsonpath=\"{.contexts[?(@.name==\\\"$(get_context)\\\")].context.namespace}\"\n}\n\nget_context() {\n  kubectl config current-context\n}\n\nswitch_context() {\n  kubectl config use-context \"${1}\"\n}\n"
  },
  {
    "path": "test/kubectx.bats",
    "content": "#!/usr/bin/env bats\n\nCOMMAND=\"${COMMAND:-$BATS_TEST_DIRNAME/../kubectx}\"\n\nload common\n\n@test \"--help should not fail\" {\n  run ${COMMAND} --help\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n}\n\n@test \"-h should not fail\" {\n  run ${COMMAND} -h\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n}\n\n@test \"switch to previous context when no one exists\" {\n  use_config config1\n\n  run ${COMMAND} -\n  echo \"$output\"\n  [ \"$status\" -eq 1 ]\n  [[ $output = *\"no previous context found\" ]]\n}\n\n@test \"list contexts when no kubeconfig exists\" {\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ \"$output\" = \"warning: kubeconfig file not found\" ]]\n}\n\n@test \"get one context and list contexts\" {\n  use_config config1\n\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ \"$output\" = \"user1@cluster1\" ]]\n}\n\n@test \"get two contexts and list contexts\" {\n  use_config config2\n\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ \"$output\" = *\"user1@cluster1\"* ]]\n  [[ \"$output\" = *\"user2@cluster1\"* ]]\n}\n\n@test \"get two contexts and select contexts\" {\n  use_config config2\n\n  run ${COMMAND} user1@cluster1\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  echo \"$(get_context)\"\n  [[ \"$(get_context)\" = \"user1@cluster1\" ]]\n\n  run ${COMMAND} user2@cluster1\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  echo \"$(get_context)\"\n  [[ \"$(get_context)\" = \"user2@cluster1\" ]]\n}\n\n@test \"get two contexts and switch between contexts\" {\n  use_config config2\n\n  run ${COMMAND} user1@cluster1\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  echo \"$(get_context)\"\n  [[ \"$(get_context)\" = \"user1@cluster1\" ]]\n\n  run ${COMMAND} user2@cluster1\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  echo \"$(get_context)\"\n  [[ \"$(get_context)\" = \"user2@cluster1\" ]]\n\n  run ${COMMAND} -\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  echo \"$(get_context)\"\n  [[ \"$(get_context)\" = \"user1@cluster1\" ]]\n\n  run ${COMMAND} -\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  echo \"$(get_context)\"\n  [[ \"$(get_context)\" = \"user2@cluster1\" ]]\n}\n\n@test \"get one context and switch to non existent context\" {\n  use_config config1\n\n  run ${COMMAND} \"unknown-context\"\n  echo \"$output\"\n  [ \"$status\" -eq 1 ]\n}\n\n@test \"-c/--current fails when no context set\" {\n  use_config config1\n\n  run \"${COMMAND}\" -c\n  echo \"$output\"\n  [ $status -eq 1 ]\n  run \"${COMMAND}\" --current\n  echo \"$output\"\n  [ $status -eq 1 ]\n}\n\n@test \"-c/--current prints the current context\" {\n  use_config config1\n\n  run \"${COMMAND}\" user1@cluster1\n  [ $status -eq 0 ]\n\n  run \"${COMMAND}\" -c\n  echo \"$output\"\n  [ $status -eq 0 ]\n  [[ \"$output\" = \"user1@cluster1\" ]]\n  run \"${COMMAND}\" --current\n  echo \"$output\"\n  [ $status -eq 0 ]\n  [[ \"$output\" = \"user1@cluster1\" ]]\n}\n\n@test \"rename context\" {\n  use_config config2\n\n  run ${COMMAND} \"new-context=user1@cluster1\"\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ ! \"$output\" = *\"user1@cluster1\"* ]]\n  [[ \"$output\" = *\"new-context\"* ]]\n  [[ \"$output\" = *\"user2@cluster1\"* ]]\n}\n\n@test \"rename current context\" {\n  use_config config2\n\n  run ${COMMAND} user2@cluster1\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND} new-context=.\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ ! \"$output\" = *\"user2@cluster1\"* ]]\n  [[ \"$output\" = *\"user1@cluster1\"* ]]\n  [[ \"$output\" = *\"new-context\"* ]]\n}\n\n@test \"delete context\" {\n  use_config config2\n\n  run ${COMMAND} -d \"user1@cluster1\"\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ ! \"$output\" = \"user1@cluster1\" ]]\n  [[ \"$output\" = \"user2@cluster1\" ]]\n}\n\n@test \"delete current context\" {\n  use_config config2\n\n  run ${COMMAND} user2@cluster1\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND} -d .\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ ! \"$output\" = \"user2@cluster1\" ]]\n  [[ \"$output\" = \"user1@cluster1\" ]]\n}\n\n@test \"delete non existent context\" {\n  use_config config1\n\n  run ${COMMAND} -d \"unknown-context\"\n  echo \"$output\"\n  [ \"$status\" -eq 1 ]\n}\n\n@test \"delete several contexts\" {\n  use_config config2\n\n  run ${COMMAND} -d \"user1@cluster1\" \"user2@cluster1\"\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ \"$output\" = \"\" ]]\n}\n\n@test \"delete several contexts including a non existent one\" {\n  use_config config2\n\n  run ${COMMAND} -d \"user1@cluster1\" \"non-existent\" \"user2@cluster1\"\n  echo \"$output\"\n  [ \"$status\" -eq 1 ]\n\n  run ${COMMAND}\n  echo \"$output\"\n  [ \"$status\" -eq 0 ]\n  [[ \"$output\" = \"user2@cluster1\" ]]\n}\n\n@test \"unset selected context\" {\n  use_config config2\n\n  run ${COMMAND} user1@cluster1\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND} -u\n  [ \"$status\" -eq 0 ]\n\n  run ${COMMAND} -c\n  [ \"$status\" -ne 0 ]\n}\n"
  },
  {
    "path": "test/kubens.bats",
    "content": "#!/usr/bin/env bats\n\nCOMMAND=\"${COMMAND:-$BATS_TEST_DIRNAME/../kubens}\"\n\n# TODO(ahmetb) remove this after bash implementations are deleted\nexport KUBECTL=\"$BATS_TEST_DIRNAME/../test/mock-kubectl\"\n\n# short-circuit namespace querying in kubens go implementation\nexport _MOCK_NAMESPACES=1\n\nload common\n\n@test \"--help should not fail\" {\n  run ${COMMAND} --help\n  echo \"$output\">&2\n  [[ \"$status\" -eq 0 ]]\n}\n\n@test \"-h should not fail\" {\n  run ${COMMAND} -h\n  echo \"$output\">&2\n  [[ \"$status\" -eq 0 ]]\n}\n\n@test \"list namespaces when no kubeconfig exists\" {\n  run ${COMMAND}\n  echo \"$output\"\n  [[ \"$status\" -eq 1 ]]\n}\n\n@test \"list namespaces\" {\n  use_config config1\n  switch_context user1@cluster1\n\n  run ${COMMAND}\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  [[ \"$output\" = *\"ns1\"* ]]\n  [[ \"$output\" = *\"ns2\"* ]]\n}\n\n@test \"switch to existing namespace\" {\n  use_config config1\n  switch_context user1@cluster1\n\n  run ${COMMAND} \"ns1\"\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  [[ \"$output\" = *'Active namespace is \"ns1\"'* ]]\n}\n\n@test \"switch to non-existing namespace\" {\n  use_config config1\n  switch_context user1@cluster1\n\n  run ${COMMAND} \"unknown-namespace\"\n  echo \"$output\"\n  [[ \"$status\" -eq 1 ]]\n  [[ \"$output\" = *'no namespace exists with name \"unknown-namespace\"'* ]]\n}\n\n@test \"switch between namespaces\" {\n  use_config config1\n  switch_context user1@cluster1\n\n  run ${COMMAND} ns1\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  echo \"$(get_namespace)\"\n  [[ \"$(get_namespace)\" = \"ns1\" ]]\n\n  run ${COMMAND} ns2\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  echo \"$(get_namespace)\"\n  [[ \"$(get_namespace)\" = \"ns2\" ]]\n\n  run ${COMMAND} -\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  echo \"$(get_namespace)\"\n  [[ \"$(get_namespace)\" = \"ns1\" ]]\n\n  run ${COMMAND} -\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  echo \"$(get_namespace)\"\n  [[ \"$(get_namespace)\" = \"ns2\" ]]\n}\n\n@test \"switch to previous namespace when none exists\" {\n  use_config config1\n  switch_context user1@cluster1\n\n  run ${COMMAND} -\n  echo \"$output\"\n  [[ \"$status\" -eq 1 ]]\n  [[ \"$output\" = *\"No previous namespace found for current context\"* ]]\n}\n\n@test \"switch to namespace when current context is empty\" {\n  use_config config1\n\n  run ${COMMAND} -\n  echo \"$output\"\n  [[ \"$status\" -eq 1 ]]\n  [[ \"$output\" = *\"current-context is not set\"* ]]\n}\n\n@test \"-c/--current works when no namespace is set on context\" {\n  use_config config1\n  switch_context user1@cluster1\n\n  run ${COMMAND} \"-c\"\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  [[ \"$output\" = \"default\" ]]\n  run ${COMMAND} \"--current\"\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  [[ \"$output\" = \"default\" ]]\n}\n\n@test \"-c/--current prints the namespace after it is set\" {\n  use_config config1\n  switch_context user1@cluster1\n  ${COMMAND} ns1\n\n  run ${COMMAND} \"-c\"\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  [[ \"$output\" = \"ns1\" ]]\n  run ${COMMAND} \"--current\"\n  echo \"$output\"\n  [[ \"$status\" -eq 0 ]]\n  [[ \"$output\" = \"ns1\" ]]\n}\n\n@test \"-c/--current fails when current context is not set\" {\n  use_config config1\n  run ${COMMAND} -c\n  echo \"$output\"\n  [[ \"$status\" -eq 1 ]]\n\n  run ${COMMAND} --current\n  echo \"$output\"\n  [[ \"$status\" -eq 1 ]]\n}\n"
  },
  {
    "path": "test/mock-kubectl",
    "content": "#!/usr/bin/env bash\n\n[[ -n $DEBUG ]] && set -x\n\nset -eou pipefail\n\nif [[ $@ == *'get namespaces'* ]]; then\n  echo \"ns1\"\n  echo \"ns2\"\nelse\n  kubectl $@\nfi\n"
  },
  {
    "path": "test/testdata/config1",
    "content": "# config with one context\n\napiVersion: v1\nclusters:\n- cluster:\n    server: \"\"\n  name: cluster1\ncontexts:\n- context:\n    cluster: cluster1\n    user: user1\n  name: user1@cluster1\ncurrent-context: \"\"\nkind: Config\npreferences: {}\nusers:\n- name: user1\n  user: {}\n"
  },
  {
    "path": "test/testdata/config2",
    "content": "# config with two contexts\n\napiVersion: v1\nclusters:\n- cluster:\n    server: \"\"\n  name: cluster1\ncontexts:\n- context:\n    cluster: cluster1\n    user: user1\n  name: user1@cluster1\n- context:\n    cluster: cluster1\n    user: user2\n  name: user2@cluster1\ncurrent-context: \"\"\nkind: Config\npreferences: {}\nusers:\n- name: user1\n  user: {}\n- name: user2\n  user: {}\n"
  }
]