Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package kubelogin for openSUSE:Factory 
checked in at 2026-03-02 17:36:26
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kubelogin (Old)
 and      /work/SRC/openSUSE:Factory/.kubelogin.new.29461 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kubelogin"

Mon Mar  2 17:36:26 2026 rev:25 rq:1335655 version:0.2.15

Changes:
--------
--- /work/SRC/openSUSE:Factory/kubelogin/kubelogin.changes      2026-01-12 
11:49:58.021241009 +0100
+++ /work/SRC/openSUSE:Factory/.kubelogin.new.29461/kubelogin.changes   
2026-03-02 17:36:29.933757244 +0100
@@ -1,0 +2,11 @@
+Wed Feb 25 09:19:07 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 0.2.15:
+  * Bug Fixes
+    - PoP token flow crash with nil pointer in cache.Replace when
+      running non-root by @vineeth-thumma in #736
+  * Enhancements
+    - feat: automate CHANGELOG.md generation for releases by
+      @Copilot in #737
+
+-------------------------------------------------------------------

Old:
----
  kubelogin-0.2.14.obscpio

New:
----
  kubelogin-0.2.15.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ kubelogin.spec ++++++
--- /var/tmp/diff_new_pack.9dZW8d/_old  2026-03-02 17:36:31.869838802 +0100
+++ /var/tmp/diff_new_pack.9dZW8d/_new  2026-03-02 17:36:31.921840993 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           kubelogin
-Version:        0.2.14
+Version:        0.2.15
 Release:        0
 Summary:        Kubernetes client credential plugin implementing Azure 
authentication
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.9dZW8d/_old  2026-03-02 17:36:32.301857001 +0100
+++ /var/tmp/diff_new_pack.9dZW8d/_new  2026-03-02 17:36:32.337858517 +0100
@@ -2,7 +2,7 @@
   <service name="obs_scm" mode="manual">
     <param name="url">https://github.com/Azure/kubelogin.git</param>
     <param name="scm">git</param>
-    <param name="revision">v0.2.14</param>
+    <param name="revision">v0.2.15</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.9dZW8d/_old  2026-03-02 17:36:32.585868965 +0100
+++ /var/tmp/diff_new_pack.9dZW8d/_new  2026-03-02 17:36:32.625870650 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/Azure/kubelogin.git</param>
-              <param 
name="changesrevision">6e8b5babc994d57e752e24297dd9c6f59247a1f8</param></service></servicedata>
+              <param 
name="changesrevision">7936910173e517c5c651d1bbd56d3143c4b1fe4e</param></service></servicedata>
 (No newline at EOF)
 

++++++ kubelogin-0.2.14.obscpio -> kubelogin-0.2.15.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/.bingo/go.mod 
new/kubelogin-0.2.15/.bingo/go.mod
--- old/kubelogin-0.2.14/.bingo/go.mod  2026-01-06 19:59:04.000000000 +0100
+++ new/kubelogin-0.2.15/.bingo/go.mod  2026-02-20 03:16:58.000000000 +0100
@@ -1 +1 @@
-module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility 
with non-Go projects. Commit this file, together with other .mod files.
\ No newline at end of file
+module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility 
with non-Go projects. Commit this file, together with other .mod files.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.14/.github/skills/update-changelog/SKILL.md 
new/kubelogin-0.2.15/.github/skills/update-changelog/SKILL.md
--- old/kubelogin-0.2.14/.github/skills/update-changelog/SKILL.md       
1970-01-01 01:00:00.000000000 +0100
+++ new/kubelogin-0.2.15/.github/skills/update-changelog/SKILL.md       
2026-02-20 03:16:58.000000000 +0100
@@ -0,0 +1,74 @@
+---
+name: update-changelog
+description: >
+  Generate and update CHANGELOG.md for a new kubelogin release. Use this
+  when asked to prepare release notes or update the changelog for a new
+  version. Fetches merged pull requests since the previous release,
+  categorizes them, identifies new contributors, and prepares a formatted
+  entry for CHANGELOG.md.
+---
+
+## Overview
+
+The `make changelog` target runs `hack/changelog-generator/main.go`, which:
+
+1. Calls `gh api repos/Azure/kubelogin/releases/latest` to determine the
+   previous release tag (when `SINCE_TAG` is not supplied).
+2. Calls `gh api repos/Azure/kubelogin/commits/<tag>` to get the tag date.
+3. Fetches all merged pull requests since that date via
+   `gh api --paginate repos/Azure/kubelogin/pulls?state=closed&...`.
+4. Categorizes each PR by GitHub label, then by title prefix:
+   - **Bug Fixes** — label `bug`/`fix`; prefix `fix:`, `bugfix:`, `hotfix:`
+   - **Enhancements** — label `enhancement`/`feature`; prefix `feat:`
+   - **Maintenance** — label `dependencies`/`chore`; prefix `bump `, `update `
+   - **Doc Update** — label `documentation`/`docs`; prefix `docs:`
+   - **What's Changed** — everything else
+5. Identifies first-time contributors by comparing PR authors against all
+   prior merged PR authors.
+6. Writes a formatted entry to `changelog-entry.md`.
+
+## Steps to follow
+
+1. Determine the new version number (e.g. `0.2.15`) and, optionally, the
+   previous tag to compare from (e.g. `v0.2.14`).
+   - If the previous tag is not provided, the tool will auto-detect the
+     latest stable release.
+
+2. Run `make changelog` to generate the changelog entry:
+
+   ```bash
+   # SINCE_TAG is optional – omit to auto-detect the latest release tag
+   VERSION=0.2.15 make changelog
+   # or explicitly:
+   VERSION=0.2.15 SINCE_TAG=v0.2.14 make changelog
+   ```
+
+   This writes the formatted entry to `changelog-entry.md`. When using the
+   [GitHub Actions workflow](../../.github/workflows/update-changelog.yml),
+   the workflow then inserts `changelog-entry.md` after the header of
+   `CHANGELOG.md` and opens a pull request automatically.
+
+   When running locally, insert the content manually:
+
+   ```bash
+   # Insert after the "# Change Log" header
+   {
+     head -n 2 CHANGELOG.md
+     echo ""
+     cat changelog-entry.md
+     echo ""
+     tail -n +3 CHANGELOG.md
+   } > CHANGELOG.md.new && mv CHANGELOG.md.new CHANGELOG.md
+   rm changelog-entry.md
+   ```
+
+   Authentication is handled automatically by the `gh` CLI. Ensure you
+   are authenticated (`gh auth login`) or that `GH_TOKEN`/`GITHUB_TOKEN`
+   is set in the environment.
+
+3. Review the updated `CHANGELOG.md` and edit entries as needed for
+   clarity before committing.
+
+4. After the changelog is merged to the default branch, trigger the
+   [Release workflow](../../.github/workflows/release.yml) to create the
+   GitHub release and build binaries.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.14/.github/workflows/update-changelog.yml 
new/kubelogin-0.2.15/.github/workflows/update-changelog.yml
--- old/kubelogin-0.2.14/.github/workflows/update-changelog.yml 1970-01-01 
01:00:00.000000000 +0100
+++ new/kubelogin-0.2.15/.github/workflows/update-changelog.yml 2026-02-20 
03:16:58.000000000 +0100
@@ -0,0 +1,81 @@
+---
+name: Update Changelog
+
+"on":
+  workflow_dispatch:
+    inputs:
+      version:
+        description: 'Version number (e.g., 0.2.15)'
+        required: true
+        type: string
+      since_tag:
+        description: >-
+          Previous version tag (e.g., v0.2.14); defaults to latest tag
+        required: false
+        type: string
+
+permissions:
+  contents: write
+  pull-requests: write
+
+jobs:
+  update-changelog:
+    name: Generate and Update Changelog
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        # v4.1.1
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+        with:
+          fetch-depth: 0  # Fetch all history
+
+      - name: Set up Go
+        # v4.1.0
+        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe
+        with:
+          go-version-file: "go.mod"
+          cache: true
+
+      - name: Generate changelog entry
+        id: generate
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          VERSION=${{ inputs.version }} \
+          SINCE_TAG=${{ inputs.since_tag }} \
+          make changelog
+
+      - name: Update CHANGELOG.md
+        run: |
+          # Create a temporary file with entry after header
+          {
+            head -n 2 CHANGELOG.md
+            echo ""
+            cat changelog-entry.md
+            echo ""
+            tail -n +3 CHANGELOG.md
+          } > CHANGELOG.md.new
+
+          mv CHANGELOG.md.new CHANGELOG.md
+          rm changelog-entry.md
+
+      - name: Create Pull Request
+        uses: peter-evans/create-pull-request@v5
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          commit-message: >-
+            docs: update CHANGELOG.md for v${{ inputs.version }}
+          title: "v${{ inputs.version }} release"
+          body: |
+            This PR updates the CHANGELOG.md with release notes for
+            version ${{ inputs.version }}.
+
+            The changelog entry has been automatically generated from
+            merged pull requests. Please review and edit as needed
+            before merging.
+
+            After merging, you can trigger the release workflow to
+            create the release.
+          branch: changelog/v${{ inputs.version }}
+          delete-branch: true
+          labels: documentation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/.gitignore 
new/kubelogin-0.2.15/.gitignore
--- old/kubelogin-0.2.14/.gitignore     2026-01-06 19:59:04.000000000 +0100
+++ new/kubelogin-0.2.15/.gitignore     2026-02-20 03:16:58.000000000 +0100
@@ -21,3 +21,6 @@
 
 # JetBrains IDE folder
 .idea
+
+# hack/changelog-generator binary
+hack/changelog-generator/changelog-generator
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/CHANGELOG.md 
new/kubelogin-0.2.15/CHANGELOG.md
--- old/kubelogin-0.2.14/CHANGELOG.md   2026-01-06 19:59:04.000000000 +0100
+++ new/kubelogin-0.2.15/CHANGELOG.md   2026-02-20 03:16:58.000000000 +0100
@@ -1,5 +1,17 @@
 # Change Log
 
+## [0.2.15]
+
+### Bug Fixes
+
+* PoP token flow crash with nil pointer in cache.Replace when running non-root 
by @vineeth-thumma in https://github.com/Azure/kubelogin/pull/736
+
+### Enhancements
+
+* feat: automate CHANGELOG.md generation for releases by @Copilot in 
https://github.com/Azure/kubelogin/pull/737
+
+**Full Changelog**: 
https://github.com/Azure/kubelogin/compare/v0.2.14...v0.2.15
+
 ## [0.2.14]
 
 ### Maintenance
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/Makefile 
new/kubelogin-0.2.15/Makefile
--- old/kubelogin-0.2.14/Makefile       2026-01-06 19:59:04.000000000 +0100
+++ new/kubelogin-0.2.15/Makefile       2026-02-20 03:16:58.000000000 +0100
@@ -25,15 +25,23 @@
        @echo "  test         - Run tests (includes linting)"
        @echo "  clean        - Remove built binaries"
        @echo "  build-image  - Build Docker image with kubelogin binary"
+       @echo "  changelog    - Generate a CHANGELOG.md entry for a new release"
        @echo ""
        @echo "Docker image build options:"
        @echo "  make build-image                    # Build with 'latest' tag"
        @echo "  GIT_TAG=v1.0.0 make build-image   # Build with specific tag"
        @echo ""
+       @echo "Changelog generation options:"
+       @echo "  VERSION=0.2.15 make changelog                    # auto-detect 
previous tag"
+       @echo "  VERSION=0.2.15 SINCE_TAG=v0.2.14 make changelog # explicit 
previous tag"
+       @echo "  Token is read from GITHUB_TOKEN env var or 'gh auth token' 
automatically"
+       @echo ""
        @echo "Environment variables:"
        @echo "  GOOS         - Target OS (default: $(OS))"
        @echo "  GOARCH       - Target architecture (default: $(ARCH))"
        @echo "  GIT_TAG      - Git tag for version info and Docker tagging"
+       @echo "  VERSION      - New version number for changelog generation"
+       @echo "  SINCE_TAG    - Previous tag to compare from for changelog 
(optional)"
 
 lint: $(GOLANGCI_LINT)
        $(GOLANGCI_LINT) run
@@ -47,6 +55,16 @@
 clean:
        -rm -f $(BIN)
 
+changelog:
+       @if [ -z "$(VERSION)" ]; then \
+               echo "Error: VERSION is required. Usage: VERSION=0.2.15 make 
changelog"; \
+               exit 1; \
+       fi
+       go run hack/changelog-generator/main.go \
+               --version="$(VERSION)" \
+               $(if $(SINCE_TAG),--since-tag="$(SINCE_TAG)",) \
+               --repo="Azure/kubelogin"
+
 # Docker image build target
 IMAGE_NAME    := ghcr.io/azure/kubelogin
 IMAGE_TAG     := $(if $(GIT_TAG),$(GIT_TAG),latest)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/docs/book/src/development.md 
new/kubelogin-0.2.15/docs/book/src/development.md
--- old/kubelogin-0.2.14/docs/book/src/development.md   2026-01-06 
19:59:04.000000000 +0100
+++ new/kubelogin-0.2.15/docs/book/src/development.md   2026-02-20 
03:16:58.000000000 +0100
@@ -44,3 +44,53 @@
 ```
 
 **Note**: Tests require the system dependencies listed above. If you encounter 
errors related to `libsecret-1.so` or "encrypted storage isn't possible", 
ensure the libsecret library is installed.
+
+## Releases
+
+### Automated Changelog Generation
+
+The project includes an automated changelog generation tool that creates 
properly formatted CHANGELOG.md entries from merged pull requests.
+
+#### Using the GitHub Actions Workflow
+
+1. Navigate to the [Update Changelog 
workflow](https://github.com/Azure/kubelogin/actions/workflows/update-changelog.yml)
+2. Click "Run workflow"
+3. Provide the required inputs:
+   - **Version number**: The new version (e.g., `0.2.15`) without the 'v' 
prefix
+   - **Previous version tag**: The tag to compare from (e.g., `v0.2.14`) with 
the 'v' prefix
+4. Click "Run workflow"
+
+The workflow will:
+- Fetch all merged PRs since the previous version
+- Categorize them (What's Changed, Maintenance, Enhancements, Bug Fixes, Doc 
Update)
+- Identify new contributors
+- Generate a formatted changelog entry
+- Create a pull request with the updated CHANGELOG.md
+
+#### Running Locally
+
+You can generate a changelog entry locally using the `gh` CLI and `make`:
+
+```bash
+# Authenticate with the gh CLI (one-time setup)
+gh auth login
+
+VERSION=0.2.15 make changelog
+```
+
+The tool uses `gh api` for all GitHub API calls, so only `gh auth login`
+is required. In CI the `GH_TOKEN` environment variable is used instead.
+
+See 
[hack/changelog-generator/README.md](../../hack/changelog-generator/README.md) 
for more details.
+
+### Release Process
+
+After the changelog is updated:
+
+1. Review and merge the changelog PR
+2. Trigger the [Release 
workflow](https://github.com/Azure/kubelogin/actions/workflows/release.yml)
+3. The workflow will:
+   - Read the version from CHANGELOG.md
+   - Create a draft GitHub release
+   - Build binaries for all platforms
+   - Upload artifacts to the release
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/hack/changelog-generator/README.md 
new/kubelogin-0.2.15/hack/changelog-generator/README.md
--- old/kubelogin-0.2.14/hack/changelog-generator/README.md     1970-01-01 
01:00:00.000000000 +0100
+++ new/kubelogin-0.2.15/hack/changelog-generator/README.md     2026-02-20 
03:16:58.000000000 +0100
@@ -0,0 +1,140 @@
+# Changelog Generator
+
+This tool automatically generates CHANGELOG.md entries for kubelogin releases 
by fetching merged pull requests from GitHub and categorizing them 
appropriately.
+
+## Features
+
+- Fetches merged PRs since the last release tag
+- Automatically categorizes PRs into:
+  - What's Changed (general changes)
+  - Enhancements (new features)
+  - Bug Fixes (bug fixes)
+  - Maintenance (dependency updates, CVE fixes, chores)
+  - Doc Update (documentation changes)
+- Identifies and lists new contributors
+- Generates a Full Changelog comparison link
+- Follows the existing CHANGELOG.md format
+
+## Quick Start
+
+### Via GitHub Actions (Recommended)
+
+1. Go to the [Actions 
tab](https://github.com/Azure/kubelogin/actions/workflows/update-changelog.yml)
+2. Click "Run workflow"
+3. Fill in the required inputs:
+   - **Version number**: e.g., `0.2.15` (without the 'v' prefix)
+   - **Previous version tag**: e.g., `v0.2.14` (with the 'v' prefix)
+4. Click "Run workflow"
+5. Review and merge the generated PR
+6. Trigger the [Release 
workflow](https://github.com/Azure/kubelogin/actions/workflows/release.yml)
+
+### Via Make Target
+
+```bash
+# Authenticate once with the gh CLI (one-time setup):
+gh auth login
+
+# SINCE_TAG is optional; omit it to auto-detect the latest tag
+VERSION=0.2.15 make changelog
+
+# Or specify the previous tag explicitly
+VERSION=0.2.15 SINCE_TAG=v0.2.14 make changelog
+```
+
+This generates a `changelog-entry.md` file that you can manually insert into 
CHANGELOG.md.
+
+### Running Directly
+
+```bash
+# Authenticate once with the gh CLI (one-time setup):
+gh auth login
+go run hack/changelog-generator/main.go \
+  --version="0.2.15" \
+  --repo="Azure/kubelogin" \
+  --output="changelog-entry.md"
+
+# Or specify the previous tag explicitly
+go run hack/changelog-generator/main.go \
+  --version="0.2.15" \
+  --since-tag="v0.2.14" \
+  --repo="Azure/kubelogin" \
+  --output="changelog-entry.md"
+```
+
+## PR Categorization
+
+PRs are categorized first by **GitHub labels**, then by **title patterns**:
+
+| Category | Labels | Title prefixes / patterns |
+|---|---|---|
+| Bug Fixes | `bug`, `fix` | `fix:`, `bugfix:`, `bug fix:`, `hotfix:` |
+| Enhancements | `enhancement`, `feature` | `feat:`, `feature:`, `add 
support`, `new feature` |
+| Maintenance | `maintenance`, `dependencies`, `chore` | `bump `, `update `, 
`CVE-`, `fix cve`, `chore` |
+| Doc Update | `documentation`, `docs` | `docs:`, `doc:`, `documentation`, 
`install doc` |
+| What's Changed | *(default)* | *(everything else)* |
+
+### New Contributor Detection
+
+A contributor is marked as "new" if they have a merged PR in the current 
release but **no** merged PRs before the previous release tag.
+
+## Example Output
+
+```markdown
+## [0.2.15]
+
+### What's Changed
+
+* Add new authentication method by @username in 
https://github.com/Azure/kubelogin/pull/123
+
+### Enhancements
+
+* Add Y support by @username in https://github.com/Azure/kubelogin/pull/124
+
+### Bug Fixes
+
+* Fix nil pointer in cache.Replace by @username in 
https://github.com/Azure/kubelogin/pull/127
+
+### Maintenance
+
+* Bump Go to 1.24.12 by @dependabot in 
https://github.com/Azure/kubelogin/pull/125
+
+### Doc Update
+
+* Update installation guide by @username in 
https://github.com/Azure/kubelogin/pull/126
+
+### New Contributors
+
+* @newuser made their first contribution in 
https://github.com/Azure/kubelogin/pull/123
+
+**Full Changelog**: 
https://github.com/Azure/kubelogin/compare/v0.2.14...v0.2.15
+```
+
+## Integration with Release Workflow
+
+1. **Generate** changelog entry (this tool) → creates a PR
+2. **Merge** the changelog PR
+3. **Trigger** the [Release 
workflow](https://github.com/Azure/kubelogin/actions/workflows/release.yml)
+   - Reads version from CHANGELOG.md
+   - Creates a draft release
+   - Builds binaries for all platforms
+   - Publishes artifacts
+
+## Troubleshooting
+
+**"No PRs found"**
+- Verify the tag exists: `git tag -l | grep <tag>`
+- Check that PRs were merged after the `since_tag` date
+
+**"API rate limit exceeded"**
+- Run `gh auth login` if you haven't already
+- Wait for rate limit reset (typically 1 hour)
+
+**Wrong categorization**
+- Add appropriate labels to PRs before running the tool
+- Or manually edit the generated changelog before merging
+
+## Requirements
+
+- [`gh` CLI](https://cli.github.com/) authenticated via `gh auth login`
+- Go 1.24.11 or later
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/hack/changelog-generator/go.mod 
new/kubelogin-0.2.15/hack/changelog-generator/go.mod
--- old/kubelogin-0.2.14/hack/changelog-generator/go.mod        1970-01-01 
01:00:00.000000000 +0100
+++ new/kubelogin-0.2.15/hack/changelog-generator/go.mod        2026-02-20 
03:16:58.000000000 +0100
@@ -0,0 +1,3 @@
+module github.com/Azure/kubelogin/hack/changelog-generator
+
+go 1.24.11
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/hack/changelog-generator/main.go 
new/kubelogin-0.2.15/hack/changelog-generator/main.go
--- old/kubelogin-0.2.14/hack/changelog-generator/main.go       1970-01-01 
01:00:00.000000000 +0100
+++ new/kubelogin-0.2.15/hack/changelog-generator/main.go       2026-02-20 
03:16:58.000000000 +0100
@@ -0,0 +1,397 @@
+package main
+
+import (
+       "encoding/json"
+       "flag"
+       "fmt"
+       "log"
+       "os"
+       "os/exec"
+       "regexp"
+       "sort"
+       "strings"
+       "time"
+)
+
+// GitHubPR represents a GitHub pull request
+type GitHubPR struct {
+       Number    int       `json:"number"`
+       Title     string    `json:"title"`
+       HTMLURL   string    `json:"html_url"`
+       User      User      `json:"user"`
+       MergedAt  time.Time `json:"merged_at"`
+       Labels    []Label   `json:"labels"`
+       CreatedAt time.Time `json:"created_at"`
+}
+
+// User represents a GitHub user
+type User struct {
+       Login string `json:"login"`
+}
+
+// Label represents a GitHub label
+type Label struct {
+       Name string `json:"name"`
+}
+
+// Contributor tracks first-time contributors
+type Contributor struct {
+       Login  string
+       PRURL  string
+       Number int
+}
+
+func main() {
+       version := flag.String("version", "", "Version number (e.g., 0.2.15)")
+       sinceTag := flag.String("since-tag", "", "Previous version tag (e.g., 
v0.2.14); defaults to the latest tag")
+       repo := flag.String("repo", "Azure/kubelogin", "Repository in format 
owner/repo")
+       output := flag.String("output", "changelog-entry.md", "Output file 
path")
+       flag.Parse()
+
+       if *version == "" {
+               log.Fatal("--version is required")
+       }
+
+       // Resolve the previous tag if not provided
+       if *sinceTag == "" {
+               resolved, err := getLatestTag(*repo)
+               if err != nil {
+                       log.Fatalf("Failed to resolve previous tag: %v", err)
+               }
+               log.Printf("No --since-tag provided; using latest tag: %s", 
resolved)
+               *sinceTag = resolved
+       }
+
+       // Get the date of the previous tag
+       tagDate, err := getTagDate(*repo, *sinceTag)
+       if err != nil {
+               log.Fatalf("Failed to get tag date: %v", err)
+       }
+
+       // Fetch merged PRs since the tag
+       prs, err := getMergedPRsSince(*repo, tagDate)
+       if err != nil {
+               log.Fatalf("Failed to fetch PRs: %v", err)
+       }
+
+       if len(prs) == 0 {
+               log.Println("No merged PRs found since", *sinceTag)
+       }
+
+       // Get all contributors before this tag to identify new ones
+       allContributorsBefore, err := getAllContributorsBefore(*repo, tagDate)
+       if err != nil {
+               log.Printf("Warning: Failed to get historical contributors: 
%v", err)
+               allContributorsBefore = make(map[string]bool)
+       }
+
+       // Categorize PRs and identify new contributors
+       categories := categorizePRs(prs, allContributorsBefore)
+
+       // Generate the changelog entry
+       entry := generateChangelogEntry(*version, *sinceTag, categories, *repo)
+
+       // Write to output file
+       if err := os.WriteFile(*output, []byte(entry), 0644); err != nil {
+               log.Fatalf("Failed to write output file: %v", err)
+       }
+
+       log.Printf("Successfully generated changelog entry for version %s", 
*version)
+}
+
+// ghAPI runs "gh api <args>" and returns the output.
+// Authentication is handled automatically by the gh CLI
+// (GITHUB_TOKEN env var or the credential stored by "gh auth login").
+func ghAPI(args ...string) ([]byte, error) {
+       out, err := exec.Command("gh", append([]string{"api"}, 
args...)...).Output()
+       if err != nil {
+               if exitErr, ok := err.(*exec.ExitError); ok {
+                       return nil, fmt.Errorf("gh api %v: %w\n%s", args, err, 
exitErr.Stderr)
+               }
+               return nil, fmt.Errorf("gh api %v: %w", args, err)
+       }
+       return out, nil
+}
+
+// getLatestTag returns the tag of the most recent stable release.
+func getLatestTag(repo string) (string, error) {
+       out, err := ghAPI(fmt.Sprintf("repos/%s/releases/latest", repo), 
"--jq", ".tag_name")
+       if err != nil {
+               return "", err
+       }
+       tag := strings.TrimSpace(string(out))
+       if tag == "" {
+               return "", fmt.Errorf("no releases found in repository %s", 
repo)
+       }
+       return tag, nil
+}
+
+// getTagDate returns the author date of the commit the tag points to.
+func getTagDate(repo, tag string) (time.Time, error) {
+       out, err := ghAPI(
+               fmt.Sprintf("repos/%s/commits/%s", repo, tag),
+               "--jq", ".commit.author.date",
+       )
+       if err != nil {
+               return time.Time{}, err
+       }
+       return time.Parse(time.RFC3339, strings.TrimSpace(string(out)))
+}
+
+// decodePRStream decodes newline-delimited JSON objects produced by
+// "gh api --paginate ... --jq '.[]'".
+func decodePRStream(data []byte) ([]GitHubPR, error) {
+       var prs []GitHubPR
+       dec := json.NewDecoder(strings.NewReader(string(data)))
+       for dec.More() {
+               var pr GitHubPR
+               if err := dec.Decode(&pr); err != nil {
+                       return nil, err
+               }
+               prs = append(prs, pr)
+       }
+       return prs, nil
+}
+
+// releasePRTitle matches PR titles that represent a version release
+// (e.g. "v0.2.14 release") so they can be excluded from the changelog.
+var releasePRTitle = regexp.MustCompile(`(?i)^v?\d+\.\d+\.\d+`)
+
+// isReleasePR returns true when the PR title looks like a release commit
+// (e.g. "v0.2.14 release", "0.2.14 release").
+func isReleasePR(title string) bool {
+       return releasePRTitle.MatchString(strings.TrimSpace(title))
+}
+
+// hasLabel returns true if the PR has a label with the given name 
(case-insensitive).
+func hasLabel(pr GitHubPR, name string) bool {
+       for _, l := range pr.Labels {
+               if strings.EqualFold(l.Name, name) {
+                       return true
+               }
+       }
+       return false
+}
+
+// getMergedPRsSince returns all merged PRs after the given time.
+func getMergedPRsSince(repo string, since time.Time) ([]GitHubPR, error) {
+       out, err := ghAPI(
+               "--paginate",
+               
fmt.Sprintf("repos/%s/pulls?state=closed&sort=created&direction=desc&per_page=100",
 repo),
+               "--jq", ".[]",
+       )
+       if err != nil {
+               return nil, err
+       }
+       all, err := decodePRStream(out)
+       if err != nil {
+               return nil, err
+       }
+       var prs []GitHubPR
+       for _, pr := range all {
+               if !pr.MergedAt.IsZero() && pr.MergedAt.After(since) && 
!isReleasePR(pr.Title) && !hasLabel(pr, "release") {
+                       prs = append(prs, pr)
+               }
+       }
+       return prs, nil
+}
+
+// getAllContributorsBefore returns the set of logins that contributed before 
the given time.
+func getAllContributorsBefore(repo string, before time.Time) (map[string]bool, 
error) {
+       out, err := ghAPI(
+               "--paginate",
+               
fmt.Sprintf("repos/%s/pulls?state=closed&sort=created&direction=asc&per_page=100",
 repo),
+               "--jq", ".[]",
+       )
+       if err != nil {
+               return nil, err
+       }
+       all, err := decodePRStream(out)
+       if err != nil {
+               return nil, err
+       }
+       contributors := make(map[string]bool)
+       for _, pr := range all {
+               if !pr.MergedAt.IsZero() && pr.MergedAt.Before(before) {
+                       contributors[pr.User.Login] = true
+               }
+       }
+       return contributors, nil
+}
+
+// Categories holds all categorized PRs
+type Categories struct {
+       Changes         []GitHubPR
+       BugFixes        []GitHubPR
+       Maintenance     []GitHubPR
+       Enhancements    []GitHubPR
+       DocUpdates      []GitHubPR
+       NewContributors []Contributor
+}
+
+func categorizePRs(prs []GitHubPR, existingContributors map[string]bool) 
Categories {
+       cats := Categories{
+               Changes:         make([]GitHubPR, 0),
+               BugFixes:        make([]GitHubPR, 0),
+               Maintenance:     make([]GitHubPR, 0),
+               Enhancements:    make([]GitHubPR, 0),
+               DocUpdates:      make([]GitHubPR, 0),
+               NewContributors: make([]Contributor, 0),
+       }
+
+       seenNewContributors := make(map[string]bool)
+
+       for _, pr := range prs {
+               // Check for new contributors
+               if !existingContributors[pr.User.Login] && 
!seenNewContributors[pr.User.Login] {
+                       cats.NewContributors = append(cats.NewContributors, 
Contributor{
+                               Login:  pr.User.Login,
+                               PRURL:  pr.HTMLURL,
+                               Number: pr.Number,
+                       })
+                       seenNewContributors[pr.User.Login] = true
+               }
+
+               // Categorize based on labels and title
+               category := categorizeByLabelsAndTitle(pr)
+               switch category {
+               case "bugfix":
+                       cats.BugFixes = append(cats.BugFixes, pr)
+               case "maintenance":
+                       cats.Maintenance = append(cats.Maintenance, pr)
+               case "enhancement":
+                       cats.Enhancements = append(cats.Enhancements, pr)
+               case "documentation":
+                       cats.DocUpdates = append(cats.DocUpdates, pr)
+               default:
+                       cats.Changes = append(cats.Changes, pr)
+               }
+       }
+
+       return cats
+}
+
+func categorizeByLabelsAndTitle(pr GitHubPR) string {
+       title := strings.ToLower(pr.Title)
+
+       // Check labels first
+       for _, label := range pr.Labels {
+               labelName := strings.ToLower(label.Name)
+               if strings.Contains(labelName, "bug") ||
+                       strings.Contains(labelName, "fix") {
+                       return "bugfix"
+               }
+               if strings.Contains(labelName, "maintenance") ||
+                       strings.Contains(labelName, "dependencies") ||
+                       strings.Contains(labelName, "chore") {
+                       return "maintenance"
+               }
+               if strings.Contains(labelName, "enhancement") ||
+                       strings.Contains(labelName, "feature") {
+                       return "enhancement"
+               }
+               if strings.Contains(labelName, "documentation") ||
+                       strings.Contains(labelName, "docs") {
+                       return "documentation"
+               }
+       }
+
+       // Check title patterns
+       if strings.HasPrefix(title, "fix:") ||
+               strings.HasPrefix(title, "bugfix:") ||
+               strings.HasPrefix(title, "bug fix:") ||
+               strings.HasPrefix(title, "hotfix:") {
+               return "bugfix"
+       }
+
+       if strings.HasPrefix(title, "bump ") ||
+               strings.HasPrefix(title, "update ") ||
+               strings.Contains(title, "cve-") ||
+               strings.Contains(title, "fix cve") ||
+               strings.Contains(title, "dependencies") ||
+               strings.HasPrefix(title, "chore:") ||
+               strings.HasPrefix(title, "chore ") {
+               return "maintenance"
+       }
+
+       if strings.HasPrefix(title, "docs:") ||
+               strings.HasPrefix(title, "doc:") ||
+               strings.Contains(title, "documentation") ||
+               strings.Contains(title, "install doc") {
+               return "documentation"
+       }
+
+       if strings.HasPrefix(title, "feat:") ||
+               strings.HasPrefix(title, "feature:") ||
+               strings.Contains(title, "add support") ||
+               strings.Contains(title, "new feature") {
+               return "enhancement"
+       }
+
+       return "change"
+}
+
+func generateChangelogEntry(version, sinceTag string, cats Categories, repo 
string) string {
+       var sb strings.Builder
+
+       sb.WriteString(fmt.Sprintf("## [%s]\n", version))
+
+       // What's Changed
+       if len(cats.Changes) > 0 {
+               sb.WriteString("\n### What's Changed\n\n")
+               for _, pr := range cats.Changes {
+                       sb.WriteString(fmt.Sprintf("* %s by @%s in %s\n", 
pr.Title, pr.User.Login, pr.HTMLURL))
+               }
+       }
+
+       // Enhancements
+       if len(cats.Enhancements) > 0 {
+               sb.WriteString("\n### Enhancements\n\n")
+               for _, pr := range cats.Enhancements {
+                       sb.WriteString(fmt.Sprintf("* %s by @%s in %s\n", 
pr.Title, pr.User.Login, pr.HTMLURL))
+               }
+       }
+
+       // Bug Fixes
+       if len(cats.BugFixes) > 0 {
+               sb.WriteString("\n### Bug Fixes\n\n")
+               for _, pr := range cats.BugFixes {
+                       sb.WriteString(fmt.Sprintf("* %s by @%s in %s\n", 
pr.Title, pr.User.Login, pr.HTMLURL))
+               }
+       }
+
+       // Maintenance
+       if len(cats.Maintenance) > 0 {
+               sb.WriteString("\n### Maintenance\n\n")
+               for _, pr := range cats.Maintenance {
+                       sb.WriteString(fmt.Sprintf("* %s by @%s in %s\n", 
pr.Title, pr.User.Login, pr.HTMLURL))
+               }
+       }
+
+       // Doc Updates
+       if len(cats.DocUpdates) > 0 {
+               sb.WriteString("\n### Doc Update\n\n")
+               for _, pr := range cats.DocUpdates {
+                       sb.WriteString(fmt.Sprintf("* %s by @%s in %s\n", 
pr.Title, pr.User.Login, pr.HTMLURL))
+               }
+       }
+
+       // New Contributors
+       if len(cats.NewContributors) > 0 {
+               sb.WriteString("\n### New Contributors\n\n")
+               // Sort by username for consistency
+               sort.Slice(cats.NewContributors, func(i, j int) bool {
+                       return cats.NewContributors[i].Login < 
cats.NewContributors[j].Login
+               })
+               for _, c := range cats.NewContributors {
+                       sb.WriteString(fmt.Sprintf("* @%s made their first 
contribution in %s\n", c.Login, c.PRURL))
+               }
+       }
+
+       // Full Changelog link
+       currentTag := "v" + version
+       sb.WriteString(fmt.Sprintf("\n**Full Changelog**: 
https://github.com/%s/compare/%s...%s\n";,
+               repo, sinceTag, currentTag))
+
+       return sb.String()
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.14/hack/changelog-generator/main_test.go 
new/kubelogin-0.2.15/hack/changelog-generator/main_test.go
--- old/kubelogin-0.2.14/hack/changelog-generator/main_test.go  1970-01-01 
01:00:00.000000000 +0100
+++ new/kubelogin-0.2.15/hack/changelog-generator/main_test.go  2026-02-20 
03:16:58.000000000 +0100
@@ -0,0 +1,179 @@
+package main
+
+import (
+       "fmt"
+       "strings"
+       "testing"
+       "time"
+)
+
+func TestCategorizeByLabelsAndTitle(t *testing.T) {
+       tests := []struct {
+               name     string
+               pr       GitHubPR
+               expected string
+       }{
+               // Label-based categorization takes precedence
+               {name: "bug label", pr: GitHubPR{Title: "something", Labels: 
[]Label{{Name: "bug"}}}, expected: "bugfix"},
+               {name: "fix label", pr: GitHubPR{Title: "something", Labels: 
[]Label{{Name: "fix"}}}, expected: "bugfix"},
+               {name: "enhancement label", pr: GitHubPR{Title: "something", 
Labels: []Label{{Name: "enhancement"}}}, expected: "enhancement"},
+               {name: "feature label", pr: GitHubPR{Title: "something", 
Labels: []Label{{Name: "feature"}}}, expected: "enhancement"},
+               {name: "dependencies label", pr: GitHubPR{Title: "something", 
Labels: []Label{{Name: "dependencies"}}}, expected: "maintenance"},
+               {name: "documentation label", pr: GitHubPR{Title: "something", 
Labels: []Label{{Name: "documentation"}}}, expected: "documentation"},
+               {name: "docs label", pr: GitHubPR{Title: "something", Labels: 
[]Label{{Name: "docs"}}}, expected: "documentation"},
+
+               // Title prefix — bug fixes
+               {name: "fix: prefix", pr: GitHubPR{Title: "fix: nil pointer"}, 
expected: "bugfix"},
+               {name: "bugfix: prefix", pr: GitHubPR{Title: "bugfix: 
something"}, expected: "bugfix"},
+               {name: "bug fix: prefix", pr: GitHubPR{Title: "bug fix: 
something"}, expected: "bugfix"},
+               {name: "hotfix: prefix", pr: GitHubPR{Title: "hotfix: 
something"}, expected: "bugfix"},
+
+               // Title prefix — maintenance
+               {name: "bump prefix", pr: GitHubPR{Title: "Bump Go to 1.24"}, 
expected: "maintenance"},
+               {name: "update prefix", pr: GitHubPR{Title: "Update 
dependency"}, expected: "maintenance"},
+               {name: "cve in title", pr: GitHubPR{Title: "address 
cve-2024-1234"}, expected: "maintenance"},
+               {name: "fix cve", pr: GitHubPR{Title: "fix cve issues"}, 
expected: "maintenance"},
+               {name: "chore: prefix", pr: GitHubPR{Title: "chore: tidy 
modules"}, expected: "maintenance"},
+               {name: "chore space prefix", pr: GitHubPR{Title: "chore bump 
version"}, expected: "maintenance"},
+               {name: "choreography not matched", pr: GitHubPR{Title: 
"choreography work"}, expected: "change"},
+
+               // Title prefix — documentation
+               {name: "docs: prefix", pr: GitHubPR{Title: "docs: update 
readme"}, expected: "documentation"},
+               {name: "doc: prefix", pr: GitHubPR{Title: "doc: fix typo"}, 
expected: "documentation"},
+
+               // Title prefix — enhancements
+               {name: "feat: prefix", pr: GitHubPR{Title: "feat: add new 
auth"}, expected: "enhancement"},
+               {name: "feature: prefix", pr: GitHubPR{Title: "feature: new 
login"}, expected: "enhancement"},
+               {name: "add support", pr: GitHubPR{Title: "add support for X"}, 
expected: "enhancement"},
+
+               // Default
+               {name: "unrecognized", pr: GitHubPR{Title: "Refactor internal 
logic"}, expected: "change"},
+       }
+
+       for _, tc := range tests {
+               t.Run(tc.name, func(t *testing.T) {
+                       got := categorizeByLabelsAndTitle(tc.pr)
+                       if got != tc.expected {
+                               t.Errorf("categorizeByLabelsAndTitle(%q, 
labels=%v) = %q; want %q",
+                                       tc.pr.Title, tc.pr.Labels, got, 
tc.expected)
+                       }
+               })
+       }
+}
+
+func prURL(n int) string {
+       return fmt.Sprintf("https://github.com/Azure/kubelogin/pull/%d";, n)
+}
+
+func TestCategorizePRs(t *testing.T) {
+       now := time.Now()
+       prs := []GitHubPR{
+               {Number: 1, Title: "feat: add thing", User: User{Login: 
"alice"}, HTMLURL: prURL(1), MergedAt: now},
+               {Number: 2, Title: "fix: nil pointer", User: User{Login: 
"bob"}, HTMLURL: prURL(2), MergedAt: now},
+               {Number: 3, Title: "Bump Go to 1.24", User: User{Login: 
"dependabot"}, HTMLURL: prURL(3), MergedAt: now},
+               {Number: 4, Title: "docs: update readme", User: User{Login: 
"carol"}, HTMLURL: prURL(4), MergedAt: now},
+               {Number: 5, Title: "General change", User: User{Login: 
"alice"}, HTMLURL: prURL(5), MergedAt: now},
+       }
+       // bob and carol are new; alice and dependabot are existing
+       existing := map[string]bool{"alice": true, "dependabot": true}
+
+       cats := categorizePRs(prs, existing)
+
+       if len(cats.Enhancements) != 1 || cats.Enhancements[0].Number != 1 {
+               t.Errorf("expected 1 enhancement (PR#1), got %d", 
len(cats.Enhancements))
+       }
+       if len(cats.BugFixes) != 1 || cats.BugFixes[0].Number != 2 {
+               t.Errorf("expected 1 bug fix (PR#2), got %d", 
len(cats.BugFixes))
+       }
+       if len(cats.Maintenance) != 1 || cats.Maintenance[0].Number != 3 {
+               t.Errorf("expected 1 maintenance (PR#3), got %d", 
len(cats.Maintenance))
+       }
+       if len(cats.DocUpdates) != 1 || cats.DocUpdates[0].Number != 4 {
+               t.Errorf("expected 1 doc update (PR#4), got %d", 
len(cats.DocUpdates))
+       }
+       if len(cats.Changes) != 1 || cats.Changes[0].Number != 5 {
+               t.Errorf("expected 1 general change (PR#5), got %d", 
len(cats.Changes))
+       }
+
+       // New contributors: bob (PR#2) and carol (PR#4)
+       if len(cats.NewContributors) != 2 {
+               t.Errorf("expected 2 new contributors, got %d", 
len(cats.NewContributors))
+       }
+       newLogins := map[string]bool{}
+       for _, c := range cats.NewContributors {
+               newLogins[c.Login] = true
+       }
+       if !newLogins["bob"] || !newLogins["carol"] {
+               t.Errorf("expected bob and carol as new contributors, got %v", 
newLogins)
+       }
+}
+
+func TestGenerateChangelogEntry(t *testing.T) {
+       now := time.Now()
+       cats := Categories{
+               Enhancements:    []GitHubPR{{Number: 1, Title: "feat: new 
thing", User: User{Login: "alice"}, HTMLURL: 
"https://github.com/Azure/kubelogin/pull/1";, MergedAt: now}},
+               BugFixes:        []GitHubPR{{Number: 2, Title: "fix: crash", 
User: User{Login: "bob"}, HTMLURL: "https://github.com/Azure/kubelogin/pull/2";, 
MergedAt: now}},
+               NewContributors: []Contributor{{Login: "bob", PRURL: 
"https://github.com/Azure/kubelogin/pull/2"}},
+       }
+
+       entry := generateChangelogEntry("0.2.15", "v0.2.14", cats, 
"Azure/kubelogin")
+
+       for _, want := range []string{
+               "## [0.2.15]",
+               "### Enhancements",
+               "feat: new thing",
+               "@alice",
+               "### Bug Fixes",
+               "fix: crash",
+               "@bob",
+               "### New Contributors",
+               "@bob made their first contribution",
+               "**Full Changelog**: 
https://github.com/Azure/kubelogin/compare/v0.2.14...v0.2.15";,
+       } {
+               if !strings.Contains(entry, want) {
+                       t.Errorf("expected changelog entry to contain 
%q\nGot:\n%s", want, entry)
+               }
+       }
+}
+
+func TestIsReleasePR(t *testing.T) {
+       cases := []struct {
+               title    string
+               expected bool
+       }{
+               {"v0.2.14 release", true},
+               {"0.2.14 release", true},
+               {"v1.0.0", true},
+               {"v0.2.14", true},
+               // Regular PRs — must NOT be filtered
+               {"fix: nil pointer", false},
+               {"feat: add new login flow", false},
+               {"Bump Go to 1.24.11", false},
+               {"docs: update readme", false},
+               {"[Bug Fix] - PoP token crash", false},
+               {"chore: update CHANGELOG.md for v0.2.15", false},
+       }
+       for _, tc := range cases {
+               got := isReleasePR(tc.title)
+               if got != tc.expected {
+                       t.Errorf("isReleasePR(%q) = %v; want %v", tc.title, 
got, tc.expected)
+               }
+       }
+}
+
+func TestHasLabel(t *testing.T) {
+       pr := GitHubPR{Labels: []Label{{Name: "release"}, {Name: "chore"}}}
+       if !hasLabel(pr, "release") {
+               t.Error("expected hasLabel to return true for 'release'")
+       }
+       if !hasLabel(pr, "Release") {
+               t.Error("expected hasLabel to be case-insensitive")
+       }
+       if hasLabel(pr, "bug") {
+               t.Error("expected hasLabel to return false for 'bug'")
+       }
+       empty := GitHubPR{}
+       if hasLabel(empty, "release") {
+               t.Error("expected hasLabel to return false for PR with no 
labels")
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kubelogin-0.2.14/pkg/internal/token/execCredentialPlugin.go 
new/kubelogin-0.2.15/pkg/internal/token/execCredentialPlugin.go
--- old/kubelogin-0.2.14/pkg/internal/token/execCredentialPlugin.go     
2026-01-06 19:59:04.000000000 +0100
+++ new/kubelogin-0.2.15/pkg/internal/token/execCredentialPlugin.go     
2026-02-20 03:16:58.000000000 +0100
@@ -31,18 +31,17 @@
 
 func New(o *Options) (ExecCredentialPlugin, error) {
        klog.V(10).Info(o.ToString())
-
        // Initialize PoP token cache in Options if enabled
        if o.IsPoPTokenEnabled && o.popTokenCache == nil {
                // Create PoP token cache using the official MSAL & MSAL 
extension libraries.
                popTokenCache, err := popcache.NewCache(o.AuthRecordCacheDir)
                if err != nil {
                        // Fallback: Log warning and continue without PoP token 
caching when cache creation fails
-                       klog.V(2).Infof("PoP token caching disabled due to 
secure storage failure (likely container environment): %v", err)
-                       popTokenCache = nil
-                       // Continue execution without using cached PoP tokens
+                       // Leave popTokenCache unset (nil field) so 
GetPoPTokenCache() returns an untyped nil interface.
+                       klog.Warningf("PoP token caching disabled due to secure 
storage failure (likely container environment): %v", err)
+               } else {
+                       o.setPoPTokenCache(popTokenCache)
                }
-               o.setPoPTokenCache(popTokenCache)
        }
 
        return &execCredentialPlugin{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/pkg/internal/token/options.go 
new/kubelogin-0.2.15/pkg/internal/token/options.go
--- old/kubelogin-0.2.14/pkg/internal/token/options.go  2026-01-06 
19:59:04.000000000 +0100
+++ new/kubelogin-0.2.15/pkg/internal/token/options.go  2026-02-20 
03:16:58.000000000 +0100
@@ -17,6 +17,7 @@
        "github.com/Azure/kubelogin/pkg/internal/env"
        "github.com/Azure/kubelogin/pkg/internal/pop"
        popcache "github.com/Azure/kubelogin/pkg/internal/pop/cache"
+       msalcache 
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
 )
 
 // PoPKeyProvider provides PoP keys based on the configured cache policy
@@ -378,9 +379,14 @@
        })
 }
 
-// GetPoPTokenCache returns the PoP token cache if available.
-// Returns nil if PoP is disabled or cache creation failed (e.g., container 
environments).
-func (o *Options) GetPoPTokenCache() *popcache.Cache {
+// GetPoPTokenCache returns the PoP token cache if available, or nil if PoP is
+// disabled or cache creation failed (e.g., container environments).
+// It returns the interface type so that a nil field produces an untyped nil,
+// which correctly compares as nil in downstream interface checks.
+func (o *Options) GetPoPTokenCache() msalcache.ExportReplace {
+       if o.popTokenCache == nil {
+               return nil
+       }
        return o.popTokenCache
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kubelogin-0.2.14/pkg/internal/token/options_test.go 
new/kubelogin-0.2.15/pkg/internal/token/options_test.go
--- old/kubelogin-0.2.14/pkg/internal/token/options_test.go     2026-01-06 
19:59:04.000000000 +0100
+++ new/kubelogin-0.2.15/pkg/internal/token/options_test.go     2026-02-20 
03:16:58.000000000 +0100
@@ -681,3 +681,40 @@
                })
        }
 }
+
+// TestGetPoPTokenCache_NilReturnsUntypedNil verifies that GetPoPTokenCache()
+// returns a true untyped nil (not a typed nil *Cache wrapped in an interface)
+// when the cache field is not set.
+func TestGetPoPTokenCache_NilReturnsUntypedNil(t *testing.T) {
+       t.Run("nil cache field returns interface-nil", func(t *testing.T) {
+               o := &Options{}
+               // popTokenCache is its zero value (nil *popcache.Cache)
+
+               cache := o.GetPoPTokenCache()
+
+               // This is the critical assertion: the returned interface must 
be nil.
+               if cache != nil {
+                       t.Fatal("GetPoPTokenCache() returned non-nil interface 
for nil cache field; this would cause a nil pointer panic in MSAL")
+               }
+       })
+
+       t.Run("valid cache field returns non-nil", func(t *testing.T) {
+               tmpDir := t.TempDir()
+               o := &Options{
+                       IsPoPTokenEnabled:  true,
+                       AuthRecordCacheDir: tmpDir,
+               }
+
+               // Initialize via New() which calls setPoPTokenCache on success
+               _, err := New(o)
+               if err != nil {
+                       t.Fatalf("New() failed: %v", err)
+               }
+
+               // If cache creation succeeded (depends on environment), verify 
it's non-nil
+               cache := o.GetPoPTokenCache()
+               if o.popTokenCache != nil && cache == nil {
+                       t.Fatal("GetPoPTokenCache() returned nil interface for 
non-nil cache field")
+               }
+       })
+}

++++++ kubelogin.obsinfo ++++++
--- /var/tmp/diff_new_pack.9dZW8d/_old  2026-03-02 17:36:34.213937548 +0100
+++ /var/tmp/diff_new_pack.9dZW8d/_new  2026-03-02 17:36:34.249939064 +0100
@@ -1,5 +1,5 @@
 name: kubelogin
-version: 0.2.14
-mtime: 1767725944
-commit: 6e8b5babc994d57e752e24297dd9c6f59247a1f8
+version: 0.2.15
+mtime: 1771553818
+commit: 7936910173e517c5c651d1bbd56d3143c4b1fe4e
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/kubelogin/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.kubelogin.new.29461/vendor.tar.gz differ: char 13, 
line 1

Reply via email to