Full Code of ahmetb/kubectx for AI

master f500964e2415 cached
82 files
171.1 KB
53.3k tokens
186 symbols
1 requests
Download .txt
Repository: ahmetb/kubectx
Branch: master
Commit: f500964e2415
Files: 82
Total size: 171.1 KB

Directory structure:
gitextract_5i38l93y/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── bash-frozen.yml
│       ├── ci.yml
│       ├── dependabot.yml
│       └── release.yml
├── .goreleaser.yml
├── .krew/
│   ├── ctx.yaml
│   └── ns.yaml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cmd/
│   ├── kubectx/
│   │   ├── current.go
│   │   ├── delete.go
│   │   ├── env.go
│   │   ├── flags.go
│   │   ├── flags_test.go
│   │   ├── fzf.go
│   │   ├── help.go
│   │   ├── help_test.go
│   │   ├── isolated_shell_guard.go
│   │   ├── list.go
│   │   ├── main.go
│   │   ├── rename.go
│   │   ├── rename_test.go
│   │   ├── shell.go
│   │   ├── shell_test.go
│   │   ├── state.go
│   │   ├── state_test.go
│   │   ├── switch.go
│   │   ├── unset.go
│   │   └── version.go
│   └── kubens/
│       ├── current.go
│       ├── flags.go
│       ├── flags_test.go
│       ├── fzf.go
│       ├── help.go
│       ├── list.go
│       ├── main.go
│       ├── statefile.go
│       ├── statefile_test.go
│       ├── switch.go
│       ├── unset.go
│       └── version.go
├── completion/
│   ├── _kubectx.zsh
│   ├── _kubens.zsh
│   ├── kubectx.bash
│   ├── kubectx.fish
│   ├── kubens.bash
│   └── kubens.fish
├── go.mod
├── go.sum
├── internal/
│   ├── cmdutil/
│   │   ├── deprecated.go
│   │   ├── deprecated_test.go
│   │   ├── interactive.go
│   │   ├── util.go
│   │   └── util_test.go
│   ├── env/
│   │   └── constants.go
│   ├── kubeconfig/
│   │   ├── contextmodify.go
│   │   ├── contextmodify_test.go
│   │   ├── contexts.go
│   │   ├── contexts_test.go
│   │   ├── currentcontext.go
│   │   ├── currentcontext_test.go
│   │   ├── helper_test.go
│   │   ├── kubeconfig.go
│   │   ├── kubeconfig_test.go
│   │   ├── kubeconfigloader.go
│   │   ├── kubeconfigloader_test.go
│   │   ├── namespace.go
│   │   └── namespace_test.go
│   ├── printer/
│   │   ├── color.go
│   │   ├── color_test.go
│   │   └── printer.go
│   └── testutil/
│       └── kubeconfigbuilder.go
├── kubectx
├── kubens
└── test/
    ├── common.bash
    ├── kubectx.bats
    ├── kubens.bats
    ├── mock-kubectl
    └── testdata/
        ├── config1
        └── config2

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: github-actions
  directory: /
  schedule:
    interval: weekly
  commit-message:
    prefix: chore
    include: scope

- package-ecosystem: gomod
  directory: /
  schedule:
    interval: weekly
  commit-message:
    prefix: chore
    include: scope
  groups:
    kubernetes:
      patterns:
        - "k8s.io/*"


================================================
FILE: .github/workflows/bash-frozen.yml
================================================
name: Bash scripts frozen

on:
  pull_request:
    paths:
      - 'kubectx'
      - 'kubens'

jobs:
  comment:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Comment on PR if author is not ahmetb
        if: github.event.pull_request.user.login != 'ahmetb'
        uses: actions/github-script@v7
        with:
          script: |
            const body = [
              '> [!WARNING]',
              '> **This PR will not be merged.**',
              '>',
              '> The bash implementation of `kubectx` and `kubens` is **frozen** and is provided only for convenience.',
              '> We are not accepting any improvements to the bash scripts.',
              '>',
              '> Please propose your improvements to the **Go implementation** instead.',
            ].join('\n');

            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: body
            });


================================================
FILE: .github/workflows/ci.yml
================================================
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Go implementation (CI)
on:
  push:
  pull_request:
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@master
    - name: Setup Go
      uses: actions/setup-go@v6
      with:
        go-version: '1.25'
    - id: go-cache-paths
      run: |
        echo "::set-output name=go-build::$(go env GOCACHE)"
        echo "::set-output name=go-mod::$(go env GOMODCACHE)"
    - name: Go Build Cache
      uses: actions/cache@v5
      with:
        path: ${{ steps.go-cache-paths.outputs.go-build }}
        key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
    - name: Go Mod Cache
      uses: actions/cache@v5
      with:
        path: ${{ steps.go-cache-paths.outputs.go-mod }}
        key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
    - name: Ensure gofmt
      run: test -z "$(gofmt -s -d .)"
    - name: Ensure go.mod is already tidied
      run: go mod tidy && git diff --exit-code
    - name: Run unit tests
      run: go test ./...
    - name: Build with Goreleaser
      uses: goreleaser/goreleaser-action@v7
      with:
        version: latest
        args: release --snapshot --skip publish,snapcraft --clean
    - name: Setup BATS framework
      run: sudo npm install -g bats
    - name: kubectx (Go) integration tests
      run: COMMAND=./dist/kubectx_linux_amd64_v1/kubectx bats test/kubectx.bats
    - name: kubens (Go) integration tests
      run: COMMAND=./dist/kubens_linux_amd64_v1/kubens bats test/kubens.bats


================================================
FILE: .github/workflows/dependabot.yml
================================================
name: Dependabot

on:
  pull_request:

permissions:
  contents: write
  pull-requests: write

jobs:
  auto-merge:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:
    - name: Dependabot metadata
      id: metadata
      uses: dependabot/fetch-metadata@v2

    - name: Enable auto-merge for Dependabot PRs
      if: ${{ steps.metadata.outputs.update-type != 'version-update:semver-major' }}
      run: gh pr merge --auto --merge "$PR_URL"
      env:
        PR_URL: ${{ github.event.pull_request.html_url }}
        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/release.yml
================================================
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Release
on:
  push:
    tags:
    - 'v*.*.*'
jobs:
  goreleaser:
    permissions:
      contents: write
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v4
    - run: git fetch --tags
    - name: Setup Go
      uses: actions/setup-go@v6
      with:
        go-version: '1.25'
    - name: Install Snapcraft
      uses: samuelmeuli/action-snapcraft@v3
    - name: Setup Snapcraft
      run: |
        # https://github.com/goreleaser/goreleaser/issues/1715
        mkdir -p $HOME/.cache/snapcraft/download
        mkdir -p $HOME/.cache/snapcraft/stage-packages
    - name: GoReleaser
      uses: goreleaser/goreleaser-action@v7
      with:
        version: latest
        args: release --clean
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: Update new version for plugin 'ctx' in krew-index
      uses: rajatjindal/krew-release-bot@v0.0.51
      with:
        krew_template_file: .krew/ctx.yaml
    - name: Update new version for plugin 'ns' in krew-index
      uses: rajatjindal/krew-release-bot@v0.0.51
      with:
        krew_template_file: .krew/ns.yaml
    - name: Publish Snaps to the Snap Store (stable channel)
      run: for snap in $(ls dist/*.snap); do snapcraft upload --release=stable $snap; done
      env:
        SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}


================================================
FILE: .goreleaser.yml
================================================
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json

# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at https://goreleaser.com

version: 2
before:
  hooks:
    - go mod download
builds:
- id: kubectx
  main: ./cmd/kubectx
  binary: kubectx
  env:
  - CGO_ENABLED=0
  goos:
    - linux
    - darwin
    - windows
  goarch:
    - amd64
    - arm
    - arm64
    - ppc64le
    - s390x
  goarm: [6, 7]
- id: kubens
  main: ./cmd/kubens
  binary: kubens
  env:
  - CGO_ENABLED=0
  goos:
    - linux
    - darwin
    - windows
  goarch:
    - amd64
    - arm
    - arm64
    - ppc64le
    - s390x
  goarm: [6, 7]
archives:
- id: kubectx-archive
  name_template: |-
    kubectx_{{ .Tag }}_{{ .Os }}_
    {{- with .Arch -}}
      {{- if (eq . "386") -}}i386
      {{- else if (eq . "amd64") -}}x86_64
      {{- else -}}{{- . -}}
      {{- end -}} 
    {{ end }}
    {{- with .Arm -}}
      {{- if (eq . "6") -}}hf
      {{- else -}}v{{- . -}}
      {{- end -}}
    {{- end -}}
  ids:
    - kubectx
  format_overrides:
    - goos: windows
      formats: [zip]
  files: ["LICENSE"]
- id: kubens-archive
  name_template: |-
    kubens_{{ .Tag }}_{{ .Os }}_
    {{- with .Arch -}}
      {{- if (eq . "386") -}}i386
      {{- else if (eq . "amd64") -}}x86_64
      {{- else -}}{{- . -}}
      {{- end -}}
    {{ end }}
    {{- with .Arm -}}
      {{- if (eq . "6") -}}hf
      {{- else -}}v{{- . -}}
      {{- end -}}
    {{- end -}}
  ids:
    - kubens
  format_overrides:
    - goos: windows
      formats: [zip]
  files: ["LICENSE"]
checksum:
  name_template: "checksums.txt"
  algorithm: sha256
release:
  extra_files:
    - glob: ./kubens
    - glob: ./kubectx
snapcrafts:
  - id: kubectx
    name: kubectx
    summary: 'kubectx + kubens: Power tools for kubectl'
    description: |
      kubectx is a tool to switch between contexts (clusters) on kubectl faster.
      kubens is a tool to switch between Kubernetes namespaces (and configure them for kubectl) easily.
    grade: stable
    confinement: classic
    base: core24
    apps:
      kubectx:
        command: kubectx
        completer: completion/kubectx.bash
      kubens:
        command: kubens
        completer: completion/kubens.bash


================================================
FILE: .krew/ctx.yaml
================================================
apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind: Plugin
metadata:
  name: ctx
spec:
  homepage: https://github.com/ahmetb/kubectx
  shortDescription: Switch between contexts in your kubeconfig
  version: {{ .TagName }}
  description: |
    Also known as "kubectx", a utility to switch between context entries in
    your kubeconfig file efficiently.
  caveats: |
    If fzf is installed on your machine, you can interactively choose
    between the entries using the arrow keys, or by fuzzy searching
    as you type.
    See https://github.com/ahmetb/kubectx for customization and details.
  platforms:
  - selector:
      matchExpressions:
      - key: os
        operator: In
        values:
        - darwin
        - linux
    {{addURIAndSha "https://github.com/ahmetb/kubectx/archive/{{ .TagName }}.tar.gz" .TagName }}
    bin: kubectx
    files:
    - from: kubectx-*/kubectx
      to: .
    - from: kubectx-*/LICENSE
      to: .


================================================
FILE: .krew/ns.yaml
================================================
apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind: Plugin
metadata:
  name: ns
spec:
  homepage: https://github.com/ahmetb/kubectx
  shortDescription: Switch between Kubernetes namespaces
  version: {{ .TagName }}
  description: |
    Also known as "kubens", a utility to set your current namespace and switch
    between them.
  caveats: |
    If fzf is installed on your machine, you can interactively choose
    between the entries using the arrow keys, or by fuzzy searching
    as you type.
  platforms:
  - selector:
      matchExpressions:
      - key: os
        operator: In
        values:
        - darwin
        - linux
    {{addURIAndSha "https://github.com/ahmetb/kubectx/archive/{{ .TagName }}.tar.gz" .TagName }}
    bin: kubens
    files:
    - from: kubectx-*/kubens
      to: .
    - from: kubectx-*/LICENSE
      to: .


================================================
FILE: CONTRIBUTING.md
================================================
# How to contribute

We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.

## Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

## Code reviews

All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# `kubectx` + `kubens`: Power tools for kubectl

![Latest GitHub release](https://img.shields.io/github/release/ahmetb/kubectx.svg)
![GitHub stars](https://img.shields.io/github/stars/ahmetb/kubectx.svg?label=github%20stars)
![Homebrew downloads](https://img.shields.io/homebrew/installs/dy/kubectx?label=macOS%20installs)
[![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)")
![Proudly written in Bash](https://img.shields.io/badge/written%20in-bash-ff69b4.svg)

This repository provides both `kubectx` and `kubens` tools.
[Install &rarr;](#installation)

## What are `kubectx` and `kubens`?

**kubectx** is a tool to switch between contexts (clusters) on kubectl
faster.<br/>
**kubens** is a tool to switch between Kubernetes namespaces (and
configure them for kubectl) easily.

Here's a **`kubectx`** demo:
![kubectx demo GIF](img/kubectx-demo.gif)

...and here's a **`kubens`** demo:
![kubens demo GIF](img/kubens-demo.gif)

### Examples

```sh
# switch to another cluster that's in kubeconfig
$ kubectx minikube
Switched to context "minikube".

# switch back to previous cluster
$ kubectx -
Switched to context "oregon".

# start an "isolated shell" that only has a single context
$ kubectx -s minikube

# rename context
$ kubectx dublin=gke_ahmetb_europe-west1-b_dublin
Context "gke_ahmetb_europe-west1-b_dublin" renamed to "dublin".

# change the active namespace on kubectl
$ kubens kube-system
Context "test" set.
Active namespace is "kube-system".

# go back to the previous namespace
$ kubens -
Context "test" set.
Active namespace is "default".

# change the active namespace even if it doesn't exist
$ kubens not-found-namespace --force
Context "test" set.
Active namespace is "not-found-namespace".
---
$ kubens not-found-namespace -f
Context "test" set.
Active namespace is "not-found-namespace".
```

If you have [`fzf`](https://github.com/junegunn/fzf) installed, you can also
**interactively** select a context or cluster, or fuzzy-search by typing a few
characters. To learn more, read [interactive mode &rarr;](#interactive-mode)

Both `kubectx` and `kubens` support <kbd>Tab</kbd> completion on bash/zsh/fish
shells to help with long context names. You don't have to remember full context
names anymore.

-----

## Installation

| Package manager | Command |
|---|---|
| [Homebrew](https://brew.sh/) (macOS & Linux) | `brew install kubectx` |
| [MacPorts](https://www.macports.org) (macOS) | `sudo port install kubectx` |
| apt (Debian/Ubuntu) | `sudo apt install kubectx` |
| pacman (Arch Linux) | `sudo pacman -S kubectx` |
| [Chocolatey](https://chocolatey.org/) (Windows) | `choco install kubens kubectx` |
| [Scoop](https://scoop.sh/) (Windows) | `scoop bucket add main && scoop install main/kubens main/kubectx` |
| [winget](https://learn.microsoft.com/en-us/windows/package-manager/) (Windows) | `winget install --id ahmetb.kubectx && winget install --id ahmetb.kubens` |
| [Krew](https://github.com/kubernetes-sigs/krew/) (kubectl plugin) | `kubectl krew install ctx && kubectl krew install ns` |

Alternatively, download binaries from the [**Releases page &rarr;**](https://github.com/ahmetb/kubectx/releases) and add them to somewhere in your `PATH`.

<details>
<summary>Shell completion scripts</summary>

#### zsh (with [antibody](https://getantibody.github.io))

Add this line to your [Plugins File](https://getantibody.github.io/usage/) (e.g.
`~/.zsh_plugins.txt`):

```
ahmetb/kubectx path:completion kind:fpath
```

Depending on your setup, you might or might not need to call `compinit` or
`autoload -U compinit && compinit` in your `~/.zshrc` after you load the Plugins
file. If you use [oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh), load the
completions before you load `oh-my-zsh` because `oh-my-zsh` will call
`compinit`.

#### zsh (plain)

The completion scripts have to be in a path that belongs to `$fpath`. Either
link or copy them to an existing folder.

Example with [`oh-my-zsh`](https://github.com/ohmyzsh/ohmyzsh):

```bash
mkdir -p ~/.oh-my-zsh/custom/completions
chmod -R 755 ~/.oh-my-zsh/custom/completions
ln -s /opt/kubectx/completion/_kubectx.zsh ~/.oh-my-zsh/custom/completions/_kubectx.zsh
ln -s /opt/kubectx/completion/_kubens.zsh ~/.oh-my-zsh/custom/completions/_kubens.zsh
echo "fpath=($ZSH/custom/completions $fpath)" >> ~/.zshrc
```

If completion doesn't work, add `autoload -U compinit && compinit` to your
`.zshrc` (similar to
[`zsh-completions`](https://github.com/zsh-users/zsh-completions/blob/master/README.md#oh-my-zsh)).

If you are not using [`oh-my-zsh`](https://github.com/ohmyzsh/ohmyzsh), you
could link to `/usr/share/zsh/functions/Completion` (might require sudo),
depending on the `$fpath` of your zsh installation.

In case of errors, calling `compaudit` might help.

#### bash

```bash
git clone https://github.com/ahmetb/kubectx.git ~/.kubectx
COMPDIR=$(pkg-config --variable=completionsdir bash-completion)
ln -sf ~/.kubectx/completion/kubens.bash $COMPDIR/kubens
ln -sf ~/.kubectx/completion/kubectx.bash $COMPDIR/kubectx
cat << EOF >> ~/.bashrc


#kubectx and kubens
export PATH=~/.kubectx:\$PATH
EOF
```

#### fish

```fish
mkdir -p ~/.config/fish/completions
ln -s /opt/kubectx/completion/kubectx.fish ~/.config/fish/completions/
ln -s /opt/kubectx/completion/kubens.fish ~/.config/fish/completions/
```

</details>

> [!NOTE]
> Tip: Show context/namespace in your shell prompt with [oh-my-posh](https://ohmyposh.dev/) or
> simply with [kube-ps1](https://github.com/jonmosco/kube-ps1).

-----

### Interactive mode

If you want `kubectx` and `kubens` commands to present you an interactive menu
with fuzzy searching, you just need to [install
`fzf`](https://github.com/junegunn/fzf) in your `$PATH`.

![kubectx interactive search with fzf](img/kubectx-interactive.gif)

Caveats:
- If you have `fzf` installed, but want to opt out of using this feature, set the
  environment variable `KUBECTX_IGNORE_FZF=1`.
- If you want to keep `fzf` interactive mode but need the default behavior of the
  command, you can do it by piping the output to another command (e.g. `kubectx |
  cat `).

-----

### Customizing colors

If you like to customize the colors indicating the current namespace or context,
set the environment variables `KUBECTX_CURRENT_FGCOLOR` and
`KUBECTX_CURRENT_BGCOLOR` (refer color codes
[here](https://linux.101hacks.com/ps1-examples/prompt-color-using-tput/)):

```sh
export KUBECTX_CURRENT_FGCOLOR=$(tput setaf 6) # blue text
export KUBECTX_CURRENT_BGCOLOR=$(tput setab 7) # white background
```

Colors in the output can be disabled by setting the
[`NO_COLOR`](https://no-color.org/) environment variable.

-----

If you liked `kubectx`, you may like my
[`kubectl-aliases`](https://github.com/ahmetb/kubectl-aliases) project, too. I
recommend pairing kubectx and kubens with [fzf](#interactive-mode) and
[kube-ps1](https://github.com/jonmosco/kube-ps1).

#### Stargazers over time

[![Stargazers over time](https://starchart.cc/ahmetb/kubectx.svg)](https://starchart.cc/ahmetb/kubectx)
![Google Analytics](https://ga-beacon.appspot.com/UA-2609286-17/kubectx/README?pixel) <!-- TODO broken since Aug 2021 as igrigorik left Google -->


================================================
FILE: cmd/kubectx/current.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"fmt"
	"io"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
)

// CurrentOp prints the current context
type CurrentOp struct{}

func (_op CurrentOp) Run(stdout, _ io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	v, err := kc.GetCurrentContext()
	if err != nil {
		return fmt.Errorf("failed to get current context: %w", err)
	}
	if v == "" {
		return errors.New("current-context is not set")
	}
	if _, err := fmt.Fprintln(stdout, v); err != nil {
		return fmt.Errorf("write error: %w", err)
	}
	return nil
}


================================================
FILE: cmd/kubectx/delete.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"fmt"
	"io"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

// DeleteOp indicates intention to delete contexts.
type DeleteOp struct {
	Contexts []string // NAME or '.' to indicate current-context.
}

// deleteContexts deletes context entries one by one.
func (op DeleteOp) Run(_, stderr io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}
	for _, ctx := range op.Contexts {
		// TODO inefficiency here. we open/write/close the same file many times.
		deletedName, wasActiveContext, err := deleteContext(ctx)
		if err != nil {
			return fmt.Errorf("error deleting context \"%s\": %w", deletedName, err)
		}
		if wasActiveContext {
			printer.Warning(stderr, "You deleted the current context. Use \"%s\" to select a new context.",
				selfName())
		}

		_ = printer.Success(stderr, `Deleted context %s.`, printer.SuccessColor.Sprint(deletedName))
	}
	return nil
}

// deleteContext deletes a context entry by NAME or current-context
// indicated by ".".
func deleteContext(name string) (deleteName string, wasActiveContext bool, err error) {
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return deleteName, false, fmt.Errorf("kubeconfig error: %w", err)
	}

	cur, err := kc.GetCurrentContext()
	if err != nil {
		return deleteName, false, fmt.Errorf("failed to get current context: %w", err)
	}
	// resolve "." to a real name
	if name == "." {
		if cur == "" {
			return deleteName, false, errors.New("can't use '.' as the no active context is set")
		}
		wasActiveContext = true
		name = cur
	}

	exists, err := kc.ContextExists(name)
	if err != nil {
		return name, false, fmt.Errorf("failed to check context: %w", err)
	}
	if !exists {
		return name, false, errors.New("context does not exist")
	}

	if err := kc.DeleteContextEntry(name); err != nil {
		return name, false, fmt.Errorf("failed to modify yaml doc: %w", err)
	}
	if err := kc.Save(); err != nil {
		return name, wasActiveContext, fmt.Errorf("failed to save modified kubeconfig file: %w", err)
	}
	return name, wasActiveContext, nil
}


================================================
FILE: cmd/kubectx/env.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main


================================================
FILE: cmd/kubectx/flags.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/ahmetb/kubectx/internal/cmdutil"
)

// UnsupportedOp indicates an unsupported flag.
type UnsupportedOp struct{ Err error }

func (op UnsupportedOp) Run(_, _ io.Writer) error {
	return op.Err
}

// parseArgs looks at flags (excl. executable name, i.e. argv[0])
// and decides which operation should be taken.
func parseArgs(argv []string) Op {
	if len(argv) == 0 {
		if cmdutil.IsInteractiveMode(os.Stdout) {
			return InteractiveSwitchOp{SelfCmd: os.Args[0]}
		}
		return ListOp{}
	}

	if argv[0] == "--shell" || argv[0] == "-s" {
		if len(argv) != 2 {
			return UnsupportedOp{Err: fmt.Errorf("'%s' requires exactly one context name argument", argv[0])}
		}
		return ShellOp{Target: argv[1]}
	}

	if argv[0] == "-d" {
		if len(argv) == 1 {
			if cmdutil.IsInteractiveMode(os.Stdout) {
				return InteractiveDeleteOp{SelfCmd: os.Args[0]}
			} else {
				return UnsupportedOp{Err: fmt.Errorf("'-d' needs arguments")}
			}
		}
		return DeleteOp{Contexts: argv[1:]}
	}

	if len(argv) == 1 {
		v := argv[0]
		if v == "--help" || v == "-h" {
			return HelpOp{}
		}
		if v == "--version" || v == "-V" {
			return VersionOp{}
		}
		if v == "--current" || v == "-c" {
			return CurrentOp{}
		}
		if v == "--unset" || v == "-u" {
			return UnsetOp{}
		}

		if new, old, ok := parseRenameSyntax(v); ok {
			return RenameOp{New: new, Old: old}
		}

		if strings.HasPrefix(v, "-") && v != "-" {
			return UnsupportedOp{Err: fmt.Errorf("unsupported option '%s'", v)}
		}
		return SwitchOp{Target: argv[0]}
	}
	return UnsupportedOp{Err: fmt.Errorf("too many arguments")}
}


================================================
FILE: cmd/kubectx/flags_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func Test_parseArgs_new(t *testing.T) {
	tests := []struct {
		name string
		args []string
		want Op
	}{
		{name: "nil Args",
			args: nil,
			want: ListOp{}},
		{name: "empty Args",
			args: []string{},
			want: ListOp{}},
		{name: "help shorthand",
			args: []string{"-h"},
			want: HelpOp{}},
		{name: "help long form",
			args: []string{"--help"},
			want: HelpOp{}},
		{name: "current shorthand",
			args: []string{"-c"},
			want: CurrentOp{}},
		{name: "current long form",
			args: []string{"--current"},
			want: CurrentOp{}},
		{name: "unset shorthand",
			args: []string{"-u"},
			want: UnsetOp{}},
		{name: "unset long form",
			args: []string{"--unset"},
			want: UnsetOp{}},
		{name: "switch by name",
			args: []string{"foo"},
			want: SwitchOp{Target: "foo"}},
		{name: "switch by swap",
			args: []string{"-"},
			want: SwitchOp{Target: "-"}},
		{name: "delete - without contexts",
			args: []string{"-d"},
			want: UnsupportedOp{fmt.Errorf("'-d' needs arguments")}},
		{name: "delete - current context",
			args: []string{"-d", "."},
			want: DeleteOp{[]string{"."}}},
		{name: "delete - multiple contexts",
			args: []string{"-d", ".", "a", "b"},
			want: DeleteOp{[]string{".", "a", "b"}}},
		{name: "rename context",
			args: []string{"a=b"},
			want: RenameOp{"a", "b"}},
		{name: "rename context with old=current",
			args: []string{"a=."},
			want: RenameOp{"a", "."}},
		{name: "shell shorthand",
			args: []string{"-s", "prod"},
			want: ShellOp{Target: "prod"}},
		{name: "shell long form",
			args: []string{"--shell", "prod"},
			want: ShellOp{Target: "prod"}},
		{name: "shell without context name",
			args: []string{"-s"},
			want: UnsupportedOp{Err: fmt.Errorf("'-s' requires exactly one context name argument")}},
		{name: "shell with too many args",
			args: []string{"--shell", "a", "b"},
			want: UnsupportedOp{Err: fmt.Errorf("'--shell' requires exactly one context name argument")}},
		{name: "unrecognized flag",
			args: []string{"-x"},
			want: UnsupportedOp{Err: fmt.Errorf("unsupported option '-x'")}},
		{name: "too many args",
			args: []string{"a", "b", "c"},
			want: UnsupportedOp{Err: fmt.Errorf("too many arguments")}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := parseArgs(tt.args)

			var opts cmp.Options
			if _, ok := tt.want.(UnsupportedOp); ok {
				opts = append(opts, cmp.Comparer(func(x, y UnsupportedOp) bool {
					return (x.Err == nil && y.Err == nil) || (x.Err.Error() == y.Err.Error())
				}))
			}

			if diff := cmp.Diff(got, tt.want, opts...); diff != "" {
				t.Errorf("parseArgs(%#v) diff: %s", tt.args, diff)
			}
		})
	}
}


================================================
FILE: cmd/kubectx/fzf.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/ahmetb/kubectx/internal/cmdutil"
	"github.com/ahmetb/kubectx/internal/env"
	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

type InteractiveSwitchOp struct {
	SelfCmd string
}

type InteractiveDeleteOp struct {
	SelfCmd string
}

func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}
	// parse kubeconfig just to see if it can be loaded
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		if cmdutil.IsNotFoundErr(err) {
			printer.Warning(stderr, "kubeconfig file not found")
			return nil
		}
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	ctxNames, err := kc.ContextNames()
	if err != nil {
		return fmt.Errorf("failed to get context names: %w", err)
	}
	if len(ctxNames) == 0 {
		return errors.New("no contexts found in the kubeconfig file")
	}

	cmd := exec.Command("fzf", "--ansi", "--no-preview")
	var out bytes.Buffer
	cmd.Stdin = os.Stdin
	cmd.Stderr = stderr
	cmd.Stdout = &out

	cmd.Env = append(os.Environ(),
		fmt.Sprintf("FZF_DEFAULT_COMMAND=%s", op.SelfCmd),
		fmt.Sprintf("%s=1", env.EnvForceColor))
	if err := cmd.Run(); err != nil {
		var exitErr *exec.ExitError
		if !errors.As(err, &exitErr) {
			return err
		}
	}
	choice := strings.TrimSpace(out.String())
	if choice == "" {
		return errors.New("you did not choose any of the options")
	}
	name, err := switchContext(choice)
	if err != nil {
		return fmt.Errorf("failed to switch context: %w", err)
	}
	_ = printer.Success(stderr, "Switched to context \"%s\".", printer.SuccessColor.Sprint(name))
	return nil
}

func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}
	// parse kubeconfig just to see if it can be loaded
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		if cmdutil.IsNotFoundErr(err) {
			printer.Warning(stderr, "kubeconfig file not found")
			return nil
		}
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	ctxNames, err := kc.ContextNames()
	if err != nil {
		return fmt.Errorf("failed to get context names: %w", err)
	}
	if len(ctxNames) == 0 {
		return errors.New("no contexts found in config")
	}

	cmd := exec.Command("fzf", "--ansi", "--no-preview")
	var out bytes.Buffer
	cmd.Stdin = os.Stdin
	cmd.Stderr = stderr
	cmd.Stdout = &out

	cmd.Env = append(os.Environ(),
		fmt.Sprintf("FZF_DEFAULT_COMMAND=%s", op.SelfCmd),
		fmt.Sprintf("%s=1", env.EnvForceColor))
	if err := cmd.Run(); err != nil {
		var exitErr *exec.ExitError
		if !errors.As(err, &exitErr) {
			return err
		}
	}

	choice := strings.TrimSpace(out.String())
	if choice == "" {
		return errors.New("you did not choose any of the options")
	}

	name, wasActiveContext, err := deleteContext(choice)
	if err != nil {
		return fmt.Errorf("failed to delete context: %w", err)
	}

	if wasActiveContext {
		printer.Warning(stderr, "You deleted the current context. Use \"%s\" to select a new context.",
			selfName())
	}

	_ = printer.Success(stderr, `Deleted context %s.`, printer.SuccessColor.Sprint(name))

	return nil
}


================================================
FILE: cmd/kubectx/help.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

// HelpOp describes printing help.
type HelpOp struct{}

func (_ HelpOp) Run(stdout, _ io.Writer) error {
	return printUsage(stdout)
}

func printUsage(out io.Writer) error {
	help := `USAGE:
  %PROG%                       : list the contexts
  %PROG% <NAME>                : switch to context <NAME>
  %PROG% -                     : switch to the previous context
  %PROG% -c, --current         : show the current context name
  %PROG% <NEW_NAME>=<NAME>     : rename context <NAME> to <NEW_NAME>
  %PROG% <NEW_NAME>=.          : rename current-context to <NEW_NAME>
  %PROG% -u, --unset           : unset the current context
  %PROG% -d <NAME> [<NAME...>] : delete context <NAME> ('.' for current-context)
  %SPAC%                         (this command won't delete the user/cluster entry
  %SPAC%                          referenced by the context entry)
  %PROG% -s, --shell <NAME>    : start a shell scoped to context <NAME>
  %PROG% -h,--help             : show this message
  %PROG% -V,--version          : show version`
	help = strings.ReplaceAll(help, "%PROG%", selfName())
	help = strings.ReplaceAll(help, "%SPAC%", strings.Repeat(" ", len(selfName())))

	_, err := fmt.Fprintf(out, "%s\n", help)
	if err != nil {
		return fmt.Errorf("write error: %w", err)
	}
	return nil
}

// selfName guesses how the user invoked the program.
func selfName() string {
	me := filepath.Base(os.Args[0])
	pluginPrefix := "kubectl-"
	if strings.HasPrefix(me, pluginPrefix) {
		return "kubectl " + strings.TrimPrefix(me, pluginPrefix)
	}
	return "kubectx"
}


================================================
FILE: cmd/kubectx/help_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"strings"
	"testing"
)

func TestPrintHelp(t *testing.T) {
	var buf bytes.Buffer
	if err := (&HelpOp{}).Run(&buf, &buf); err != nil {
		t.Fatal(err)
	}

	out := buf.String()
	if !strings.Contains(out, "USAGE:") {
		t.Errorf("help string doesn't contain USAGE: ; output=\"%s\"", out)
	}

	if !strings.HasSuffix(out, "\n") {
		t.Errorf("does not end with New line; output=\"%s\"", out)
	}
}


================================================
FILE: cmd/kubectx/isolated_shell_guard.go
================================================
package main

import (
	"fmt"
	"os"

	"github.com/ahmetb/kubectx/internal/env"
	"github.com/ahmetb/kubectx/internal/kubeconfig"
)

func checkIsolatedMode() error {
	if os.Getenv(env.EnvIsolatedShell) != "1" {
		return nil
	}

	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("you are in a locked single-context shell, use 'exit' to leave")
	}

	cur, _ := kc.GetCurrentContext()
	return fmt.Errorf("you are in a locked single-context shell (\"%s\"), use 'exit' to leave", cur)
}


================================================
FILE: cmd/kubectx/list.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"

	"facette.io/natsort"

	"github.com/ahmetb/kubectx/internal/cmdutil"
	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

// ListOp describes listing contexts.
type ListOp struct{}

func (_ ListOp) Run(stdout, stderr io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		if cmdutil.IsNotFoundErr(err) {
			printer.Warning(stderr, "kubeconfig file not found")
			return nil
		}
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	ctxs, err := kc.ContextNames()
	if err != nil {
		return fmt.Errorf("failed to get context names: %w", err)
	}
	natsort.Sort(ctxs)

	cur, err := kc.GetCurrentContext()
	if err != nil {
		return fmt.Errorf("failed to get current context: %w", err)
	}
	for _, c := range ctxs {
		s := c
		if c == cur {
			s = printer.ActiveItemColor.Sprint(c)
		}
		fmt.Fprintf(stdout, "%s\n", s)
	}
	return nil
}


================================================
FILE: cmd/kubectx/main.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"os"

	"github.com/ahmetb/kubectx/internal/cmdutil"
	"github.com/ahmetb/kubectx/internal/env"
	"github.com/ahmetb/kubectx/internal/printer"
	"github.com/fatih/color"
)

type Op interface {
	Run(stdout, stderr io.Writer) error
}

func main() {
	cmdutil.PrintDeprecatedEnvWarnings(color.Error, os.Environ())

	op := parseArgs(os.Args[1:])
	if err := op.Run(color.Output, color.Error); err != nil {
		printer.Error(color.Error, "%s", err)

		if _, ok := os.LookupEnv(env.EnvDebug); ok {
			// print stack trace in verbose mode
			fmt.Fprintf(color.Error, "[DEBUG] error: %+v\n", err)
		}
		defer os.Exit(1)
	}
}


================================================
FILE: cmd/kubectx/rename.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"strings"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

// RenameOp indicates intention to rename contexts.
type RenameOp struct {
	New string // NAME of New context
	Old string // NAME of Old context (or '.' for current-context)
}

// parseRenameSyntax parses A=B form into [A,B] and returns
// whether it is parsed correctly.
func parseRenameSyntax(v string) (string, string, bool) {
	new, old, ok := strings.Cut(v, "=")
	if !ok || new == "" || old == "" {
		return "", "", false
	}
	return new, old, true
}

// rename changes the old (NAME or '.' for current-context)
// to the "new" value. If the old refers to the current-context,
// current-context preference is also updated.
func (op RenameOp) Run(_, stderr io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	cur, err := kc.GetCurrentContext()
	if err != nil {
		return fmt.Errorf("failed to get current context: %w", err)
	}
	if op.Old == "." {
		op.Old = cur
	}

	oldExists, err := kc.ContextExists(op.Old)
	if err != nil {
		return fmt.Errorf("failed to check context: %w", err)
	}
	if !oldExists {
		return fmt.Errorf("context \"%s\" not found, can't rename it", op.Old)
	}

	newExists, err := kc.ContextExists(op.New)
	if err != nil {
		return fmt.Errorf("failed to check context: %w", err)
	}
	if newExists {
		printer.Warning(stderr, "context \"%s\" exists, overwriting it.", op.New)
		if err := kc.DeleteContextEntry(op.New); err != nil {
			return fmt.Errorf("failed to delete new context to overwrite it: %w", err)
		}
	}

	if err := kc.ModifyContextName(op.Old, op.New); err != nil {
		return fmt.Errorf("failed to change context name: %w", err)
	}
	if op.Old == cur {
		if err := kc.ModifyCurrentContext(op.New); err != nil {
			return fmt.Errorf("failed to set current-context to new name: %w", err)
		}
	}
	if err := kc.Save(); err != nil {
		return fmt.Errorf("failed to save modified kubeconfig: %w", err)
	}
	_ = printer.Success(stderr, "Context %s renamed to %s.",
		printer.SuccessColor.Sprint(op.Old),
		printer.SuccessColor.Sprint(op.New))
	return nil
}


================================================
FILE: cmd/kubectx/rename_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"testing"

	"github.com/google/go-cmp/cmp"
)

func Test_parseRenameSyntax(t *testing.T) {

	type out struct {
		New string
		Old string
		OK  bool
	}
	tests := []struct {
		name string
		in   string
		want out
	}{
		{
			name: "no equals sign",
			in:   "foo",
			want: out{OK: false},
		},
		{
			name: "no left side",
			in:   "=a",
			want: out{OK: false},
		},
		{
			name: "no right side",
			in:   "a=",
			want: out{OK: false},
		},
		{
			name: "correct format",
			in:   "a=b",
			want: out{
				New: "a",
				Old: "b",
				OK:  true,
			},
		},
		{
			name: "correct format with current context",
			in:   "NEW_NAME=.",
			want: out{
				New: "NEW_NAME",
				Old: ".",
				OK:  true,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			new, old, ok := parseRenameSyntax(tt.in)
			got := out{
				New: new,
				Old: old,
				OK:  ok,
			}
			diff := cmp.Diff(tt.want, got)
			if diff != "" {
				t.Errorf("parseRenameSyntax() diff=%s", diff)
			}
		})
	}
}


================================================
FILE: cmd/kubectx/shell.go
================================================
package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"runtime"

	"github.com/fatih/color"

	"github.com/ahmetb/kubectx/internal/env"
	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

// ShellOp indicates intention to start a scoped sub-shell for a context.
type ShellOp struct {
	Target string
}

func (op ShellOp) Run(_, stderr io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}

	kubectlPath, err := resolveKubectl()
	if err != nil {
		return err
	}

	// Verify context exists and get current context for exit message
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}
	exists, err := kc.ContextExists(op.Target)
	if err != nil {
		return fmt.Errorf("failed to check context: %w", err)
	}
	if !exists {
		return fmt.Errorf("no context exists with the name: \"%s\"", op.Target)
	}
	previousCtx, err := kc.GetCurrentContext()
	if err != nil {
		return fmt.Errorf("failed to get current context: %w", err)
	}

	// Extract minimal kubeconfig using kubectl
	data, err := extractMinimalKubeconfig(kubectlPath, op.Target)
	if err != nil {
		return fmt.Errorf("failed to extract kubeconfig for context: %w", err)
	}

	// Write to temp file
	tmpFile, err := os.CreateTemp("", "kubectx-shell-*.yaml")
	if err != nil {
		return fmt.Errorf("failed to create temp kubeconfig file: %w", err)
	}
	tmpPath := tmpFile.Name()
	defer os.Remove(tmpPath)

	if _, err := tmpFile.Write(data); err != nil {
		tmpFile.Close()
		return fmt.Errorf("failed to write temp kubeconfig: %w", err)
	}
	tmpFile.Close()

	// Print entry message
	badgeColor := color.New(color.BgRed, color.FgWhite, color.Bold)
	printer.EnableOrDisableColor(badgeColor)
	fmt.Fprintf(stderr, "%s kubectl context is %s in this shell — type 'exit' to leave.\n",
		badgeColor.Sprint("[ISOLATED SHELL]"), printer.WarningColor.Sprint(op.Target))

	// Detect and start shell
	shellBin := detectShell()
	cmd := exec.Command(shellBin)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = append(os.Environ(),
		"KUBECONFIG="+tmpPath,
		env.EnvIsolatedShell+"=1",
	)

	_ = cmd.Run()

	// Print exit message
	fmt.Fprintf(stderr, "%s kubectl context is now %s.\n",
		badgeColor.Sprint("[ISOLATED SHELL EXITED]"), printer.WarningColor.Sprint(previousCtx))

	return nil
}

func resolveKubectl() (string, error) {
	if v := os.Getenv("KUBECTL"); v != "" {
		return v, nil
	}
	path, err := exec.LookPath("kubectl")
	if err != nil {
		return "", fmt.Errorf("kubectl is required for --shell but was not found in PATH")
	}
	return path, nil
}

func extractMinimalKubeconfig(kubectlPath, contextName string) ([]byte, error) {
	cmd := exec.Command(kubectlPath, "config", "view", "--minify", "--flatten",
		"--context", contextName)
	cmd.Env = os.Environ()
	data, err := cmd.Output()
	if err != nil {
		return nil, fmt.Errorf("kubectl config view failed: %w", err)
	}
	return data, nil
}

func detectShell() string {
	if runtime.GOOS == "windows" {
		// cmd.exe always sets the PROMPT env var, so if it is present
		// we can reliably assume we are running inside cmd.exe.
		if os.Getenv("PROMPT") != "" {
			return "cmd.exe"
		}
		// Otherwise assume PowerShell. PSModulePath is always set on
		// Windows regardless of the shell, so it cannot be used as a
		// discriminator; however the absence of PROMPT is a strong
		// enough signal that we are in a PowerShell session.
		if pwsh, err := exec.LookPath("pwsh"); err == nil {
			return pwsh
		}
		if powershell, err := exec.LookPath("powershell"); err == nil {
			return powershell
		}
		return "cmd.exe"
	}
	if v := os.Getenv("SHELL"); v != "" {
		return v
	}
	return "/bin/sh"
}


================================================
FILE: cmd/kubectx/shell_test.go
================================================
package main

import (
	"bytes"
	"runtime"
	"testing"

	"github.com/ahmetb/kubectx/internal/env"
)

func Test_detectShell_unix(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("skipping unix shell detection test on windows")
	}

	tests := []struct {
		name     string
		shellEnv string
		want     string
	}{
		{
			name:     "SHELL env set",
			shellEnv: "/bin/zsh",
			want:     "/bin/zsh",
		},
		{
			name:     "SHELL env empty, falls back to /bin/sh",
			shellEnv: "",
			want:     "/bin/sh",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Setenv("SHELL", tt.shellEnv)

			got := detectShell()
			if got != tt.want {
				t.Errorf("detectShell() = %q, want %q", got, tt.want)
			}
		})
	}
}

func Test_ShellOp_blockedWhenNested(t *testing.T) {
	// Simulate being inside an isolated shell
	t.Setenv(env.EnvIsolatedShell, "1")

	op := ShellOp{Target: "some-context"}
	var stdout, stderr bytes.Buffer
	err := op.Run(&stdout, &stderr)

	if err == nil {
		t.Fatal("expected error when running ShellOp inside isolated shell, got nil")
	}

	want := "locked single-context shell to"
	if !bytes.Contains([]byte(err.Error()), []byte(want)) {
		// The error may not contain the context name if kubeconfig is not available,
		// but it should still be blocked
		want2 := "locked single-context shell"
		if !bytes.Contains([]byte(err.Error()), []byte(want2)) {
			t.Errorf("error message %q does not contain %q", err.Error(), want2)
		}
	}
}

func Test_resolveKubectl_envVar(t *testing.T) {
	t.Setenv("KUBECTL", "/custom/path/kubectl")
	got, err := resolveKubectl()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got != "/custom/path/kubectl" {
		t.Errorf("resolveKubectl() = %q, want %q", got, "/custom/path/kubectl")
	}
}

func Test_resolveKubectl_inPath(t *testing.T) {
	t.Setenv("KUBECTL", "")

	// kubectl should be findable in PATH on most dev machines
	got, err := resolveKubectl()
	if err != nil {
		t.Skip("kubectl not in PATH, skipping")
	}
	if got == "" {
		t.Error("resolveKubectl() returned empty string")
	}
}

func Test_checkIsolatedMode_notSet(t *testing.T) {
	t.Setenv(env.EnvIsolatedShell, "")

	err := checkIsolatedMode()
	if err != nil {
		t.Errorf("expected nil error when not in isolated mode, got: %v", err)
	}
}

func Test_checkIsolatedMode_set(t *testing.T) {
	t.Setenv(env.EnvIsolatedShell, "1")

	err := checkIsolatedMode()
	if err == nil {
		t.Fatal("expected error when in isolated mode, got nil")
	}

	want := "locked single-context shell"
	if !bytes.Contains([]byte(err.Error()), []byte(want)) {
		t.Errorf("error message %q does not contain %q", err.Error(), want)
	}
}


================================================
FILE: cmd/kubectx/state.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/ahmetb/kubectx/internal/cmdutil"
)

func kubectxPrevCtxFile() (string, error) {
	dir := cmdutil.CacheDir()
	if dir == "" {
		return "", errors.New("HOME or USERPROFILE environment variable not set")
	}
	return filepath.Join(dir, "kubectx"), nil
}

// readLastContext returns the saved previous context
// if the state file exists, otherwise returns "".
func readLastContext(path string) (string, error) {
	b, err := os.ReadFile(path)
	if os.IsNotExist(err) {
		return "", nil
	}
	return string(b), err
}

// writeLastContext saves the specified value to the state file.
// It creates missing parent directories.
func writeLastContext(path, value string) error {
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create parent directories: %w", err)
	}
	return os.WriteFile(path, []byte(value), 0644)
}


================================================
FILE: cmd/kubectx/state_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"os"
	"path/filepath"
	"testing"
)

func Test_readLastContext_nonExistingFile(t *testing.T) {
	s, err := readLastContext(filepath.FromSlash("/non/existing/file"))
	if err != nil {
		t.Fatal(err)
	}
	if s != "" {
		t.Fatalf("expected empty string; got=\"%s\"", s)
	}
}

func Test_readLastContext(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "testfile")
	if err := os.WriteFile(path, []byte("foo"), 0644); err != nil {
		t.Fatal(err)
	}

	s, err := readLastContext(path)
	if err != nil {
		t.Fatal(err)
	}
	if expected := "foo"; s != expected {
		t.Fatalf("expected=\"%s\"; got=\"%s\"", expected, s)
	}
}

func Test_writeLastContext_err(t *testing.T) {
	path := filepath.Join(os.DevNull, "foo", "bar")
	err := writeLastContext(path, "foo")
	if err == nil {
		t.Fatal("got empty error")
	}
}

func Test_writeLastContext(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "foo", "bar")

	if err := writeLastContext(path, "ctx1"); err != nil {
		t.Fatal(err)
	}

	v, err := readLastContext(path)
	if err != nil {
		t.Fatal(err)
	}
	if expected := "ctx1"; v != expected {
		t.Fatalf("read wrong value=\"%s\"; expected=\"%s\"", v, expected)
	}
}

func Test_kubectxFilePath(t *testing.T) {
	t.Setenv("HOME", filepath.FromSlash("/foo/bar"))
	t.Setenv("XDG_CACHE_HOME", "")

	expected := filepath.Join(filepath.FromSlash("/foo/bar"), ".kube", "kubectx")
	v, err := kubectxPrevCtxFile()
	if err != nil {
		t.Fatal(err)
	}
	if v != expected {
		t.Fatalf("expected=\"%s\" got=\"%s\"", expected, v)
	}
}

func Test_kubectxFilePath_xdgCacheHome(t *testing.T) {
	t.Setenv("XDG_CACHE_HOME", filepath.FromSlash("/tmp/xdg-cache"))

	expected := filepath.Join(filepath.FromSlash("/tmp/xdg-cache"), "kubectx")
	v, err := kubectxPrevCtxFile()
	if err != nil {
		t.Fatal(err)
	}
	if v != expected {
		t.Fatalf("expected=\"%s\" got=\"%s\"", expected, v)
	}
}

func Test_kubectxFilePath_error(t *testing.T) {
	t.Setenv("HOME", "")
	t.Setenv("USERPROFILE", "")

	_, err := kubectxPrevCtxFile()
	if err == nil {
		t.Fatal(err)
	}
}


================================================
FILE: cmd/kubectx/switch.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"fmt"
	"io"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

// SwitchOp indicates intention to switch contexts.
type SwitchOp struct {
	Target string // '-' for back and forth, or NAME
}

func (op SwitchOp) Run(_, stderr io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}
	var newCtx string
	var err error
	if op.Target == "-" {
		newCtx, err = swapContext()
	} else {
		newCtx, err = switchContext(op.Target)
	}
	if err != nil {
		return fmt.Errorf("failed to switch context: %w", err)
	}
	if err = printer.Success(stderr, "Switched to context \"%s\".", printer.SuccessColor.Sprint(newCtx)); err != nil {
		return fmt.Errorf("print error: %w", err)
	}
	return nil
}

// switchContext switches to specified context name.
func switchContext(name string) (string, error) {
	prevCtxFile, err := kubectxPrevCtxFile()
	if err != nil {
		return "", fmt.Errorf("failed to determine state file: %w", err)
	}

	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return "", fmt.Errorf("kubeconfig error: %w", err)
	}

	prev, err := kc.GetCurrentContext()
	if err != nil {
		return "", fmt.Errorf("failed to get current context: %w", err)
	}
	exists, err := kc.ContextExists(name)
	if err != nil {
		return "", fmt.Errorf("failed to check context: %w", err)
	}
	if !exists {
		return "", fmt.Errorf("no context exists with the name: \"%s\"", name)
	}
	if err := kc.ModifyCurrentContext(name); err != nil {
		return "", err
	}
	if err := kc.Save(); err != nil {
		return "", fmt.Errorf("failed to save kubeconfig: %w", err)
	}

	if prev != name {
		if err := writeLastContext(prevCtxFile, prev); err != nil {
			return "", fmt.Errorf("failed to save previous context name: %w", err)
		}
	}
	return name, nil
}

// swapContext switches to previously switch context.
func swapContext() (string, error) {
	prevCtxFile, err := kubectxPrevCtxFile()
	if err != nil {
		return "", fmt.Errorf("failed to determine state file: %w", err)
	}
	prev, err := readLastContext(prevCtxFile)
	if err != nil {
		return "", fmt.Errorf("failed to read previous context file: %w", err)
	}
	if prev == "" {
		return "", errors.New("no previous context found")
	}
	return switchContext(prev)
}


================================================
FILE: cmd/kubectx/unset.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

// UnsetOp indicates intention to remove current-context preference.
type UnsetOp struct{}

func (_ UnsetOp) Run(_, stderr io.Writer) error {
	if err := checkIsolatedMode(); err != nil {
		return err
	}
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	if err := kc.UnsetCurrentContext(); err != nil {
		return fmt.Errorf("error while modifying current-context: %w", err)
	}
	if err := kc.Save(); err != nil {
		return fmt.Errorf("failed to save kubeconfig file after modification: %w", err)
	}

	err := printer.Success(stderr, "Active context unset for kubectl.")
	if err != nil {
		return fmt.Errorf("write error: %w", err)
	}
	return nil
}


================================================
FILE: cmd/kubectx/version.go
================================================
package main

import (
	"fmt"
	"io"
)

var (
	version = "v0.0.0+unknown" // populated by goreleaser
)

// VersionOp describes printing version string.
type VersionOp struct{}

func (_ VersionOp) Run(stdout, _ io.Writer) error {
	_, err := fmt.Fprintf(stdout, "%s\n", version)
	if err != nil {
		return fmt.Errorf("write error: %w", err)
	}
	return nil
}


================================================
FILE: cmd/kubens/current.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"fmt"
	"io"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
)

type CurrentOp struct{}

func (c CurrentOp) Run(stdout, _ io.Writer) error {
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	ctx, err := kc.GetCurrentContext()
	if err != nil {
		return fmt.Errorf("failed to get current context: %w", err)
	}
	if ctx == "" {
		return errors.New("current-context is not set")
	}
	ns, err := kc.NamespaceOfContext(ctx)
	if err != nil {
		return fmt.Errorf("failed to read namespace of \"%s\": %w", ctx, err)
	}
	_, err = fmt.Fprintln(stdout, ns)
	if err != nil {
		return fmt.Errorf("write error: %w", err)
	}
	return nil
}


================================================
FILE: cmd/kubens/flags.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"os"
	"slices"
	"strings"

	"github.com/ahmetb/kubectx/internal/cmdutil"
)

// UnsupportedOp indicates an unsupported flag.
type UnsupportedOp struct{ Err error }

func (op UnsupportedOp) Run(_, _ io.Writer) error {
	return op.Err
}

// parseArgs looks at flags (excl. executable name, i.e. argv[0])
// and decides which operation should be taken.
func parseArgs(argv []string) Op {
	n := len(argv)

	if n == 0 {
		if cmdutil.IsInteractiveMode(os.Stdout) {
			return InteractiveSwitchOp{SelfCmd: os.Args[0]}
		}
		return ListOp{}
	}

	if n == 1 {
		v := argv[0]
		switch v {
		case "--help", "-h":
			return HelpOp{}
		case "--version", "-V":
			return VersionOp{}
		case "--current", "-c":
			return CurrentOp{}
		case "--unset", "-u":
			return UnsetOp{}
		default:
			return getSwitchOp(v, false)
		}
	} else if n == 2 {
		// {namespace} -f|--force
		name := argv[0]
		force := slices.Contains([]string{"-f", "--force"}, argv[1])

		if !force {
			if !slices.Contains([]string{"-f", "--force"}, argv[0]) {
				return UnsupportedOp{Err: fmt.Errorf("unsupported arguments %q", argv)}
			}

			// -f|--force {namespace}
			force = true
			name = argv[1]
		}

		return getSwitchOp(name, force)
	}

	return UnsupportedOp{Err: fmt.Errorf("too many arguments")}
}

func getSwitchOp(v string, force bool) Op {
	if strings.HasPrefix(v, "-") && v != "-" {
		return UnsupportedOp{Err: fmt.Errorf("unsupported option %q", v)}
	}
	return SwitchOp{Target: v, Force: force}
}


================================================
FILE: cmd/kubens/flags_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func Test_parseArgs_new(t *testing.T) {
	tests := []struct {
		name string
		args []string
		want Op
	}{
		{name: "nil Args",
			args: nil,
			want: ListOp{}},
		{name: "empty Args",
			args: []string{},
			want: ListOp{}},
		{name: "help shorthand",
			args: []string{"-h"},
			want: HelpOp{}},
		{name: "help long form",
			args: []string{"--help"},
			want: HelpOp{}},
		{name: "current shorthand",
			args: []string{"-c"},
			want: CurrentOp{}},
		{name: "current long form",
			args: []string{"--current"},
			want: CurrentOp{}},
		{name: "unset shorthand",
			args: []string{"-u"},
			want: UnsetOp{}},
		{name: "unset long form",
			args: []string{"--unset"},
			want: UnsetOp{}},
		{name: "switch by name",
			args: []string{"foo"},
			want: SwitchOp{Target: "foo"}},
		{name: "switch by name force short flag",
			args: []string{"foo", "-f"},
			want: SwitchOp{Target: "foo", Force: true}},
		{name: "switch by name force long flag",
			args: []string{"foo", "--force"},
			want: SwitchOp{Target: "foo", Force: true}},
		{name: "switch by name force short flag before name",
			args: []string{"-f", "foo"},
			want: SwitchOp{Target: "foo", Force: true}},
		{name: "switch by name force long flag before name",
			args: []string{"--force", "foo"},
			want: SwitchOp{Target: "foo", Force: true}},
		{name: "switch by name unknown arguments",
			args: []string{"foo", "-x"},
			want: UnsupportedOp{Err: fmt.Errorf("unsupported arguments %q", []string{"foo", "-x"})}},
		{name: "switch by name unknown arguments",
			args: []string{"-x", "foo"},
			want: UnsupportedOp{Err: fmt.Errorf("unsupported arguments %q", []string{"-x", "foo"})}},
		{name: "switch by swap",
			args: []string{"-"},
			want: SwitchOp{Target: "-"}},
		{name: "unrecognized flag",
			args: []string{"-x"},
			want: UnsupportedOp{Err: fmt.Errorf("unsupported option %q", "-x")}},
		{name: "too many args",
			args: []string{"a", "b", "c"},
			want: UnsupportedOp{Err: fmt.Errorf("too many arguments")}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := parseArgs(tt.args)

			var opts cmp.Options
			if _, ok := tt.want.(UnsupportedOp); ok {
				opts = append(opts, cmp.Comparer(func(x, y UnsupportedOp) bool {
					return (x.Err == nil && y.Err == nil) || (x.Err.Error() == y.Err.Error())
				}))
			}

			if diff := cmp.Diff(got, tt.want, opts...); diff != "" {
				t.Errorf("parseArgs(%#v) diff: %s", tt.args, diff)
			}
		})
	}
}


================================================
FILE: cmd/kubens/fzf.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

	"github.com/ahmetb/kubectx/internal/cmdutil"
	"github.com/ahmetb/kubectx/internal/env"
	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

type InteractiveSwitchOp struct {
	SelfCmd string
}

// TODO(ahmetb) This method is heavily repetitive vs kubectx/fzf.go.
func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error {
	// parse kubeconfig just to see if it can be loaded
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		if cmdutil.IsNotFoundErr(err) {
			printer.Warning(stderr, "kubeconfig file not found")
			return nil
		}
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	ctxNames, err := kc.ContextNames()
	if err != nil {
		return fmt.Errorf("failed to get context names: %w", err)
	}
	if len(ctxNames) == 0 {
		return errors.New("no contexts found in the kubeconfig file")
	}

	cmd := exec.Command("fzf", "--ansi", "--no-preview")
	var out bytes.Buffer
	cmd.Stdin = os.Stdin
	cmd.Stderr = stderr
	cmd.Stdout = &out

	cmd.Env = append(os.Environ(),
		fmt.Sprintf("FZF_DEFAULT_COMMAND=%s", op.SelfCmd),
		fmt.Sprintf("%s=1", env.EnvForceColor))
	if err := cmd.Run(); err != nil {
		var exitErr *exec.ExitError
		if !errors.As(err, &exitErr) {
			return err
		}
	}
	choice := strings.TrimSpace(out.String())
	if choice == "" {
		return errors.New("you did not choose any of the options")
	}
	name, err := switchNamespace(kc, choice, false)
	if err != nil {
		return fmt.Errorf("failed to switch namespace: %w", err)
	}
	_ = printer.Success(stderr, "Active namespace is \"%s\".", printer.SuccessColor.Sprint(name))
	return nil
}


================================================
FILE: cmd/kubens/help.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

// HelpOp describes printing help.
type HelpOp struct{}

func (_ HelpOp) Run(stdout, _ io.Writer) error {
	return printUsage(stdout)
}

func printUsage(out io.Writer) error {
	help := `USAGE:
  %PROG%                    : list the namespaces in the current context
  %PROG% <NAME>             : change the active namespace of current context
  %PROG% <NAME> --force/-f  : force change the active namespace of current context (even if it doesn't exist)
  %PROG% -                  : switch to the previous namespace in this context
  %PROG% -c, --current      : show the current namespace
  %PROG% -h,--help          : show this message
  %PROG% -u,--unset         : unset the namespace choice (set to 'default')
  %PROG% -V,--version       : show version`

	// TODO this replace logic is duplicated between this and kubectx
	help = strings.ReplaceAll(help, "%PROG%", selfName())

	_, err := fmt.Fprintf(out, "%s\n", help)
	if err != nil {
		return fmt.Errorf("write error: %w", err)
	}
	return nil
}

// selfName guesses how the user invoked the program.
func selfName() string {
	// TODO this method is duplicated between this and kubectx
	me := filepath.Base(os.Args[0])
	pluginPrefix := "kubectl-"
	if strings.HasPrefix(me, pluginPrefix) {
		return "kubectl " + strings.TrimPrefix(me, pluginPrefix)
	}
	return "kubens"
}


================================================
FILE: cmd/kubens/list.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"slices"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	_ "k8s.io/client-go/plugin/pkg/client/auth"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

type ListOp struct{}

func (op ListOp) Run(stdout, stderr io.Writer) error {
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	ctx, err := kc.GetCurrentContext()
	if err != nil {
		return fmt.Errorf("failed to get current context: %w", err)
	}
	if ctx == "" {
		return errors.New("current-context is not set")
	}
	curNs, err := kc.NamespaceOfContext(ctx)
	if err != nil {
		return fmt.Errorf("cannot read current namespace: %w", err)
	}

	ns, err := queryNamespaces(kc)
	if err != nil {
		return fmt.Errorf("could not list namespaces (is the cluster accessible?): %w", err)
	}

	for _, c := range ns {
		s := c
		if c == curNs {
			s = printer.ActiveItemColor.Sprint(c)
		}
		fmt.Fprintf(stdout, "%s\n", s)
	}
	return nil
}

func queryNamespaces(kc *kubeconfig.Kubeconfig) ([]string, error) {
	if os.Getenv("_MOCK_NAMESPACES") != "" {
		return []string{"ns1", "ns2"}, nil
	}

	clientset, err := newKubernetesClientSet(kc)
	if err != nil {
		return nil, fmt.Errorf("failed to initialize k8s REST client: %w", err)
	}

	var out []string
	var next string
	for {
		list, err := clientset.CoreV1().Namespaces().List(
			context.Background(),
			metav1.ListOptions{
				Limit:    500,
				Continue: next,
			})
		if err != nil {
			return nil, fmt.Errorf("failed to list namespaces from k8s API: %w", err)
		}
		next = list.Continue
		out = slices.Grow(out, len(list.Items))
		for _, it := range list.Items {
			out = append(out, it.Name)
		}
		if next == "" {
			break
		}
	}
	return out, nil
}

func newKubernetesClientSet(kc *kubeconfig.Kubeconfig) (*kubernetes.Clientset, error) {
	b, err := kc.Bytes()
	if err != nil {
		return nil, fmt.Errorf("failed to convert in-memory kubeconfig to yaml: %w", err)
	}
	cfg, err := clientcmd.RESTConfigFromKubeConfig(b)
	if err != nil {
		return nil, fmt.Errorf("failed to initialize config: %w", err)
	}
	return kubernetes.NewForConfig(cfg)
}


================================================
FILE: cmd/kubens/main.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io"
	"os"

	"github.com/ahmetb/kubectx/internal/cmdutil"
	"github.com/ahmetb/kubectx/internal/env"
	"github.com/ahmetb/kubectx/internal/printer"
	"github.com/fatih/color"
)

type Op interface {
	Run(stdout, stderr io.Writer) error
}

func main() {
	cmdutil.PrintDeprecatedEnvWarnings(color.Error, os.Environ())
	op := parseArgs(os.Args[1:])
	if err := op.Run(color.Output, color.Error); err != nil {
		printer.Error(color.Error, "%s", err)

		if _, ok := os.LookupEnv(env.EnvDebug); ok {
			// print stack trace in verbose mode
			fmt.Fprintf(color.Error, "[DEBUG] error: %+v\n", err)
		}
		defer os.Exit(1)
	}
}


================================================
FILE: cmd/kubens/statefile.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/ahmetb/kubectx/internal/cmdutil"
)

var defaultDir = filepath.Join(cmdutil.CacheDir(), "kubens")

type NSFile struct {
	dir string
	ctx string
}

func NewNSFile(ctx string) NSFile { return NSFile{dir: defaultDir, ctx: ctx} }

func (f NSFile) path() string {
	fn := f.ctx
	if isWindows() {
		// bug 230: eks clusters contain ':' in ctx name, not a valid file name for win32
		fn = strings.ReplaceAll(fn, ":", "__")
	}
	return filepath.Join(f.dir, fn)
}

// Load reads the previous namespace setting, or returns empty if not exists.
func (f NSFile) Load() (string, error) {
	b, err := os.ReadFile(f.path())
	if err != nil {
		if os.IsNotExist(err) {
			return "", nil
		}
		return "", err
	}
	return string(bytes.TrimSpace(b)), nil
}

// Save stores the previous namespace information in the file.
func (f NSFile) Save(value string) error {
	d := filepath.Dir(f.path())
	if err := os.MkdirAll(d, 0755); err != nil {
		return err
	}
	return os.WriteFile(f.path(), []byte(value), 0644)
}

// isWindows determines if the process is running on windows OS.
func isWindows() bool {
	if os.Getenv("_FORCE_GOOS") == "windows" { // for testing
		return true
	}
	return runtime.GOOS == "windows"
}


================================================
FILE: cmd/kubens/statefile_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"runtime"
	"strings"
	"testing"
)

func TestNSFile(t *testing.T) {
	td := t.TempDir()

	f := NewNSFile("foo")
	f.dir = td
	v, err := f.Load()
	if err != nil {
		t.Fatal(err)
	}
	if v != "" {
		t.Fatalf("Load() expected empty; got=%v", err)
	}

	err = f.Save("bar")
	if err != nil {
		t.Fatalf("Save() err=%v", err)
	}

	v, err = f.Load()
	if err != nil {
		t.Fatal(err)
	}
	if expected := "bar"; v != expected {
		t.Fatalf("Load()=\"%s\"; expected=\"%s\"", v, expected)
	}
}

func TestNSFile_path_windows(t *testing.T) {
	t.Setenv("_FORCE_GOOS", "windows")
	fp := NewNSFile("a:b:c").path()

	if expected := "a__b__c"; !strings.HasSuffix(fp, expected) {
		t.Fatalf("file did not have expected ending %q: %s", expected, fp)
	}
}

func Test_isWindows(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("won't test this case on windows")
	}

	got := isWindows()
	if got {
		t.Fatalf("isWindows() returned true for %s", runtime.GOOS)
	}

	t.Setenv("_FORCE_GOOS", "windows")
	if !isWindows() {
		t.Fatalf("isWindows() failed to detect windows with env override.")
	}
}


================================================
FILE: cmd/kubens/switch.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"

	errors2 "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

type SwitchOp struct {
	Target string // '-' for back and forth, or NAME
	Force  bool   // force switch even if the namespace doesn't exist
}

func (s SwitchOp) Run(_, stderr io.Writer) error {
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	toNS, err := switchNamespace(kc, s.Target, s.Force)
	if err != nil {
		return err
	}
	err = printer.Success(stderr, "Active namespace is \"%s\"", printer.SuccessColor.Sprint(toNS))
	return err
}

func switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (string, error) {
	ctx, err := kc.GetCurrentContext()
	if err != nil {
		return "", fmt.Errorf("failed to get current context: %w", err)
	}
	if ctx == "" {
		return "", errors.New("current-context is not set")
	}
	curNS, err := kc.NamespaceOfContext(ctx)
	if err != nil {
		return "", fmt.Errorf("failed to get current namespace: %w", err)
	}

	f := NewNSFile(ctx)
	prev, err := f.Load()
	if err != nil {
		return "", fmt.Errorf("failed to load previous namespace from file: %w", err)
	}

	if ns == "-" {
		if prev == "" {
			return "", fmt.Errorf("No previous namespace found for current context (%s)", ctx)
		}
		ns = prev
	}

	if !force {
		ok, err := namespaceExists(kc, ns)
		if err != nil {
			return "", fmt.Errorf("failed to query if namespace exists (is cluster accessible?): %w", err)
		}
		if !ok {
			return "", fmt.Errorf("no namespace exists with name \"%s\"", ns)
		}
	}

	if err := kc.SetNamespace(ctx, ns); err != nil {
		return "", fmt.Errorf("failed to change to namespace \"%s\": %w", ns, err)
	}
	if err := kc.Save(); err != nil {
		return "", fmt.Errorf("failed to save kubeconfig file: %w", err)
	}
	if curNS != ns {
		if err := f.Save(curNS); err != nil {
			return "", fmt.Errorf("failed to save the previous namespace to file: %w", err)
		}
	}
	return ns, nil
}

func namespaceExists(kc *kubeconfig.Kubeconfig, ns string) (bool, error) {
	// for tests
	if os.Getenv("_MOCK_NAMESPACES") != "" {
		return ns == "ns1" || ns == "ns2", nil
	}

	clientset, err := newKubernetesClientSet(kc)
	if err != nil {
		return false, fmt.Errorf("failed to initialize k8s REST client: %w", err)
	}

	namespace, err := clientset.CoreV1().Namespaces().Get(context.Background(), ns, metav1.GetOptions{})
	if errors2.IsNotFound(err) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("failed to query namespace %q from k8s API: %w", ns, err)
	}
	return namespace != nil, nil
}


================================================
FILE: cmd/kubens/unset.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"errors"
	"fmt"
	"io"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

// UnsetOp indicates intention to remove current namespace preference.
type UnsetOp struct{}

func (_ UnsetOp) Run(_, stderr io.Writer) error {
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	ns, err := clearNamespace(kc)
	if err != nil {
		return err
	}
	err = printer.Success(stderr, "Active namespace is \"%s\".", printer.SuccessColor.Sprint(ns))
	return err
}

func clearNamespace(kc *kubeconfig.Kubeconfig) (string, error) {
	ctx, err := kc.GetCurrentContext()
	if err != nil {
		return "", fmt.Errorf("failed to get current context: %w", err)
	}
	ns := "default"
	if ctx == "" {
		return "", errors.New("current-context is not set")
	}

	if err := kc.SetNamespace(ctx, ns); err != nil {
		return "", fmt.Errorf("failed to clear namespace: %w", err)
	}
	if err := kc.Save(); err != nil {
		return "", fmt.Errorf("failed to save kubeconfig file: %w", err)
	}
	return ns, nil
}


================================================
FILE: cmd/kubens/version.go
================================================
package main

import (
	"fmt"
	"io"
)

var (
	version = "v0.0.0+unknown" // populated by goreleaser
)

// VersionOp describes printing version string.
type VersionOp struct{}

func (_ VersionOp) Run(stdout, _ io.Writer) error {
	_, err := fmt.Fprintf(stdout, "%s\n", version)
	if err != nil {
		return fmt.Errorf("write error: %w", err)
	}
	return nil
}


================================================
FILE: completion/_kubectx.zsh
================================================
#compdef kubectx kctx=kubectx

local KUBECTX="${HOME}/.kube/kubectx"
PREV=""

local context_array=("${(@f)$(kubectl config get-contexts --output='name')}")
local all_contexts=(\'${^context_array}\')

if [ -f "$KUBECTX" ]; then
    # show '-' only if there's a saved previous context
    local PREV=$(cat "${KUBECTX}")

    _arguments \
      "-d:*: :(${all_contexts})" \
      "(- *): :(- ${all_contexts})"
else
    _arguments \
      "-d:*: :(${all_contexts})" \
      "(- *): :(${all_contexts})"
fi


================================================
FILE: completion/_kubens.zsh
================================================
#compdef kubens kns=kubens
_arguments "1: :(- $(kubectl get namespaces -o=jsonpath='{range .items[*].metadata.name}{@}{"\n"}{end}'))"


================================================
FILE: completion/kubectx.bash
================================================
_kube_contexts()
{
  local curr_arg;
  curr_arg=${COMP_WORDS[COMP_CWORD]}
  COMPREPLY=( $(compgen -W "- $(kubectl config get-contexts --output='name')" -- $curr_arg ) );
}

complete -F _kube_contexts kubectx kctx


================================================
FILE: completion/kubectx.fish
================================================
# kubectx

function __fish_kubectx_arg_number -a number
    set -l cmd (commandline -opc)
    test (count $cmd) -eq $number
end

complete -f -c kubectx
complete -f -x -c kubectx -n '__fish_kubectx_arg_number 1' -a "(kubectl config get-contexts --output='name')"
complete -f -x -c kubectx -n '__fish_kubectx_arg_number 1' -a "-" -d "switch to the previous namespace in this context"


================================================
FILE: completion/kubens.bash
================================================
_kube_namespaces()
{
  local curr_arg;
  curr_arg=${COMP_WORDS[COMP_CWORD]}
  COMPREPLY=( $(compgen -W "- $(kubectl get namespaces -o=jsonpath='{range .items[*].metadata.name}{@}{"\n"}{end}')" -- $curr_arg ) );
}

complete -F _kube_namespaces kubens kns


================================================
FILE: completion/kubens.fish
================================================
# kubens

function __fish_kubens_arg_number -a number
    set -l cmd (commandline -opc)
    test (count $cmd) -eq $number
end

complete -f -c kubens
complete -f -x -c kubens -n '__fish_kubens_arg_number 1' -a "(kubectl get ns -o=custom-columns=NAME:.metadata.name --no-headers)"
complete -f -x -c kubens -n '__fish_kubens_arg_number 1' -a "-" -d "switch to the previous namespace in this context"
complete -f -x -c kubens -n '__fish_kubens_arg_number 1' -s c -l current -d "show the current namespace"
complete -f -x -c kubens -n '__fish_kubens_arg_number 1' -s h -l help -d "show the help message"


================================================
FILE: go.mod
================================================
module github.com/ahmetb/kubectx

go 1.25.0

require (
	facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
	github.com/fatih/color v1.18.0
	github.com/google/go-cmp v0.7.0
	github.com/mattn/go-isatty v0.0.20
	k8s.io/apimachinery v0.35.2
	k8s.io/client-go v0.35.2
	sigs.k8s.io/kustomize/kyaml v0.21.1
)

require (
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/emicklei/go-restful/v3 v3.12.2 // indirect
	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
	github.com/go-errors/errors v1.4.2 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-openapi/jsonpointer v0.21.0 // indirect
	github.com/go-openapi/jsonreference v0.20.2 // indirect
	github.com/go-openapi/swag v0.23.0 // indirect
	github.com/google/gnostic-models v0.7.0 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/josharian/intern v1.0.0 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/mailru/easyjson v0.7.7 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/spf13/pflag v1.0.9 // indirect
	github.com/x448/float16 v0.8.4 // indirect
	go.yaml.in/yaml/v2 v2.4.3 // indirect
	go.yaml.in/yaml/v3 v3.0.4 // indirect
	golang.org/x/net v0.47.0 // indirect
	golang.org/x/oauth2 v0.30.0 // indirect
	golang.org/x/sys v0.38.0 // indirect
	golang.org/x/term v0.37.0 // indirect
	golang.org/x/text v0.31.0 // indirect
	golang.org/x/time v0.9.0 // indirect
	google.golang.org/protobuf v1.36.8 // indirect
	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
	gopkg.in/inf.v0 v0.9.1 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
	k8s.io/api v0.35.2 // indirect
	k8s.io/klog/v2 v2.130.1 // indirect
	k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
	k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
	sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
	sigs.k8s.io/randfill v1.0.0 // indirect
	sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
	sigs.k8s.io/yaml v1.6.0 // indirect
)


================================================
FILE: go.sum
================================================
facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:1pSweJFeR3Pqx7uoelppkzeegfUBXL6I2FFAbfXw570=
facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:npRYmtaITVom7rcSo+pRURltHSG2r4TQM1cdqJ2dUB0=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=
k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=
k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=


================================================
FILE: internal/cmdutil/deprecated.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmdutil

import (
	"io"
	"strings"

	"github.com/ahmetb/kubectx/internal/printer"
)

func PrintDeprecatedEnvWarnings(out io.Writer, vars []string) {
	for _, vv := range vars {
		parts := strings.SplitN(vv, "=", 2)
		if len(parts) != 2 {
			continue
		}
		key := parts[0]

		if key == `KUBECTX_CURRENT_FGCOLOR` || key == `KUBECTX_CURRENT_BGCOLOR` {
			printer.Warning(out, "%s environment variable is now deprecated", key)
		}
	}
}


================================================
FILE: internal/cmdutil/deprecated_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmdutil

import (
	"bytes"
	"strings"
	"testing"
)

func TestPrintDeprecatedEnvWarnings_noDeprecatedVars(t *testing.T) {
	var out bytes.Buffer
	PrintDeprecatedEnvWarnings(&out, []string{
		"A=B",
		"PATH=/foo:/bar:/bin",
	})
	if v := out.String(); len(v) > 0 {
		t.Fatalf("something written to buf: %v", v)
	}
}

func TestPrintDeprecatedEnvWarnings_bgColors(t *testing.T) {
	var out bytes.Buffer

	PrintDeprecatedEnvWarnings(&out, []string{
		"KUBECTX_CURRENT_FGCOLOR=1",
		"KUBECTX_CURRENT_BGCOLOR=2",
	})
	v := out.String()
	if !strings.Contains(v, "KUBECTX_CURRENT_FGCOLOR") {
		t.Fatalf("output doesn't contain 'KUBECTX_CURRENT_FGCOLOR': \"%s\"", v)
	}
	if !strings.Contains(v, "KUBECTX_CURRENT_BGCOLOR") {
		t.Fatalf("output doesn't contain 'KUBECTX_CURRENT_BGCOLOR': \"%s\"", v)
	}
}


================================================
FILE: internal/cmdutil/interactive.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmdutil

import (
	"os"
	"os/exec"

	"github.com/mattn/go-isatty"

	"github.com/ahmetb/kubectx/internal/env"
)

// isTerminal determines if given fd is a TTY.
func isTerminal(fd *os.File) bool {
	return isatty.IsTerminal(fd.Fd())
}

// fzfInstalled determines if fzf(1) is in PATH.
func fzfInstalled() bool {
	v, _ := exec.LookPath("fzf")
	if v != "" {
		return true
	}
	return false
}

// IsInteractiveMode determines if we can do choosing with fzf.
func IsInteractiveMode(stdout *os.File) bool {
	v := os.Getenv(env.EnvFZFIgnore)
	return v == "" && isTerminal(stdout) && fzfInstalled()
}


================================================
FILE: internal/cmdutil/util.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmdutil

import (
	"errors"
	"os"
	"path/filepath"
)

func HomeDir() string {
	home := os.Getenv("HOME")
	if home == "" {
		home = os.Getenv("USERPROFILE") // windows
	}
	return home
}

// CacheDir returns XDG_CACHE_HOME if set, otherwise $HOME/.kube,
// matching the bash scripts' behavior: ${XDG_CACHE_HOME:-$HOME/.kube}.
func CacheDir() string {
	if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
		return xdg
	}
	home := HomeDir()
	if home == "" {
		return ""
	}
	return filepath.Join(home, ".kube")
}

// IsNotFoundErr determines if the underlying error is os.IsNotExist.
func IsNotFoundErr(err error) bool {
	for e := err; e != nil; e = errors.Unwrap(e) {
		if os.IsNotExist(e) {
			return true
		}
	}
	return false
}


================================================
FILE: internal/cmdutil/util_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmdutil

import (
	"path/filepath"
	"testing"
)

func Test_homeDir(t *testing.T) {
	type env struct{ k, v string }
	cases := []struct {
		name string
		envs []env
		want string
	}{
		{
			name: "don't use XDG_CACHE_HOME as homedir",
			envs: []env{
				{"XDG_CACHE_HOME", "xdg"},
				{"HOME", "home"},
			},
			want: "home",
		},
		{
			name: "HOME over USERPROFILE",
			envs: []env{
				{"HOME", "home"},
				{"USERPROFILE", "up"},
			},
			want: "home",
		},
		{
			name: "only USERPROFILE available",
			envs: []env{
				{"HOME", ""},
				{"USERPROFILE", "up"},
			},
			want: "up",
		},
		{
			name: "none available",
			envs: []env{
				{"HOME", ""},
				{"USERPROFILE", ""},
			},
			want: "",
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(tt *testing.T) {
			for _, e := range c.envs {
				tt.Setenv(e.k, e.v)
			}

			got := HomeDir()
			if got != c.want {
				t.Errorf("expected:%q got:%q", c.want, got)
			}
		})
	}
}

func TestCacheDir(t *testing.T) {
	t.Run("XDG_CACHE_HOME set", func(t *testing.T) {
		t.Setenv("XDG_CACHE_HOME", "/tmp/xdg-cache")
		t.Setenv("HOME", "/home/user")
		if got := CacheDir(); got != "/tmp/xdg-cache" {
			t.Errorf("expected:%q got:%q", "/tmp/xdg-cache", got)
		}
	})
	t.Run("XDG_CACHE_HOME unset, falls back to HOME/.kube", func(t *testing.T) {
		t.Setenv("XDG_CACHE_HOME", "")
		t.Setenv("HOME", "/home/user")
		want := filepath.Join("/home/user", ".kube")
		if got := CacheDir(); got != want {
			t.Errorf("expected:%q got:%q", want, got)
		}
	})
	t.Run("neither set", func(t *testing.T) {
		t.Setenv("XDG_CACHE_HOME", "")
		t.Setenv("HOME", "")
		t.Setenv("USERPROFILE", "")
		if got := CacheDir(); got != "" {
			t.Errorf("expected:%q got:%q", "", got)
		}
	})
}


================================================
FILE: internal/env/constants.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package env

const (
	// EnvFZFIgnore describes the environment variable to set to disable
	// interactive context selection when fzf is installed.
	EnvFZFIgnore = "KUBECTX_IGNORE_FZF"

	// EnvNoColor describes the environment variable to disable color usage
	// when printing current context in a list.
	EnvNoColor = `NO_COLOR`

	// EnvForceColor describes the "internal" environment variable to force
	// color usage to show current context in a list.
	EnvForceColor = `_KUBECTX_FORCE_COLOR`

	// EnvDebug describes the internal environment variable for more verbose logging.
	EnvDebug = `DEBUG`

	EnvIsolatedShell = "KUBECTX_ISOLATED_SHELL"
)


================================================
FILE: internal/kubeconfig/contextmodify.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"errors"

	"sigs.k8s.io/kustomize/kyaml/yaml"
)

func (k *Kubeconfig) DeleteContextEntry(deleteName string) error {
	contexts, err := k.contextsNode()
	if err != nil {
		return err
	}
	if err := contexts.PipeE(
		yaml.ElementSetter{
			Keys:   []string{"name"},
			Values: []string{deleteName},
		},
	); err != nil {
		return err
	}
	return nil
}

func (k *Kubeconfig) ModifyCurrentContext(name string) error {
	if err := k.config.PipeE(yaml.SetField("current-context", yaml.NewScalarRNode(name))); err != nil {
		return err
	}
	return nil
}

func (k *Kubeconfig) ModifyContextName(old, new string) error {
	context, err := k.config.Pipe(yaml.Lookup("contexts", "[name="+old+"]"))
	if err != nil {
		return err
	}
	if context == nil {
		return errors.New("\"contexts\" entry is nil")
	}
	if err := context.PipeE(yaml.SetField("name", yaml.NewScalarRNode(new))); err != nil {
		return err
	}
	return nil
}


================================================
FILE: internal/kubeconfig/contextmodify_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"testing"

	"github.com/google/go-cmp/cmp"

	"github.com/ahmetb/kubectx/internal/testutil"
)

func TestKubeconfig_DeleteContextEntry_errors(t *testing.T) {
	kc := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`[1, 2, 3]`))
	_ = kc.Parse()
	err := kc.DeleteContextEntry("foo")
	if err == nil {
		t.Fatal("supposed to fail on non-mapping nodes")
	}

	kc = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`a: b`))
	_ = kc.Parse()
	err = kc.DeleteContextEntry("foo")
	if err == nil {
		t.Fatal("supposed to fail if contexts key does not exist")
	}

	kc = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`contexts: "some string"`))
	_ = kc.Parse()
	err = kc.DeleteContextEntry("foo")
	if err == nil {
		t.Fatal("supposed to fail if contexts key is not an array")
	}
}

func TestKubeconfig_DeleteContextEntry(t *testing.T) {
	test := WithMockKubeconfigLoader(
		testutil.KC().WithCtxs(
			testutil.Ctx("c1"),
			testutil.Ctx("c2"),
			testutil.Ctx("c3")).ToYAML(t))
	kc := new(Kubeconfig).WithLoader(test)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.DeleteContextEntry("c1"); err != nil {
		t.Fatal(err)
	}
	if err := kc.Save(); err != nil {
		t.Fatal(err)
	}

	expected := testutil.KC().WithCtxs(
		testutil.Ctx("c2"),
		testutil.Ctx("c3")).ToYAML(t)
	out := test.Output()
	if diff := cmp.Diff(expected, out); diff != "" {
		t.Fatalf("diff: %s", diff)
	}
}

func TestKubeconfig_ModifyCurrentContext_fieldExists(t *testing.T) {
	test := WithMockKubeconfigLoader(
		testutil.KC().WithCurrentCtx("abc").Set("field1", "value1").ToYAML(t))
	kc := new(Kubeconfig).WithLoader(test)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.ModifyCurrentContext("foo"); err != nil {
		t.Fatal(err)
	}
	if err := kc.Save(); err != nil {
		t.Fatal(err)
	}

	expected := testutil.KC().WithCurrentCtx("foo").Set("field1", "value1").ToYAML(t)
	out := test.Output()
	if diff := cmp.Diff(expected, out); diff != "" {
		t.Fatalf("diff: %s", diff)
	}
}

func TestKubeconfig_ModifyCurrentContext_fieldMissing(t *testing.T) {
	test := WithMockKubeconfigLoader(`f1: v1`)
	kc := new(Kubeconfig).WithLoader(test)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.ModifyCurrentContext("foo"); err != nil {
		t.Fatal(err)
	}
	if err := kc.Save(); err != nil {
		t.Fatal(err)
	}

	expected := `f1: v1
current-context: foo
`
	out := test.Output()
	if diff := cmp.Diff(expected, out); diff != "" {
		t.Fatalf("diff: %s", diff)
	}
}

func TestKubeconfig_ModifyContextName_noContextsEntryError(t *testing.T) {
	// no context entries
	test := WithMockKubeconfigLoader(`a: b`)
	kc := new(Kubeconfig).WithLoader(test)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.ModifyContextName("c1", "c2"); err == nil {
		t.Fatal("was expecting error for no 'contexts' entry; got nil")
	}
}

func TestKubeconfig_ModifyContextName_contextsEntryNotSequenceError(t *testing.T) {
	// no context entries
	test := WithMockKubeconfigLoader(
		`contexts: "hello"`)
	kc := new(Kubeconfig).WithLoader(test)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.ModifyContextName("c1", "c2"); err == nil {
		t.Fatal("was expecting error for 'context entry not a sequence'; got nil")
	}
}

func TestKubeconfig_ModifyContextName_noChange(t *testing.T) {
	test := WithMockKubeconfigLoader(testutil.KC().WithCtxs(
		testutil.Ctx("c1"),
		testutil.Ctx("c2"),
		testutil.Ctx("c3")).ToYAML(t))
	kc := new(Kubeconfig).WithLoader(test)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.ModifyContextName("c5", "c6"); err == nil {
		t.Fatal("was expecting error for 'no changes made'")
	}
}

func TestKubeconfig_ModifyContextName(t *testing.T) {
	test := WithMockKubeconfigLoader(testutil.KC().WithCtxs(
		testutil.Ctx("c1"),
		testutil.Ctx("c2"),
		testutil.Ctx("c3")).ToYAML(t))
	kc := new(Kubeconfig).WithLoader(test)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.ModifyContextName("c1", "ccc"); err != nil {
		t.Fatal(err)
	}
	if err := kc.Save(); err != nil {
		t.Fatal(err)
	}

	expected := testutil.KC().WithCtxs(
		testutil.Ctx("ccc"),
		testutil.Ctx("c2"),
		testutil.Ctx("c3")).ToYAML(t)
	out := test.Output()
	if diff := cmp.Diff(expected, out); diff != "" {
		t.Fatalf("diff: %s", diff)
	}
}


================================================
FILE: internal/kubeconfig/contexts.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"errors"
	"fmt"
	"slices"

	"sigs.k8s.io/kustomize/kyaml/yaml"
)

func (k *Kubeconfig) contextsNode() (*yaml.RNode, error) {
	contexts, err := k.config.Pipe(yaml.Get("contexts"))
	if err != nil {
		return nil, err
	}
	if contexts == nil {
		return nil, errors.New("\"contexts\" entry is nil")
	} else if contexts.YNode().Kind != yaml.SequenceNode {
		return nil, errors.New("\"contexts\" is not a sequence node")
	}
	return contexts, nil
}

func (k *Kubeconfig) contextNode(name string) (*yaml.RNode, error) {
	contexts, err := k.contextsNode()
	if err != nil {
		return nil, err
	}
	context, err := contexts.Pipe(yaml.Lookup("[name=" + name + "]"))
	if err != nil {
		return nil, err
	}
	if context == nil {
		return nil, fmt.Errorf("context with name \"%s\" not found", name)
	}
	return context, nil
}

func (k *Kubeconfig) ContextNames() ([]string, error) {
	contexts, err := k.config.Pipe(yaml.Get("contexts"))
	if err != nil {
		return nil, fmt.Errorf("failed to get contexts: %w", err)
	}
	if contexts == nil {
		return nil, nil
	}
	names, err := contexts.ElementValues("name")
	if err != nil {
		return nil, fmt.Errorf("failed to get context names: %w", err)
	}
	return names, nil
}

func (k *Kubeconfig) ContextExists(name string) (bool, error) {
	names, err := k.ContextNames()
	if err != nil {
		return false, err
	}
	return slices.Contains(names, name), nil
}


================================================
FILE: internal/kubeconfig/contexts_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"testing"

	"github.com/google/go-cmp/cmp"

	"github.com/ahmetb/kubectx/internal/testutil"
)

func TestKubeconfig_ContextNames(t *testing.T) {
	tl := WithMockKubeconfigLoader(
		testutil.KC().WithCtxs(
			testutil.Ctx("abc"),
			testutil.Ctx("def"),
			testutil.Ctx("ghi")).Set("field1", map[string]string{"bar": "zoo"}).ToYAML(t))
	kc := new(Kubeconfig).WithLoader(tl)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}

	ctx, err := kc.ContextNames()
	if err != nil {
		t.Fatal(err)
	}
	expected := []string{"abc", "def", "ghi"}
	if diff := cmp.Diff(expected, ctx); diff != "" {
		t.Fatalf("%s", diff)
	}
}

func TestKubeconfig_ContextNames_noContextsEntry(t *testing.T) {
	tl := WithMockKubeconfigLoader(`a: b`)
	kc := new(Kubeconfig).WithLoader(tl)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	ctx, err := kc.ContextNames()
	if err != nil {
		t.Fatal(err)
	}
	var expected []string = nil
	if diff := cmp.Diff(expected, ctx); diff != "" {
		t.Fatalf("%s", diff)
	}
}

func TestKubeconfig_ContextNames_nonArrayContextsEntry(t *testing.T) {
	tl := WithMockKubeconfigLoader(`contexts: "hello"`)
	kc := new(Kubeconfig).WithLoader(tl)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	_, err := kc.ContextNames()
	if err == nil {
		t.Fatal("expected error for non-array contexts entry")
	}
}

func TestKubeconfig_CheckContextExists(t *testing.T) {
	tl := WithMockKubeconfigLoader(
		testutil.KC().WithCtxs(
			testutil.Ctx("c1"),
			testutil.Ctx("c2")).ToYAML(t))

	kc := new(Kubeconfig).WithLoader(tl)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}

	if exists, err := kc.ContextExists("c1"); err != nil || !exists {
		t.Fatal("c1 actually exists; reported false")
	}
	if exists, err := kc.ContextExists("c2"); err != nil || !exists {
		t.Fatal("c2 actually exists; reported false")
	}
	if exists, err := kc.ContextExists("c3"); err != nil {
		t.Fatal(err)
	} else if exists {
		t.Fatal("c3 does not exist; but reported true")
	}
}


================================================
FILE: internal/kubeconfig/currentcontext.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"fmt"

	"sigs.k8s.io/kustomize/kyaml/yaml"
)

// GetCurrentContext returns "current-context" value in given
// kubeconfig object Node, or returns ("", nil) if not found.
func (k *Kubeconfig) GetCurrentContext() (string, error) {
	v, err := k.config.Pipe(yaml.Get("current-context"))
	if err != nil {
		return "", fmt.Errorf("failed to read current-context: %w", err)
	}
	return yaml.GetValue(v), nil
}

func (k *Kubeconfig) UnsetCurrentContext() error {
	return k.config.PipeE(yaml.SetField("current-context", yaml.NewStringRNode("")))
}


================================================
FILE: internal/kubeconfig/currentcontext_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"testing"

	"github.com/ahmetb/kubectx/internal/testutil"
)

func TestKubeconfig_GetCurrentContext(t *testing.T) {
	tl := WithMockKubeconfigLoader(`current-context: foo`)
	kc := new(Kubeconfig).WithLoader(tl)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	v, err := kc.GetCurrentContext()
	if err != nil {
		t.Fatal(err)
	}

	expected := "foo"
	if v != expected {
		t.Fatalf("expected=\"%s\"; got=\"%s\"", expected, v)
	}
}

func TestKubeconfig_GetCurrentContext_missingField(t *testing.T) {
	tl := WithMockKubeconfigLoader(`abc: def`)
	kc := new(Kubeconfig).WithLoader(tl)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	v, err := kc.GetCurrentContext()
	if err != nil {
		t.Fatal(err)
	}

	expected := ""
	if v != expected {
		t.Fatalf("expected=\"%s\"; got=\"%s\"", expected, v)
	}
}

func TestKubeconfig_UnsetCurrentContext(t *testing.T) {
	tl := WithMockKubeconfigLoader(testutil.KC().WithCurrentCtx("foo").ToYAML(t))
	kc := new(Kubeconfig).WithLoader(tl)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.UnsetCurrentContext(); err != nil {
		t.Fatal(err)
	}
	if err := kc.Save(); err != nil {
		t.Fatal(err)
	}

	out := tl.Output()
	expected := testutil.KC().WithCurrentCtx("").ToYAML(t)
	if out != expected {
		t.Fatalf("expected=\"%s\"; got=\"%s\"", expected, out)
	}
}


================================================
FILE: internal/kubeconfig/helper_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"bytes"
	"io"
	"strings"
)

type MockKubeconfigLoader struct {
	in  io.Reader
	out bytes.Buffer
}

func (t *MockKubeconfigLoader) Read(p []byte) (n int, err error)  { return t.in.Read(p) }
func (t *MockKubeconfigLoader) Write(p []byte) (n int, err error) { return t.out.Write(p) }
func (t *MockKubeconfigLoader) Close() error                      { return nil }
func (t *MockKubeconfigLoader) Reset() error                      { return nil }
func (t *MockKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) {
	return []ReadWriteResetCloser{ReadWriteResetCloser(t)}, nil
}
func (t *MockKubeconfigLoader) Output() string { return t.out.String() }

func WithMockKubeconfigLoader(kubecfg string) *MockKubeconfigLoader {
	return &MockKubeconfigLoader{in: strings.NewReader(kubecfg)}
}


================================================
FILE: internal/kubeconfig/kubeconfig.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"errors"
	"fmt"
	"io"

	"sigs.k8s.io/kustomize/kyaml/yaml"
)

type ReadWriteResetCloser interface {
	io.ReadWriteCloser

	// Reset truncates the file and seeks to the beginning of the file.
	Reset() error
}

type Loader interface {
	Load() ([]ReadWriteResetCloser, error)
}

type Kubeconfig struct {
	loader Loader

	f      ReadWriteResetCloser
	config *yaml.RNode
}

func (k *Kubeconfig) WithLoader(l Loader) *Kubeconfig {
	k.loader = l
	return k
}

func (k *Kubeconfig) Close() error {
	if k.f == nil {
		return nil
	}
	return k.f.Close()
}

func (k *Kubeconfig) Parse() error {
	files, err := k.loader.Load()
	if err != nil {
		return fmt.Errorf("failed to load: %w", err)
	}

	// TODO since we don't support multiple kubeconfig files at the moment, there's just 1 file
	f := files[0]

	k.f = f
	var v yaml.Node
	if err := yaml.NewDecoder(f).Decode(&v); err != nil {
		return fmt.Errorf("failed to decode: %w", err)
	}
	k.config = yaml.NewRNode(&v)
	if k.config.YNode().Kind != yaml.MappingNode {
		return errors.New("kubeconfig file is not a map document")
	}
	return nil
}

func (k *Kubeconfig) Bytes() ([]byte, error) {
	str, err := k.config.String()
	if err != nil {
		return nil, err
	}
	return []byte(str), nil
}

func (k *Kubeconfig) Save() error {
	if err := k.f.Reset(); err != nil {
		return fmt.Errorf("failed to reset file: %w", err)
	}
	enc := yaml.NewEncoder(k.f)
	enc.SetIndent(0)
	if err := enc.Encode(k.config.YNode()); err != nil {
		return err
	}
	return enc.Close()
}


================================================
FILE: internal/kubeconfig/kubeconfig_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"testing"

	"github.com/google/go-cmp/cmp"

	"github.com/ahmetb/kubectx/internal/testutil"
)

func TestParse(t *testing.T) {
	err := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`a: [1, 2`)).Parse()
	if err == nil {
		t.Fatal("expected error from bad yaml")
	}

	err = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`[1, 2, 3]`)).Parse()
	if err == nil {
		t.Fatal("expected error from not-mapping root node")
	}

	err = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(`current-context: foo`)).Parse()
	if err != nil {
		t.Fatal(err)
	}

	err = new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(testutil.KC().
		WithCurrentCtx("foo").
		WithCtxs().ToYAML(t))).Parse()
	if err != nil {
		t.Fatal(err)
	}
}

func TestSave(t *testing.T) {
	in := "a: [1, 2, 3]\n"
	test := WithMockKubeconfigLoader(in)
	kc := new(Kubeconfig).WithLoader(test)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}
	if err := kc.ModifyCurrentContext("hello"); err != nil {
		t.Fatal(err)
	}
	if err := kc.Save(); err != nil {
		t.Fatal(err)
	}
	expected := "a: [1, 2, 3]\ncurrent-context: hello\n"
	if diff := cmp.Diff(expected, test.Output()); diff != "" {
		t.Fatal(diff)
	}
}


================================================
FILE: internal/kubeconfig/kubeconfigloader.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/ahmetb/kubectx/internal/cmdutil"
)

var (
	DefaultLoader Loader = new(StandardKubeconfigLoader)
)

type StandardKubeconfigLoader struct{}

type kubeconfigFile struct{ *os.File }

func (*StandardKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) {
	cfgPath, err := kubeconfigPath()
	if err != nil {
		return nil, fmt.Errorf("cannot determine kubeconfig path: %w", err)
	}

	f, err := os.OpenFile(cfgPath, os.O_RDWR, 0)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, fmt.Errorf("kubeconfig file not found: %w", err)
		}
		return nil, fmt.Errorf("failed to open file: %w", err)
	}

	// TODO we'll return all kubeconfig files when we start implementing multiple kubeconfig support
	return []ReadWriteResetCloser{ReadWriteResetCloser(&kubeconfigFile{f})}, nil
}

func (kf *kubeconfigFile) Reset() error {
	if err := kf.Truncate(0); err != nil {
		return fmt.Errorf("failed to truncate file: %w", err)
	}
	if _, err := kf.Seek(0, 0); err != nil {
		return fmt.Errorf("failed to seek in file: %w", err)
	}
	return nil
}

func kubeconfigPath() (string, error) {
	// KUBECONFIG env var
	if v := os.Getenv("KUBECONFIG"); v != "" {
		list := filepath.SplitList(v)
		if len(list) > 1 {
			// TODO KUBECONFIG=file1:file2 currently not supported
			return "", errors.New("multiple files in KUBECONFIG are currently not supported")
		}
		return v, nil
	}

	// default path
	home := cmdutil.HomeDir()
	if home == "" {
		return "", errors.New("HOME or USERPROFILE environment variable not set")
	}
	return filepath.Join(home, ".kube", "config"), nil
}


================================================
FILE: internal/kubeconfig/kubeconfigloader_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/ahmetb/kubectx/internal/cmdutil"
)

func Test_kubeconfigPath(t *testing.T) {
	t.Setenv("HOME", "/x/y/z")

	expected := filepath.FromSlash("/x/y/z/.kube/config")
	got, err := kubeconfigPath()
	if err != nil {
		t.Fatal(err)
	}
	if got != expected {
		t.Fatalf("got=%q expected=%q", got, expected)
	}
}

func Test_kubeconfigPath_noEnvVars(t *testing.T) {
	t.Setenv("XDG_CACHE_HOME", "")
	t.Setenv("HOME", "")
	t.Setenv("USERPROFILE", "")

	_, err := kubeconfigPath()
	if err == nil {
		t.Fatalf("expected error")
	}
}

func Test_kubeconfigPath_envOvveride(t *testing.T) {
	t.Setenv("KUBECONFIG", "foo")

	v, err := kubeconfigPath()
	if err != nil {
		t.Fatal(err)
	}
	if expected := "foo"; v != expected {
		t.Fatalf("expected=%q, got=%q", expected, v)
	}
}

func Test_kubeconfigPath_envOvverideDoesNotSupportPathSeparator(t *testing.T) {
	path := strings.Join([]string{"file1", "file2"}, string(os.PathListSeparator))
	t.Setenv("KUBECONFIG", path)

	_, err := kubeconfigPath()
	if err == nil {
		t.Fatal("expected error")
	}
}

func TestStandardKubeconfigLoader_returnsNotFoundErr(t *testing.T) {
	t.Setenv("KUBECONFIG", "foo")
	kc := new(Kubeconfig).WithLoader(DefaultLoader)
	err := kc.Parse()
	if err == nil {
		t.Fatal("expected err")
	}
	if !cmdutil.IsNotFoundErr(err) {
		t.Fatalf("expected ENOENT error; got=%v", err)
	}
}


================================================
FILE: internal/kubeconfig/namespace.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

const (
	defaultNamespace = "default"
)

func (k *Kubeconfig) NamespaceOfContext(contextName string) (string, error) {
	ctx, err := k.contextNode(contextName)
	if err != nil {
		return "", err
	}
	namespace, err := ctx.Pipe(yaml.Lookup("context", "namespace"))
	if namespace == nil || err != nil {
		return defaultNamespace, err
	}
	return yaml.GetValue(namespace), nil
}

func (k *Kubeconfig) SetNamespace(ctxName string, ns string) error {
	ctx, err := k.contextNode(ctxName)
	if err != nil {
		return err
	}
	if err := ctx.PipeE(
		yaml.LookupCreate(yaml.MappingNode, "context"),
		yaml.SetField("namespace", yaml.NewStringRNode(ns)),
	); err != nil {
		return err
	}
	return nil
}


================================================
FILE: internal/kubeconfig/namespace_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubeconfig

import (
	"testing"

	"github.com/google/go-cmp/cmp"

	"github.com/ahmetb/kubectx/internal/testutil"
)

func TestKubeconfig_NamespaceOfContext_ctxNotFound(t *testing.T) {
	kc := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(testutil.KC().
		WithCtxs(testutil.Ctx("c1")).ToYAML(t)))
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}

	_, err := kc.NamespaceOfContext("c2")
	if err == nil {
		t.Fatal("expected err")
	}
}

func TestKubeconfig_NamespaceOfContext(t *testing.T) {
	kc := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(testutil.KC().
		WithCtxs(
			testutil.Ctx("c1"),
			testutil.Ctx("c2").Ns("c2n1")).ToYAML(t)))
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}

	v1, err := kc.NamespaceOfContext("c1")
	if err != nil {
		t.Fatal("expected err")
	}
	if expected := `default`; v1 != expected {
		t.Fatalf("c1: expected=\"%s\" got=\"%s\"", expected, v1)
	}

	v2, err := kc.NamespaceOfContext("c2")
	if err != nil {
		t.Fatal("expected err")
	}
	if expected := `c2n1`; v2 != expected {
		t.Fatalf("c2: expected=\"%s\" got=\"%s\"", expected, v2)
	}
}

func TestKubeconfig_SetNamespace(t *testing.T) {
	l := WithMockKubeconfigLoader(testutil.KC().
		WithCtxs(
			testutil.Ctx("c1"),
			testutil.Ctx("c2").Ns("c2n1")).ToYAML(t))
	kc := new(Kubeconfig).WithLoader(l)
	if err := kc.Parse(); err != nil {
		t.Fatal(err)
	}

	if err := kc.SetNamespace("c3", "foo"); err == nil {
		t.Fatalf("expected error for non-existing ctx")
	}

	if err := kc.SetNamespace("c1", "c1n1"); err != nil {
		t.Fatal(err)
	}
	if err := kc.SetNamespace("c2", "c2n2"); err != nil {
		t.Fatal(err)
	}
	if err := kc.Save(); err != nil {
		t.Fatal(err)
	}

	expected := testutil.KC().WithCtxs(
		testutil.Ctx("c1").Ns("c1n1"),
		testutil.Ctx("c2").Ns("c2n2")).ToYAML(t)
	if diff := cmp.Diff(l.Output(), expected); diff != "" {
		t.Fatal(diff)
	}
}


================================================
FILE: internal/printer/color.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package printer

import (
	"os"

	"github.com/fatih/color"

	"github.com/ahmetb/kubectx/internal/env"
)

var (
	ActiveItemColor = color.New(color.FgGreen, color.Bold)
)

func init() {
	EnableOrDisableColor(ActiveItemColor)
}

// useColors returns true if colors are force-enabled,
// false if colors are disabled, or nil for default behavior
// which is determined based on factors like if stdout is tty.
func useColors() *bool {
	tr, fa := true, false
	if os.Getenv(env.EnvForceColor) != "" {
		return &tr
	} else if os.Getenv(env.EnvNoColor) != "" {
		return &fa
	}
	return nil
}

// EnableOrDisableColor determines if color should be force-enabled or force-disabled
// or left untouched based on environment configuration.
func EnableOrDisableColor(c *color.Color) {
	if v := useColors(); v != nil && *v {
		c.EnableColor()
	} else if v != nil && !*v {
		c.DisableColor()
	}
}


================================================
FILE: internal/printer/color_test.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package printer

import (
	"testing"

	"github.com/google/go-cmp/cmp"
)

var (
	tr, fa = true, false
)

func Test_useColors_forceColors(t *testing.T) {
	t.Setenv("_KUBECTX_FORCE_COLOR", "1")
	t.Setenv("NO_COLOR", "1")

	if v := useColors(); !cmp.Equal(v, &tr) {
		t.Fatalf("expected useColors() = true; got = %v", v)
	}
}

func Test_useColors_disableColors(t *testing.T) {
	t.Setenv("NO_COLOR", "1")

	if v := useColors(); !cmp.Equal(v, &fa) {
		t.Fatalf("expected useColors() = false; got = %v", v)
	}
}

func Test_useColors_default(t *testing.T) {
	t.Setenv("NO_COLOR", "")
	t.Setenv("_KUBECTX_FORCE_COLOR", "")

	if v := useColors(); v != nil {
		t.Fatalf("expected useColors() = nil; got=%v", *v)
	}
}


================================================
FILE: internal/printer/printer.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package printer

import (
	"fmt"
	"io"

	"github.com/fatih/color"
)

var (
	ErrorColor   = color.New(color.FgRed, color.Bold)
	WarningColor = color.New(color.FgYellow, color.Bold)
	SuccessColor = color.New(color.FgGreen)
)

func init() {
	colors := useColors()
	if colors == nil {
		return
	}
	if *colors {
		ErrorColor.EnableColor()
		WarningColor.EnableColor()
		SuccessColor.EnableColor()
	} else {
		ErrorColor.DisableColor()
		WarningColor.DisableColor()
		SuccessColor.DisableColor()
	}
}

func Error(w io.Writer, format string, args ...any) error {
	_, err := io.WriteString(w, ErrorColor.Sprint("error: ")+fmt.Sprintf(format, args...)+"\n")
	return err
}

func Warning(w io.Writer, format string, args ...any) error {
	_, err := io.WriteString(w, WarningColor.Sprint("warning: ")+fmt.Sprintf(format, args...)+"\n")
	return err
}

func Success(w io.Writer, format string, args ...any) error {
	_, err := io.WriteString(w, SuccessColor.Sprint("✔ ")+fmt.Sprintf(format, args...)+"\n")
	return err
}


================================================
FILE: internal/testutil/kubeconfigbuilder.go
================================================
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package testutil

import (
	"strings"
	"testing"

	"sigs.k8s.io/kustomize/kyaml/yaml"
)

type Context struct {
	Name    string `yaml:"name,omitempty"`
	Context struct {
		Namespace string `yaml:"namespace,omitempty"`
	} `yaml:"context,omitempty"`
}

func Ctx(name string) *Context           { return &Context{Name: name} }
func (c *Context) Ns(ns string) *Context { c.Context.Namespace = ns; return c }

type Kubeconfig map[string]any

func KC() *Kubeconfig {
	return &Kubeconfig{
		"apiVersion": "v1",
		"kind":       "Config"}
}

func (k *Kubeconfig) Set(key string, v any) *Kubeconfig   { (*k)[key] = v; return k }
func (k *Kubeconfig) WithCurrentCtx(s string) *Kubeconfig { (*k)["current-context"] = s; return k }
func (k *Kubeconfig) WithCtxs(c ...*Context) *Kubeconfig  { (*k)["contexts"] = c; return k }

func (k *Kubeconfig) ToYAML(t *testing.T) string {
	t.Helper()
	var v strings.Builder
	enc := yaml.NewEncoder(&v)
	enc.SetIndent(0)
	if err := enc.Encode(*k); err != nil {
		t.Fatalf("failed to encode mock kubeconfig: %v", err)
	}
	return v.String()
}


================================================
FILE: kubectx
================================================
#!/usr/bin/env bash
#
# kubectx(1) is a utility to manage and switch between kubectl contexts.

# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[[ -n $DEBUG ]] && set -x

set -eou pipefail
IFS=$'\n\t'

SELF_CMD="$0"

KUBECTX="${XDG_CACHE_HOME:-$HOME/.kube}/kubectx"

usage() {
  local SELF
  SELF="kubectx"
  if [[ "$(basename "$0")" == kubectl-* ]]; then # invoked as plugin
    SELF="kubectl ctx"
  fi

  cat <<EOF
Manage and switch between kubectl contexts.

USAGE:
  $SELF                       : list the contexts
  $SELF <NAME>                : switch to context <NAME>
  $SELF -                     : switch to the previous context
  $SELF -c, --current         : show the current context name
  $SELF <NEW_NAME>=<NAME>     : rename context <NAME> to <NEW_NAME>
  $SELF <NEW_NAME>=.          : rename current-context to <NEW_NAME>
  $SELF -d <NAME> [<NAME...>] : delete context <NAME> ('.' for current-context)
                                  (this command won't delete the user/cluster entry
                                  that is used by the context)
  $SELF -u, --unset           : unset the current context

  $SELF -h,--help             : show this message

  (This executable is the legacy bash-based implementation, consider upgrading to Go-based implementation.)
EOF
}

exit_err() {
   echo >&2 "${1}"
   exit 1
}

current_context() {
  $KUBECTL config view -o=jsonpath='{.current-context}'
}

get_contexts() {
  $KUBECTL config get-contexts -o=name | sort -n
}

list_contexts() {
  set -u pipefail
  local cur ctx_list
  cur="$(current_context)" || exit_err "error getting current context"
  ctx_list=$(get_contexts) || exit_err "error getting context list"

  local yellow darkbg normal
  yellow=$(tput setaf 3 || true)
  darkbg=$(tput setab 0 || true)
  normal=$(tput sgr0 || true)

  local cur_ctx_fg cur_ctx_bg
  cur_ctx_fg=${KUBECTX_CURRENT_FGCOLOR:-$yellow}
  cur_ctx_bg=${KUBECTX_CURRENT_BGCOLOR:-$darkbg}

  for c in $ctx_list; do
  if [[ -n "${_KUBECTX_FORCE_COLOR:-}" || \
       -t 1 && -z "${NO_COLOR:-}" ]]; then
    # colored output mode
    if [[ "${c}" = "${cur}" ]]; then
      echo "${cur_ctx_bg}${cur_ctx_fg}${c}${normal}"
    else
      echo "${c}"
    fi
  else
    echo "${c}"
  fi
  done
}

read_context() {
  if [[ -f "${KUBECTX}" ]]; then
    cat "${KUBECTX}"
  fi
}

save_context() {
  local saved
  saved="$(read_context)"

  if [[ "${saved}" != "${1}" ]]; then
    printf %s "${1}" > "${KUBECTX}"
  fi
}

switch_context() {
  $KUBECTL config use-context "${1}"
}

choose_context_interactive() {
  local choice
  choice="$(_KUBECTX_FORCE_COLOR=1 \
    FZF_DEFAULT_COMMAND="${SELF_CMD}" \
    fzf --ansi --no-preview || true)"
  if [[ -z "${choice}" ]]; then
    echo 2>&1 "error: you did not choose any of the options"
    exit 1
  else
    set_context "${choice}"
  fi
}

set_context() {
  local prev
  prev="$(current_context)" || exit_err "error getting current context"

  switch_context "${1}"

  if [[ "${prev}" != "${1}" ]]; then
    save_context "${prev}"
  fi
}

swap_context() {
  local ctx
  ctx="$(read_context)"
  if [[ -z "${ctx}" ]]; then
    echo "error: No previous context found." >&2
    exit 1
  fi
  set_context "${ctx}"
}

context_exists() {
  grep -q ^"${1}"\$ <($KUBECTL config get-contexts -o=name)
}

rename_context() {
  local old_name="${1}"
  local new_name="${2}"

  if [[ "${old_name}" == "." ]]; then
    old_name="$(current_context)"
  fi

  if ! context_exists "${old_name}"; then
    echo "error: Context \"${old_name}\" not found, can't rename it." >&2
    exit 1
  fi

  if context_exists "${new_name}"; then
    echo "Context \"${new_name}\" exists, deleting..." >&2
    $KUBECTL config delete-context "${new_name}" 1>/dev/null 2>&1
  fi

  $KUBECTL config rename-context "${old_name}" "${new_name}"
}

delete_contexts() {
  for i in "${@}"; do
    delete_context "${i}"
  done
}

delete_context() {
  local ctx
  ctx="${1}"
  if [[ "${ctx}" == "." ]]; then
    ctx="$(current_context)" || exit_err "error getting current context"
  fi
  echo "Deleting context \"${ctx}\"..." >&2
  $KUBECTL config delete-context "${ctx}"
}

unset_context() {
  echo "Unsetting current context." >&2
  $KUBECTL config unset current-context
}

main() {
  if [[ -z "${KUBECTL:-}" ]]; then
    if hash kubectl 2>/dev/null; then
      KUBECTL=kubectl
    elif hash kubectl.exe  2>/dev/null; then
      KUBECTL=kubectl.exe
    else
      echo >&2 "kubectl is not installed"
      exit 1
    fi
  fi

  if [[ "$#" -eq 0 ]]; then
    if [[ -t 1 &&  -z "${KUBECTX_IGNORE_FZF:-}" && "$(type fzf &>/dev/null; echo $?)" -eq 0 ]]; then
      choose_context_interactive
    else
      list_contexts
    fi
  elif [[ "${1}" == "-d" ]]; then
    if [[ "$#" -lt 2 ]]; then
      echo "error: missing context NAME" >&2
      usage
      exit 1
    fi
    delete_contexts "${@:2}"
  elif [[ "$#" -gt 1 ]]; then
    echo "error: too many arguments" >&2
    usage
    exit 1
  elif [[ "$#" -eq 1 ]]; then
    if [[ "${1}" == "-" ]]; then
      swap_context
    elif [[ "${1}" == '-c' || "${1}" == '--current' ]]; then
      # we don't call current_context here for two reasons:
      # - it does not fail when current-context property is not set
      # - it does not return a trailing newline
      $KUBECTL config current-context
    elif [[ "${1}" == '-u' || "${1}" == '--unset' ]]; then
      unset_context
    elif [[ "${1}" == '-h' || "${1}" == '--help' ]]; then
      usage
    elif [[ "${1}" =~ ^-(.*) ]]; then
      echo "error: unrecognized flag \"${1}\"" >&2
      usage
      exit 1
    elif [[ "${1}" =~ (.+)=(.+) ]]; then
      rename_context "${BASH_REMATCH[2]}" "${BASH_REMATCH[1]}"
    else
      set_context "${1}"
    fi
  else
    usage
    exit 1
  fi
}

main "$@"


================================================
FILE: kubens
================================================
#!/usr/bin/env bash
#
# kubens(1) is a utility to switch between Kubernetes namespaces.

# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[[ -n $DEBUG ]] && set -x

set -eou pipefail
IFS=$'\n\t'

SELF_CMD="$0"

KUBENS_DIR="${XDG_CACHE_HOME:-$HOME/.kube}/kubens"

usage() {
  local SELF
  SELF="kubens"
  if [[ "$(basename "$0")" == kubectl-* ]]; then # invoked as plugin
    SELF="kubectl ns"
  fi

  cat <<EOF
Switch between Kubernetes namespaces.

USAGE:
  $SELF                    : list the namespaces in the current context
  $SELF <NAME>             : change the active namespace of current context
  $SELF -                  : switch to the previous namespace in this context
  $SELF -c, --current      : show the current namespace
  $SELF -h,--help          : show this message

  (This executable is the legacy bash-based implementation, consider upgrading to Go-based implementation.)
EOF
}

exit_err() {
   echo >&2 "${1}"
   exit 1
}

current_namespace() {
  local cur_ctx

  cur_ctx="$(current_context)" || exit_err "error getting current context"
  ns="$($KUBECTL config view -o=jsonpath="{.contexts[?(@.name==\"${cur_ctx}\")].context.namespace}")" \
     || exit_err "error getting current namespace"

  if [[ -z "${ns}" ]]; then
    echo "default"
  else
    echo "${ns}"
  fi
}

current_context() {
  $KUBECTL config current-context
}

get_namespaces() {
  $KUBECTL get namespaces -o=jsonpath='{range .items[*].metadata.name}{@}{"\n"}{end}'
}

escape_context_name() {
  echo "${1//\//-}"
}

namespace_file() {
  local ctx

  ctx="$(escape_context_name "${1}")"
  echo "${KUBENS_DIR}/${ctx}"
}

read_namespace() {
  local f
  f="$(namespace_file "${1}")"
  [[ -f "${f}" ]] && cat "${f}"
  return 0
}

save_namespace() {
  mkdir -p "${KUBENS_DIR}"
  local f saved
  f="$(namespace_file "${1}")"
  saved="$(read_namespace "${1}")"

  if [[ "${saved}" != "${2}" ]]; then
    printf %s "${2}" > "${f}"
  fi
}

switch_namespace() {
  local ctx="${1}"
  $KUBECTL config set-context "${ctx}" --namespace="${2}"
  echo "Active namespace is \"${2}\".">&2
}

choose_namespace_interactive() {
  # directly calling kubens via fzf might fail with a cryptic error like
  # "$FZF_DEFAULT_COMMAND failed", so try to see if we can list namespaces
  # locally first
  if [[ -z "$(list_namespaces)" ]]; then
    echo >&2 "error: could not list namespaces (is the cluster accessible?)"
    exit 1
  fi

  local choice
  choice="$(_KUBECTX_FORCE_COLOR=1 \
    FZF_DEFAULT_COMMAND="${SELF_CMD}" \
    fzf --ansi --no-preview || true)"
  if [[ -z "${choice}" ]]; then
    echo 2>&1 "error: you did not choose any of the options"
    exit 1
  else
    set_namespace "${choice}"
  fi
}

set_namespace() {
  local ctx prev
  ctx="$(current_context)" || exit_err "error getting current context"
  prev="$(current_namespace)" || exit_error "error getting current namespace"

  if grep -q ^"${1}"\$ <(get_namespaces); then
    switch_namespace "${ctx}" "${1}"

    if [[ "${prev}" != "${1}" ]]; then
      save_namespace "${ctx}" "${prev}"
    fi
  else
    echo "error: no namespace exists with name \"${1}\".">&2
    exit 1
  fi
}

list_namespaces() {
  local yellow darkbg normal
  yellow=$(tput setaf 3 || true)
  darkbg=$(tput setab 0 || true)
  normal=$(tput sgr0 || true)

  local cur_ctx_fg cur_ctx_bg
  cur_ctx_fg=${KUBECTX_CURRENT_FGCOLOR:-$yellow}
  cur_ctx_bg=${KUBECTX_CURRENT_BGCOLOR:-$darkbg}

  local cur ns_list
  cur="$(current_namespace)" || exit_err "error getting current namespace"
  ns_list=$(get_namespaces) || exit_err "error getting namespace list"

  for c in $ns_list; do
  if [[ -n "${_KUBECTX_FORCE_COLOR:-}" || \
       -t 1 && -z "${NO_COLOR:-}" ]]; then
    # colored output mode
    if [[ "${c}" = "${cur}" ]]; then
      echo "${cur_ctx_bg}${cur_ctx_fg}${c}${normal}"
    else
      echo "${c}"
    fi
  else
    echo "${c}"
  fi
  done
}

swap_namespace() {
  local ctx ns
  ctx="$(current_context)" || exit_err "error getting current context"
  ns="$(read_namespace "${ctx}")"
  if [[ -z "${ns}" ]]; then
    echo "error: No previous namespace found for current context." >&2
    exit 1
  fi
  set_namespace "${ns}"
}

main() {
  if [[ -z "${KUBECTL:-}" ]]; then
    if hash kubectl 2>/dev/null; then
      KUBECTL=kubectl
    elif hash kubectl.exe  2>/dev/null; then
      KUBECTL=kubectl.exe
    else
      echo >&2 "kubectl is not installed"
      exit 1
    fi
  fi

  if [[ "$#" -eq 0 ]]; then
    if [[ -t 1 && -z ${KUBECTX_IGNORE_FZF:-} && "$(type fzf &>/dev/null; echo $?)" -eq 0 ]]; then
      choose_namespace_interactive
    else
      list_namespaces
    fi
  elif [[ "$#" -eq 1 ]]; then
    if [[ "${1}" == '-h' || "${1}" == '--help' ]]; then
      usage
    elif [[ "${1}" == "-" ]]; then
      swap_namespace
    elif [[ "${1}" == '-c' || "${1}" == '--current' ]]; then
      current_namespace
    elif [[ "${1}" =~ ^-(.*) ]]; then
      echo "error: unrecognized flag \"${1}\"" >&2
      usage
      exit 1
    elif [[ "${1}" =~ (.+)=(.+) ]]; then
      alias_context "${BASH_REMATCH[2]}" "${BASH_REMATCH[1]}"
    else
      set_namespace "${1}"
    fi
  else
    echo "error: too many flags" >&2
    usage
    exit 1
  fi
}

main "$@"


================================================
FILE: test/common.bash
================================================
#!/usr/bin/env bats

# bats setup function
setup() {
  TEMP_HOME="$(mktemp -d)"
  export TEMP_HOME
  export HOME=$TEMP_HOME
  export KUBECONFIG="${TEMP_HOME}/config"
}

# bats teardown function
teardown() {
  rm -rf "$TEMP_HOME"
}

use_config() {
  cp "$BATS_TEST_DIRNAME/testdata/$1" $KUBECONFIG
}

# wrappers around "kubectl config" command

get_namespace() {
  kubectl config view -o=jsonpath="{.contexts[?(@.name==\"$(get_context)\")].context.namespace}"
}

get_context() {
  kubectl config current-context
}

switch_context() {
  kubectl config use-context "${1}"
}


================================================
FILE: test/kubectx.bats
================================================
#!/usr/bin/env bats

COMMAND="${COMMAND:-$BATS_TEST_DIRNAME/../kubectx}"

load common

@test "--help should not fail" {
  run ${COMMAND} --help
  echo "$output"
  [ "$status" -eq 0 ]
}

@test "-h should not fail" {
  run ${COMMAND} -h
  echo "$output"
  [ "$status" -eq 0 ]
}

@test "switch to previous context when no one exists" {
  use_config config1

  run ${COMMAND} -
  echo "$output"
  [ "$status" -eq 1 ]
  [[ $output = *"no previous context found" ]]
}

@test "list contexts when no kubeconfig exists" {
  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ "$output" = "warning: kubeconfig file not found" ]]
}

@test "get one context and list contexts" {
  use_config config1

  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ "$output" = "user1@cluster1" ]]
}

@test "get two contexts and list contexts" {
  use_config config2

  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ "$output" = *"user1@cluster1"* ]]
  [[ "$output" = *"user2@cluster1"* ]]
}

@test "get two contexts and select contexts" {
  use_config config2

  run ${COMMAND} user1@cluster1
  echo "$output"
  [ "$status" -eq 0 ]
  echo "$(get_context)"
  [[ "$(get_context)" = "user1@cluster1" ]]

  run ${COMMAND} user2@cluster1
  echo "$output"
  [ "$status" -eq 0 ]
  echo "$(get_context)"
  [[ "$(get_context)" = "user2@cluster1" ]]
}

@test "get two contexts and switch between contexts" {
  use_config config2

  run ${COMMAND} user1@cluster1
  echo "$output"
  [ "$status" -eq 0 ]
  echo "$(get_context)"
  [[ "$(get_context)" = "user1@cluster1" ]]

  run ${COMMAND} user2@cluster1
  echo "$output"
  [ "$status" -eq 0 ]
  echo "$(get_context)"
  [[ "$(get_context)" = "user2@cluster1" ]]

  run ${COMMAND} -
  echo "$output"
  [ "$status" -eq 0 ]
  echo "$(get_context)"
  [[ "$(get_context)" = "user1@cluster1" ]]

  run ${COMMAND} -
  echo "$output"
  [ "$status" -eq 0 ]
  echo "$(get_context)"
  [[ "$(get_context)" = "user2@cluster1" ]]
}

@test "get one context and switch to non existent context" {
  use_config config1

  run ${COMMAND} "unknown-context"
  echo "$output"
  [ "$status" -eq 1 ]
}

@test "-c/--current fails when no context set" {
  use_config config1

  run "${COMMAND}" -c
  echo "$output"
  [ $status -eq 1 ]
  run "${COMMAND}" --current
  echo "$output"
  [ $status -eq 1 ]
}

@test "-c/--current prints the current context" {
  use_config config1

  run "${COMMAND}" user1@cluster1
  [ $status -eq 0 ]

  run "${COMMAND}" -c
  echo "$output"
  [ $status -eq 0 ]
  [[ "$output" = "user1@cluster1" ]]
  run "${COMMAND}" --current
  echo "$output"
  [ $status -eq 0 ]
  [[ "$output" = "user1@cluster1" ]]
}

@test "rename context" {
  use_config config2

  run ${COMMAND} "new-context=user1@cluster1"
  echo "$output"
  [ "$status" -eq 0 ]

  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ ! "$output" = *"user1@cluster1"* ]]
  [[ "$output" = *"new-context"* ]]
  [[ "$output" = *"user2@cluster1"* ]]
}

@test "rename current context" {
  use_config config2

  run ${COMMAND} user2@cluster1
  echo "$output"
  [ "$status" -eq 0 ]

  run ${COMMAND} new-context=.
  echo "$output"
  [ "$status" -eq 0 ]

  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ ! "$output" = *"user2@cluster1"* ]]
  [[ "$output" = *"user1@cluster1"* ]]
  [[ "$output" = *"new-context"* ]]
}

@test "delete context" {
  use_config config2

  run ${COMMAND} -d "user1@cluster1"
  echo "$output"
  [ "$status" -eq 0 ]

  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ ! "$output" = "user1@cluster1" ]]
  [[ "$output" = "user2@cluster1" ]]
}

@test "delete current context" {
  use_config config2

  run ${COMMAND} user2@cluster1
  echo "$output"
  [ "$status" -eq 0 ]

  run ${COMMAND} -d .
  echo "$output"
  [ "$status" -eq 0 ]

  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ ! "$output" = "user2@cluster1" ]]
  [[ "$output" = "user1@cluster1" ]]
}

@test "delete non existent context" {
  use_config config1

  run ${COMMAND} -d "unknown-context"
  echo "$output"
  [ "$status" -eq 1 ]
}

@test "delete several contexts" {
  use_config config2

  run ${COMMAND} -d "user1@cluster1" "user2@cluster1"
  echo "$output"
  [ "$status" -eq 0 ]

  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ "$output" = "" ]]
}

@test "delete several contexts including a non existent one" {
  use_config config2

  run ${COMMAND} -d "user1@cluster1" "non-existent" "user2@cluster1"
  echo "$output"
  [ "$status" -eq 1 ]

  run ${COMMAND}
  echo "$output"
  [ "$status" -eq 0 ]
  [[ "$output" = "user2@cluster1" ]]
}

@test "unset selected context" {
  use_config config2

  run ${COMMAND} user1@cluster1
  [ "$status" -eq 0 ]

  run ${COMMAND} -u
  [ "$status" -eq 0 ]

  run ${COMMAND} -c
  [ "$status" -ne 0 ]
}


================================================
FILE: test/kubens.bats
================================================
#!/usr/bin/env bats

COMMAND="${COMMAND:-$BATS_TEST_DIRNAME/../kubens}"

# TODO(ahmetb) remove this after bash implementations are deleted
export KUBECTL="$BATS_TEST_DIRNAME/../test/mock-kubectl"

# short-circuit namespace querying in kubens go implementation
export _MOCK_NAMESPACES=1

load common

@test "--help should not fail" {
  run ${COMMAND} --help
  echo "$output">&2
  [[ "$status" -eq 0 ]]
}

@test "-h should not fail" {
  run ${COMMAND} -h
  echo "$output">&2
  [[ "$status" -eq 0 ]]
}

@test "list namespaces when no kubeconfig exists" {
  run ${COMMAND}
  echo "$output"
  [[ "$status" -eq 1 ]]
}

@test "list namespaces" {
  use_config config1
  switch_context user1@cluster1

  run ${COMMAND}
  echo "$output"
  [[ "$status" -eq 0 ]]
  [[ "$output" = *"ns1"* ]]
  [[ "$output" = *"ns2"* ]]
}

@test "switch to existing namespace" {
  use_config config1
  switch_context user1@cluster1

  run ${COMMAND} "ns1"
  echo "$output"
  [[ "$status" -eq 0 ]]
  [[ "$output" = *'Active namespace is "ns1"'* ]]
}

@test "switch to non-existing namespace" {
  use_config config1
  switch_context user1@cluster1

  run ${COMMAND} "unknown-namespace"
  echo "$output"
  [[ "$status" -eq 1 ]]
  [[ "$output" = *'no namespace exists with name "unknown-namespace"'* ]]
}

@test "switch between namespaces" {
  use_config config1
  switch_context user1@cluster1

  run ${COMMAND} ns1
  echo "$output"
  [[ "$status" -eq 0 ]]
  echo "$(get_namespace)"
  [[ "$(get_namespace)" = "ns1" ]]

  run ${COMMAND} ns2
  echo "$output"
  [[ "$status" -eq 0 ]]
  echo "$(get_namespace)"
  [[ "$(get_namespace)" = "ns2" ]]

  run ${COMMAND} -
  echo "$output"
  [[ "$status" -eq 0 ]]
  echo "$(get_namespace)"
  [[ "$(get_namespace)" = "ns1" ]]

  run ${COMMAND} -
  echo "$output"
  [[ "$status" -eq 0 ]]
  echo "$(get_namespace)"
  [[ "$(get_namespace)" = "ns2" ]]
}

@test "switch to previous namespace when none exists" {
  use_config config1
  switch_context user1@cluster1

  run ${COMMAND} -
  echo "$output"
  [[ "$status" -eq 1 ]]
  [[ "$output" = *"No previous namespace found for current context"* ]]
}

@test "switch to namespace when current context is empty" {
  use_config config1

  run ${COMMAND} -
  echo "$output"
  [[ "$status" -eq 1 ]]
  [[ "$output" = *"current-context is not set"* ]]
}

@test "-c/--current works when no namespace is set on context" {
  use_config config1
  switch_context user1@cluster1

  run ${COMMAND} "-c"
  echo "$output"
  [[ "$status" -eq 0 ]]
  [[ "$output" = "default" ]]
  run ${COMMAND} "--current"
  echo "$output"
  [[ "$status" -eq 0 ]]
  [[ "$output" = "default" ]]
}

@test "-c/--current prints the namespace after it is set" {
  use_config config1
  switch_context user1@cluster1
  ${COMMAND} ns1

  run ${COMMAND} "-c"
  echo "$output"
  [[ "$status" -eq 0 ]]
  [[ "$output" = "ns1" ]]
  run ${COMMAND} "--current"
  echo "$output"
  [[ "$status" -eq 0 ]]
  [[ "$output" = "ns1" ]]
}

@test "-c/--current fails when current context is not set" {
  use_config config1
  run ${COMMAND} -c
  echo "$output"
  [[ "$status" -eq 1 ]]

  run ${COMMAND} --current
  echo "$output"
  [[ "$status" -eq 1 ]]
}


================================================
FILE: test/mock-kubectl
================================================
#!/usr/bin/env bash

[[ -n $DEBUG ]] && set -x

set -eou pipefail

if [[ $@ == *'get namespaces'* ]]; then
  echo "ns1"
  echo "ns2"
else
  kubectl $@
fi


================================================
FILE: test/testdata/config1
================================================
# config with one context

apiVersion: v1
clusters:
- cluster:
    server: ""
  name: cluster1
contexts:
- context:
    cluster: cluster1
    user: user1
  name: user1@cluster1
current-context: ""
kind: Config
preferences: {}
users:
- name: user1
  user: {}


================================================
FILE: test/testdata/config2
================================================
# config with two contexts

apiVersion: v1
clusters:
- cluster:
    server: ""
  name: cluster1
contexts:
- context:
    cluster: cluster1
    user: user1
  name: user1@cluster1
- context:
    cluster: cluster1
    user: user2
  name: user2@cluster1
current-context: ""
kind: Config
preferences: {}
users:
- name: user1
  user: {}
- name: user2
  user: {}
Download .txt
gitextract_5i38l93y/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── bash-frozen.yml
│       ├── ci.yml
│       ├── dependabot.yml
│       └── release.yml
├── .goreleaser.yml
├── .krew/
│   ├── ctx.yaml
│   └── ns.yaml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cmd/
│   ├── kubectx/
│   │   ├── current.go
│   │   ├── delete.go
│   │   ├── env.go
│   │   ├── flags.go
│   │   ├── flags_test.go
│   │   ├── fzf.go
│   │   ├── help.go
│   │   ├── help_test.go
│   │   ├── isolated_shell_guard.go
│   │   ├── list.go
│   │   ├── main.go
│   │   ├── rename.go
│   │   ├── rename_test.go
│   │   ├── shell.go
│   │   ├── shell_test.go
│   │   ├── state.go
│   │   ├── state_test.go
│   │   ├── switch.go
│   │   ├── unset.go
│   │   └── version.go
│   └── kubens/
│       ├── current.go
│       ├── flags.go
│       ├── flags_test.go
│       ├── fzf.go
│       ├── help.go
│       ├── list.go
│       ├── main.go
│       ├── statefile.go
│       ├── statefile_test.go
│       ├── switch.go
│       ├── unset.go
│       └── version.go
├── completion/
│   ├── _kubectx.zsh
│   ├── _kubens.zsh
│   ├── kubectx.bash
│   ├── kubectx.fish
│   ├── kubens.bash
│   └── kubens.fish
├── go.mod
├── go.sum
├── internal/
│   ├── cmdutil/
│   │   ├── deprecated.go
│   │   ├── deprecated_test.go
│   │   ├── interactive.go
│   │   ├── util.go
│   │   └── util_test.go
│   ├── env/
│   │   └── constants.go
│   ├── kubeconfig/
│   │   ├── contextmodify.go
│   │   ├── contextmodify_test.go
│   │   ├── contexts.go
│   │   ├── contexts_test.go
│   │   ├── currentcontext.go
│   │   ├── currentcontext_test.go
│   │   ├── helper_test.go
│   │   ├── kubeconfig.go
│   │   ├── kubeconfig_test.go
│   │   ├── kubeconfigloader.go
│   │   ├── kubeconfigloader_test.go
│   │   ├── namespace.go
│   │   └── namespace_test.go
│   ├── printer/
│   │   ├── color.go
│   │   ├── color_test.go
│   │   └── printer.go
│   └── testutil/
│       └── kubeconfigbuilder.go
├── kubectx
├── kubens
└── test/
    ├── common.bash
    ├── kubectx.bats
    ├── kubens.bats
    ├── mock-kubectl
    └── testdata/
        ├── config1
        └── config2
Download .txt
SYMBOL INDEX (186 symbols across 54 files)

FILE: cmd/kubectx/current.go
  type CurrentOp (line 26) | type CurrentOp struct
    method Run (line 28) | func (_op CurrentOp) Run(stdout, _ io.Writer) error {

FILE: cmd/kubectx/delete.go
  type DeleteOp (line 27) | type DeleteOp struct
    method Run (line 32) | func (op DeleteOp) Run(_, stderr io.Writer) error {
  function deleteContext (line 54) | func deleteContext(name string) (deleteName string, wasActiveContext boo...

FILE: cmd/kubectx/flags.go
  type UnsupportedOp (line 27) | type UnsupportedOp struct
    method Run (line 29) | func (op UnsupportedOp) Run(_, _ io.Writer) error {
  function parseArgs (line 35) | func parseArgs(argv []string) Op {

FILE: cmd/kubectx/flags_test.go
  function Test_parseArgs_new (line 24) | func Test_parseArgs_new(t *testing.T) {

FILE: cmd/kubectx/fzf.go
  type InteractiveSwitchOp (line 32) | type InteractiveSwitchOp struct
    method Run (line 40) | func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error {
  type InteractiveDeleteOp (line 36) | type InteractiveDeleteOp struct
    method Run (line 90) | func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error {

FILE: cmd/kubectx/help.go
  type HelpOp (line 26) | type HelpOp struct
    method Run (line 28) | func (_ HelpOp) Run(stdout, _ io.Writer) error {
  function printUsage (line 32) | func printUsage(out io.Writer) error {
  function selfName (line 58) | func selfName() string {

FILE: cmd/kubectx/help_test.go
  function TestPrintHelp (line 23) | func TestPrintHelp(t *testing.T) {

FILE: cmd/kubectx/isolated_shell_guard.go
  function checkIsolatedMode (line 11) | func checkIsolatedMode() error {

FILE: cmd/kubectx/list.go
  type ListOp (line 29) | type ListOp struct
    method Run (line 31) | func (_ ListOp) Run(stdout, stderr io.Writer) error {

FILE: cmd/kubectx/main.go
  type Op (line 28) | type Op interface
  function main (line 32) | func main() {

FILE: cmd/kubectx/rename.go
  type RenameOp (line 27) | type RenameOp struct
    method Run (line 45) | func (op RenameOp) Run(_, stderr io.Writer) error {
  function parseRenameSyntax (line 34) | func parseRenameSyntax(v string) (string, string, bool) {

FILE: cmd/kubectx/rename_test.go
  function Test_parseRenameSyntax (line 23) | func Test_parseRenameSyntax(t *testing.T) {

FILE: cmd/kubectx/shell.go
  type ShellOp (line 18) | type ShellOp struct
    method Run (line 22) | func (op ShellOp) Run(_, stderr io.Writer) error {
  function resolveKubectl (line 96) | func resolveKubectl() (string, error) {
  function extractMinimalKubeconfig (line 107) | func extractMinimalKubeconfig(kubectlPath, contextName string) ([]byte, ...
  function detectShell (line 118) | func detectShell() string {

FILE: cmd/kubectx/shell_test.go
  function Test_detectShell_unix (line 11) | func Test_detectShell_unix(t *testing.T) {
  function Test_ShellOp_blockedWhenNested (line 45) | func Test_ShellOp_blockedWhenNested(t *testing.T) {
  function Test_resolveKubectl_envVar (line 68) | func Test_resolveKubectl_envVar(t *testing.T) {
  function Test_resolveKubectl_inPath (line 79) | func Test_resolveKubectl_inPath(t *testing.T) {
  function Test_checkIsolatedMode_notSet (line 92) | func Test_checkIsolatedMode_notSet(t *testing.T) {
  function Test_checkIsolatedMode_set (line 101) | func Test_checkIsolatedMode_set(t *testing.T) {

FILE: cmd/kubectx/state.go
  function kubectxPrevCtxFile (line 26) | func kubectxPrevCtxFile() (string, error) {
  function readLastContext (line 36) | func readLastContext(path string) (string, error) {
  function writeLastContext (line 46) | func writeLastContext(path, value string) error {

FILE: cmd/kubectx/state_test.go
  function Test_readLastContext_nonExistingFile (line 23) | func Test_readLastContext_nonExistingFile(t *testing.T) {
  function Test_readLastContext (line 33) | func Test_readLastContext(t *testing.T) {
  function Test_writeLastContext_err (line 49) | func Test_writeLastContext_err(t *testing.T) {
  function Test_writeLastContext (line 57) | func Test_writeLastContext(t *testing.T) {
  function Test_kubectxFilePath (line 74) | func Test_kubectxFilePath(t *testing.T) {
  function Test_kubectxFilePath_xdgCacheHome (line 88) | func Test_kubectxFilePath_xdgCacheHome(t *testing.T) {
  function Test_kubectxFilePath_error (line 101) | func Test_kubectxFilePath_error(t *testing.T) {

FILE: cmd/kubectx/switch.go
  type SwitchOp (line 27) | type SwitchOp struct
    method Run (line 31) | func (op SwitchOp) Run(_, stderr io.Writer) error {
  function switchContext (line 52) | func switchContext(name string) (string, error) {
  function swapContext (line 91) | func swapContext() (string, error) {

FILE: cmd/kubectx/unset.go
  type UnsetOp (line 26) | type UnsetOp struct
    method Run (line 28) | func (_ UnsetOp) Run(_, stderr io.Writer) error {

FILE: cmd/kubectx/version.go
  type VersionOp (line 13) | type VersionOp struct
    method Run (line 15) | func (_ VersionOp) Run(stdout, _ io.Writer) error {

FILE: cmd/kubens/current.go
  type CurrentOp (line 25) | type CurrentOp struct
    method Run (line 27) | func (c CurrentOp) Run(stdout, _ io.Writer) error {

FILE: cmd/kubens/flags.go
  type UnsupportedOp (line 28) | type UnsupportedOp struct
    method Run (line 30) | func (op UnsupportedOp) Run(_, _ io.Writer) error {
  function parseArgs (line 36) | func parseArgs(argv []string) Op {
  function getSwitchOp (line 81) | func getSwitchOp(v string, force bool) Op {

FILE: cmd/kubens/flags_test.go
  function Test_parseArgs_new (line 24) | func Test_parseArgs_new(t *testing.T) {

FILE: cmd/kubens/fzf.go
  type InteractiveSwitchOp (line 32) | type InteractiveSwitchOp struct
    method Run (line 37) | func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error {

FILE: cmd/kubens/help.go
  type HelpOp (line 26) | type HelpOp struct
    method Run (line 28) | func (_ HelpOp) Run(stdout, _ io.Writer) error {
  function printUsage (line 32) | func printUsage(out io.Writer) error {
  function selfName (line 54) | func selfName() string {

FILE: cmd/kubens/list.go
  type ListOp (line 34) | type ListOp struct
    method Run (line 36) | func (op ListOp) Run(stdout, stderr io.Writer) error {
  function queryNamespaces (line 70) | func queryNamespaces(kc *kubeconfig.Kubeconfig) ([]string, error) {
  function newKubernetesClientSet (line 104) | func newKubernetesClientSet(kc *kubeconfig.Kubeconfig) (*kubernetes.Clie...

FILE: cmd/kubens/main.go
  type Op (line 28) | type Op interface
  function main (line 32) | func main() {

FILE: cmd/kubens/statefile.go
  type NSFile (line 29) | type NSFile struct
    method path (line 36) | func (f NSFile) path() string {
    method Load (line 46) | func (f NSFile) Load() (string, error) {
    method Save (line 58) | func (f NSFile) Save(value string) error {
  function NewNSFile (line 34) | func NewNSFile(ctx string) NSFile { return NSFile{dir: defaultDir, ctx: ...
  function isWindows (line 67) | func isWindows() bool {

FILE: cmd/kubens/statefile_test.go
  function TestNSFile (line 23) | func TestNSFile(t *testing.T) {
  function TestNSFile_path_windows (line 50) | func TestNSFile_path_windows(t *testing.T) {
  function Test_isWindows (line 59) | func Test_isWindows(t *testing.T) {

FILE: cmd/kubens/switch.go
  type SwitchOp (line 31) | type SwitchOp struct
    method Run (line 36) | func (s SwitchOp) Run(_, stderr io.Writer) error {
  function switchNamespace (line 51) | func switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (...
  function namespaceExists (line 101) | func namespaceExists(kc *kubeconfig.Kubeconfig, ns string) (bool, error) {

FILE: cmd/kubens/unset.go
  type UnsetOp (line 27) | type UnsetOp struct
    method Run (line 29) | func (_ UnsetOp) Run(_, stderr io.Writer) error {
  function clearNamespace (line 44) | func clearNamespace(kc *kubeconfig.Kubeconfig) (string, error) {

FILE: cmd/kubens/version.go
  type VersionOp (line 13) | type VersionOp struct
    method Run (line 15) | func (_ VersionOp) Run(stdout, _ io.Writer) error {

FILE: internal/cmdutil/deprecated.go
  function PrintDeprecatedEnvWarnings (line 24) | func PrintDeprecatedEnvWarnings(out io.Writer, vars []string) {

FILE: internal/cmdutil/deprecated_test.go
  function TestPrintDeprecatedEnvWarnings_noDeprecatedVars (line 23) | func TestPrintDeprecatedEnvWarnings_noDeprecatedVars(t *testing.T) {
  function TestPrintDeprecatedEnvWarnings_bgColors (line 34) | func TestPrintDeprecatedEnvWarnings_bgColors(t *testing.T) {

FILE: internal/cmdutil/interactive.go
  function isTerminal (line 27) | func isTerminal(fd *os.File) bool {
  function fzfInstalled (line 32) | func fzfInstalled() bool {
  function IsInteractiveMode (line 41) | func IsInteractiveMode(stdout *os.File) bool {

FILE: internal/cmdutil/util.go
  function HomeDir (line 23) | func HomeDir() string {
  function CacheDir (line 33) | func CacheDir() string {
  function IsNotFoundErr (line 45) | func IsNotFoundErr(err error) bool {

FILE: internal/cmdutil/util_test.go
  function Test_homeDir (line 22) | func Test_homeDir(t *testing.T) {
  function TestCacheDir (line 77) | func TestCacheDir(t *testing.T) {

FILE: internal/env/constants.go
  constant EnvFZFIgnore (line 20) | EnvFZFIgnore = "KUBECTX_IGNORE_FZF"
  constant EnvNoColor (line 24) | EnvNoColor = `NO_COLOR`
  constant EnvForceColor (line 28) | EnvForceColor = `_KUBECTX_FORCE_COLOR`
  constant EnvDebug (line 31) | EnvDebug = `DEBUG`
  constant EnvIsolatedShell (line 33) | EnvIsolatedShell = "KUBECTX_ISOLATED_SHELL"

FILE: internal/kubeconfig/contextmodify.go
  method DeleteContextEntry (line 23) | func (k *Kubeconfig) DeleteContextEntry(deleteName string) error {
  method ModifyCurrentContext (line 39) | func (k *Kubeconfig) ModifyCurrentContext(name string) error {
  method ModifyContextName (line 46) | func (k *Kubeconfig) ModifyContextName(old, new string) error {

FILE: internal/kubeconfig/contextmodify_test.go
  function TestKubeconfig_DeleteContextEntry_errors (line 25) | func TestKubeconfig_DeleteContextEntry_errors(t *testing.T) {
  function TestKubeconfig_DeleteContextEntry (line 48) | func TestKubeconfig_DeleteContextEntry(t *testing.T) {
  function TestKubeconfig_ModifyCurrentContext_fieldExists (line 74) | func TestKubeconfig_ModifyCurrentContext_fieldExists(t *testing.T) {
  function TestKubeconfig_ModifyCurrentContext_fieldMissing (line 95) | func TestKubeconfig_ModifyCurrentContext_fieldMissing(t *testing.T) {
  function TestKubeconfig_ModifyContextName_noContextsEntryError (line 117) | func TestKubeconfig_ModifyContextName_noContextsEntryError(t *testing.T) {
  function TestKubeconfig_ModifyContextName_contextsEntryNotSequenceError (line 129) | func TestKubeconfig_ModifyContextName_contextsEntryNotSequenceError(t *t...
  function TestKubeconfig_ModifyContextName_noChange (line 142) | func TestKubeconfig_ModifyContextName_noChange(t *testing.T) {
  function TestKubeconfig_ModifyContextName (line 156) | func TestKubeconfig_ModifyContextName(t *testing.T) {

FILE: internal/kubeconfig/contexts.go
  method contextsNode (line 25) | func (k *Kubeconfig) contextsNode() (*yaml.RNode, error) {
  method contextNode (line 38) | func (k *Kubeconfig) contextNode(name string) (*yaml.RNode, error) {
  method ContextNames (line 53) | func (k *Kubeconfig) ContextNames() ([]string, error) {
  method ContextExists (line 68) | func (k *Kubeconfig) ContextExists(name string) (bool, error) {

FILE: internal/kubeconfig/contexts_test.go
  function TestKubeconfig_ContextNames (line 25) | func TestKubeconfig_ContextNames(t *testing.T) {
  function TestKubeconfig_ContextNames_noContextsEntry (line 46) | func TestKubeconfig_ContextNames_noContextsEntry(t *testing.T) {
  function TestKubeconfig_ContextNames_nonArrayContextsEntry (line 62) | func TestKubeconfig_ContextNames_nonArrayContextsEntry(t *testing.T) {
  function TestKubeconfig_CheckContextExists (line 74) | func TestKubeconfig_CheckContextExists(t *testing.T) {

FILE: internal/kubeconfig/currentcontext.go
  method GetCurrentContext (line 25) | func (k *Kubeconfig) GetCurrentContext() (string, error) {
  method UnsetCurrentContext (line 33) | func (k *Kubeconfig) UnsetCurrentContext() error {

FILE: internal/kubeconfig/currentcontext_test.go
  function TestKubeconfig_GetCurrentContext (line 23) | func TestKubeconfig_GetCurrentContext(t *testing.T) {
  function TestKubeconfig_GetCurrentContext_missingField (line 40) | func TestKubeconfig_GetCurrentContext_missingField(t *testing.T) {
  function TestKubeconfig_UnsetCurrentContext (line 57) | func TestKubeconfig_UnsetCurrentContext(t *testing.T) {

FILE: internal/kubeconfig/helper_test.go
  type MockKubeconfigLoader (line 23) | type MockKubeconfigLoader struct
    method Read (line 28) | func (t *MockKubeconfigLoader) Read(p []byte) (n int, err error)  { re...
    method Write (line 29) | func (t *MockKubeconfigLoader) Write(p []byte) (n int, err error) { re...
    method Close (line 30) | func (t *MockKubeconfigLoader) Close() error                      { re...
    method Reset (line 31) | func (t *MockKubeconfigLoader) Reset() error                      { re...
    method Load (line 32) | func (t *MockKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) {
    method Output (line 35) | func (t *MockKubeconfigLoader) Output() string { return t.out.String() }
  function WithMockKubeconfigLoader (line 37) | func WithMockKubeconfigLoader(kubecfg string) *MockKubeconfigLoader {

FILE: internal/kubeconfig/kubeconfig.go
  type ReadWriteResetCloser (line 25) | type ReadWriteResetCloser interface
  type Loader (line 32) | type Loader interface
  type Kubeconfig (line 36) | type Kubeconfig struct
    method WithLoader (line 43) | func (k *Kubeconfig) WithLoader(l Loader) *Kubeconfig {
    method Close (line 48) | func (k *Kubeconfig) Close() error {
    method Parse (line 55) | func (k *Kubeconfig) Parse() error {
    method Bytes (line 76) | func (k *Kubeconfig) Bytes() ([]byte, error) {
    method Save (line 84) | func (k *Kubeconfig) Save() error {

FILE: internal/kubeconfig/kubeconfig_test.go
  function TestParse (line 25) | func TestParse(t *testing.T) {
  function TestSave (line 49) | func TestSave(t *testing.T) {

FILE: internal/kubeconfig/kubeconfigloader.go
  type StandardKubeconfigLoader (line 30) | type StandardKubeconfigLoader struct
    method Load (line 34) | func (*StandardKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) {
  type kubeconfigFile (line 32) | type kubeconfigFile struct
    method Reset (line 52) | func (kf *kubeconfigFile) Reset() error {
  function kubeconfigPath (line 62) | func kubeconfigPath() (string, error) {

FILE: internal/kubeconfig/kubeconfigloader_test.go
  function Test_kubeconfigPath (line 26) | func Test_kubeconfigPath(t *testing.T) {
  function Test_kubeconfigPath_noEnvVars (line 39) | func Test_kubeconfigPath_noEnvVars(t *testing.T) {
  function Test_kubeconfigPath_envOvveride (line 50) | func Test_kubeconfigPath_envOvveride(t *testing.T) {
  function Test_kubeconfigPath_envOvverideDoesNotSupportPathSeparator (line 62) | func Test_kubeconfigPath_envOvverideDoesNotSupportPathSeparator(t *testi...
  function TestStandardKubeconfigLoader_returnsNotFoundErr (line 72) | func TestStandardKubeconfigLoader_returnsNotFoundErr(t *testing.T) {

FILE: internal/kubeconfig/namespace.go
  constant defaultNamespace (line 22) | defaultNamespace = "default"
  method NamespaceOfContext (line 25) | func (k *Kubeconfig) NamespaceOfContext(contextName string) (string, err...
  method SetNamespace (line 37) | func (k *Kubeconfig) SetNamespace(ctxName string, ns string) error {

FILE: internal/kubeconfig/namespace_test.go
  function TestKubeconfig_NamespaceOfContext_ctxNotFound (line 25) | func TestKubeconfig_NamespaceOfContext_ctxNotFound(t *testing.T) {
  function TestKubeconfig_NamespaceOfContext (line 38) | func TestKubeconfig_NamespaceOfContext(t *testing.T) {
  function TestKubeconfig_SetNamespace (line 64) | func TestKubeconfig_SetNamespace(t *testing.T) {

FILE: internal/printer/color.go
  function init (line 29) | func init() {
  function useColors (line 36) | func useColors() *bool {
  function EnableOrDisableColor (line 48) | func EnableOrDisableColor(c *color.Color) {

FILE: internal/printer/color_test.go
  function Test_useColors_forceColors (line 27) | func Test_useColors_forceColors(t *testing.T) {
  function Test_useColors_disableColors (line 36) | func Test_useColors_disableColors(t *testing.T) {
  function Test_useColors_default (line 44) | func Test_useColors_default(t *testing.T) {

FILE: internal/printer/printer.go
  function init (line 30) | func init() {
  function Error (line 46) | func Error(w io.Writer, format string, args ...any) error {
  function Warning (line 51) | func Warning(w io.Writer, format string, args ...any) error {
  function Success (line 56) | func Success(w io.Writer, format string, args ...any) error {

FILE: internal/testutil/kubeconfigbuilder.go
  type Context (line 24) | type Context struct
    method Ns (line 32) | func (c *Context) Ns(ns string) *Context { c.Context.Namespace = ns; r...
  function Ctx (line 31) | func Ctx(name string) *Context           { return &Context{Name: name} }
  type Kubeconfig (line 34) | type Kubeconfig
    method Set (line 42) | func (k *Kubeconfig) Set(key string, v any) *Kubeconfig   { (*k)[key] ...
    method WithCurrentCtx (line 43) | func (k *Kubeconfig) WithCurrentCtx(s string) *Kubeconfig { (*k)["curr...
    method WithCtxs (line 44) | func (k *Kubeconfig) WithCtxs(c ...*Context) *Kubeconfig  { (*k)["cont...
    method ToYAML (line 46) | func (k *Kubeconfig) ToYAML(t *testing.T) string {
  function KC (line 36) | func KC() *Kubeconfig {
Condensed preview — 82 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (193K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 353,
    "preview": "version: 2\nupdates:\n- package-ecosystem: github-actions\n  directory: /\n  schedule:\n    interval: weekly\n  commit-message"
  },
  {
    "path": ".github/workflows/bash-frozen.yml",
    "chars": 1065,
    "preview": "name: Bash scripts frozen\n\non:\n  pull_request:\n    paths:\n      - 'kubectx'\n      - 'kubens'\n\njobs:\n  comment:\n    runs-"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 2076,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".github/workflows/dependabot.yml",
    "chars": 591,
    "preview": "name: Dependabot\n\non:\n  pull_request:\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  auto-merge:\n    ru"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1934,
    "preview": "# Copyright 2021 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": ".goreleaser.yml",
    "chars": 2849,
    "preview": "# yaml-language-server: $schema=https://goreleaser.com/static/schema.json\n\n# Copyright 2021 Google LLC\n#\n# Licensed unde"
  },
  {
    "path": ".krew/ctx.yaml",
    "chars": 952,
    "preview": "apiVersion: krew.googlecontainertools.github.com/v1alpha2\nkind: Plugin\nmetadata:\n  name: ctx\nspec:\n  homepage: https://g"
  },
  {
    "path": ".krew/ns.yaml",
    "chars": 853,
    "preview": "apiVersion: krew.googlecontainertools.github.com/v1alpha2\nkind: Plugin\nmetadata:\n  name: ns\nspec:\n  homepage: https://gi"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 968,
    "preview": "# How to contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guid"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 7281,
    "preview": "# `kubectx` + `kubens`: Power tools for kubectl\n\n![Latest GitHub release](https://img.shields.io/github/release/ahmetb/k"
  },
  {
    "path": "cmd/kubectx/current.go",
    "chars": 1342,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/delete.go",
    "chars": 2783,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/env.go",
    "chars": 602,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/flags.go",
    "chars": 2214,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/flags_test.go",
    "chars": 3296,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/fzf.go",
    "chars": 3917,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/help.go",
    "chars": 2211,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/help_test.go",
    "chars": 1011,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/isolated_shell_guard.go",
    "chars": 574,
    "preview": "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/kube"
  },
  {
    "path": "cmd/kubectx/list.go",
    "chars": 1651,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/main.go",
    "chars": 1235,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/rename.go",
    "chars": 2920,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/rename_test.go",
    "chars": 1610,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/shell.go",
    "chars": 3793,
    "preview": "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/"
  },
  {
    "path": "cmd/kubectx/shell_test.go",
    "chars": 2650,
    "preview": "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_detectShel"
  },
  {
    "path": "cmd/kubectx/state.go",
    "chars": 1524,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/state_test.go",
    "chars": 2650,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/switch.go",
    "chars": 2936,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/unset.go",
    "chars": 1496,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubectx/version.go",
    "chars": 354,
    "preview": "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 desc"
  },
  {
    "path": "cmd/kubens/current.go",
    "chars": 1374,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/flags.go",
    "chars": 2090,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/flags_test.go",
    "chars": 3106,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/fzf.go",
    "chars": 2340,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/help.go",
    "chars": 1986,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/list.go",
    "chars": 2930,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/main.go",
    "chars": 1234,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/statefile.go",
    "chars": 1866,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/statefile_test.go",
    "chars": 1682,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/switch.go",
    "chars": 3385,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/unset.go",
    "chars": 1746,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "cmd/kubens/version.go",
    "chars": 354,
    "preview": "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 desc"
  },
  {
    "path": "completion/_kubectx.zsh",
    "chars": 501,
    "preview": "#compdef kubectx kctx=kubectx\n\nlocal KUBECTX=\"${HOME}/.kube/kubectx\"\nPREV=\"\"\n\nlocal context_array=(\"${(@f)$(kubectl conf"
  },
  {
    "path": "completion/_kubens.zsh",
    "chars": 134,
    "preview": "#compdef kubens kns=kubens\n_arguments \"1: :(- $(kubectl get namespaces -o=jsonpath='{range .items[*].metadata.name}{@}{\""
  },
  {
    "path": "completion/kubectx.bash",
    "chars": 213,
    "preview": "_kube_contexts()\n{\n  local curr_arg;\n  curr_arg=${COMP_WORDS[COMP_CWORD]}\n  COMPREPLY=( $(compgen -W \"- $(kubectl config"
  },
  {
    "path": "completion/kubectx.fish",
    "chars": 382,
    "preview": "# kubectx\n\nfunction __fish_kubectx_arg_number -a number\n    set -l cmd (commandline -opc)\n    test (count $cmd) -eq $num"
  },
  {
    "path": "completion/kubens.bash",
    "chars": 254,
    "preview": "_kube_namespaces()\n{\n  local curr_arg;\n  curr_arg=${COMP_WORDS[COMP_CWORD]}\n  COMPREPLY=( $(compgen -W \"- $(kubectl get "
  },
  {
    "path": "completion/kubens.fish",
    "chars": 599,
    "preview": "# kubens\n\nfunction __fish_kubens_arg_number -a number\n    set -l cmd (commandline -opc)\n    test (count $cmd) -eq $numbe"
  },
  {
    "path": "go.mod",
    "chars": 2218,
    "preview": "module github.com/ahmetb/kubectx\n\ngo 1.25.0\n\nrequire (\n\tfacette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb\n\tgithub.co"
  },
  {
    "path": "go.sum",
    "chars": 12636,
    "preview": "facette.io/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:1pSweJFeR3Pqx7uoelppkzeegfUBXL6I2FFAbfXw570=\nfacette.io/natsort"
  },
  {
    "path": "internal/cmdutil/deprecated.go",
    "chars": 1028,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/cmdutil/deprecated_test.go",
    "chars": 1387,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/cmdutil/interactive.go",
    "chars": 1187,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/cmdutil/util.go",
    "chars": 1322,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/cmdutil/util_test.go",
    "chars": 2316,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/env/constants.go",
    "chars": 1235,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/contextmodify.go",
    "chars": 1524,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/contextmodify_test.go",
    "chars": 4895,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/contexts.go",
    "chars": 1990,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/contexts_test.go",
    "chars": 2575,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/currentcontext.go",
    "chars": 1157,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/currentcontext_test.go",
    "chars": 1931,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/helper_test.go",
    "chars": 1405,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/kubeconfig.go",
    "chars": 2110,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/kubeconfig_test.go",
    "chars": 1811,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/kubeconfigloader.go",
    "chars": 2224,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/kubeconfigloader_test.go",
    "chars": 2006,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/namespace.go",
    "chars": 1342,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/kubeconfig/namespace_test.go",
    "chars": 2456,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/printer/color.go",
    "chars": 1469,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/printer/color_test.go",
    "chars": 1295,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/printer/printer.go",
    "chars": 1593,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "internal/testutil/kubeconfigbuilder.go",
    "chars": 1653,
    "preview": "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "kubectx",
    "chars": 6263,
    "preview": "#!/usr/bin/env bash\n#\n# kubectx(1) is a utility to manage and switch between kubectl contexts.\n\n# Copyright 2017 Google "
  },
  {
    "path": "kubens",
    "chars": 5703,
    "preview": "#!/usr/bin/env bash\n#\n# kubens(1) is a utility to switch between Kubernetes namespaces.\n\n# Copyright 2017 Google Inc.\n#\n"
  },
  {
    "path": "test/common.bash",
    "chars": 571,
    "preview": "#!/usr/bin/env bats\n\n# bats setup function\nsetup() {\n  TEMP_HOME=\"$(mktemp -d)\"\n  export TEMP_HOME\n  export HOME=$TEMP_H"
  },
  {
    "path": "test/kubectx.bats",
    "chars": 4770,
    "preview": "#!/usr/bin/env bats\n\nCOMMAND=\"${COMMAND:-$BATS_TEST_DIRNAME/../kubectx}\"\n\nload common\n\n@test \"--help should not fail\" {\n"
  },
  {
    "path": "test/kubens.bats",
    "chars": 3145,
    "preview": "#!/usr/bin/env bats\n\nCOMMAND=\"${COMMAND:-$BATS_TEST_DIRNAME/../kubens}\"\n\n# TODO(ahmetb) remove this after bash implement"
  },
  {
    "path": "test/mock-kubectl",
    "chars": 154,
    "preview": "#!/usr/bin/env bash\n\n[[ -n $DEBUG ]] && set -x\n\nset -eou pipefail\n\nif [[ $@ == *'get namespaces'* ]]; then\n  echo \"ns1\"\n"
  },
  {
    "path": "test/testdata/config1",
    "chars": 258,
    "preview": "# config with one context\n\napiVersion: v1\nclusters:\n- cluster:\n    server: \"\"\n  name: cluster1\ncontexts:\n- context:\n    "
  },
  {
    "path": "test/testdata/config2",
    "chars": 356,
    "preview": "# config with two contexts\n\napiVersion: v1\nclusters:\n- cluster:\n    server: \"\"\n  name: cluster1\ncontexts:\n- context:\n   "
  }
]

About this extraction

This page contains the full source code of the ahmetb/kubectx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 82 files (171.1 KB), approximately 53.3k tokens, and a symbol index with 186 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!