Skip to content

Commit 007d01e

Browse files
authored
Merge branch 'main' into fix/nix-workflow-pr
2 parents 1eaf412 + 1eae24e commit 007d01e

9 files changed

Lines changed: 99 additions & 30 deletions

File tree

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
# Hash of Go module dependencies.
4242
# Update this after changing go.mod/go.sum:
4343
# task nix-update-hash
44-
vendorHash = "sha256-jxzvSrc07U7zSkv/CnCVD1uhklhadze1rcr/KGYiJQs=";
44+
vendorHash = "sha256-r4BIg/7Ofkq7I3+zZJ23zM41/8CemEwwvFLFOO84wPE=";
4545

4646
env.CGO_ENABLED = 0;
4747

go.mod

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ module go.datum.net/datumctl
22

33
go 1.25.8
44

5-
toolchain go1.26.3
5+
toolchain go1.26.4
66

77
require (
88
charm.land/bubbles/v2 v2.1.0
9-
charm.land/bubbletea/v2 v2.0.6
9+
charm.land/bubbletea/v2 v2.0.7
1010
charm.land/huh/v2 v2.0.3
1111
charm.land/lipgloss/v2 v2.0.3
1212
github.com/coreos/go-oidc/v3 v3.18.0
1313
github.com/go-jose/go-jose/v4 v4.1.4
14+
github.com/go-logr/logr v1.4.3
1415
github.com/google/uuid v1.6.0
1516
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1617
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
@@ -19,7 +20,7 @@ require (
1920
github.com/spf13/pflag v1.0.10
2021
github.com/zalando/go-keyring v0.2.8
2122
go.miloapis.com/activity v0.7.0
22-
go.miloapis.com/milo v0.24.11
23+
go.miloapis.com/milo v0.28.1
2324
golang.org/x/mod v0.35.0
2425
golang.org/x/oauth2 v0.36.0
2526
golang.org/x/term v0.43.0
@@ -28,6 +29,7 @@ require (
2829
k8s.io/cli-runtime v0.35.3
2930
k8s.io/client-go v0.35.3
3031
k8s.io/component-base v0.35.3
32+
k8s.io/klog/v2 v2.140.0
3133
k8s.io/kubectl v0.35.3
3234
sigs.k8s.io/controller-runtime v0.23.3
3335
sigs.k8s.io/yaml v1.6.0
@@ -43,7 +45,7 @@ require (
4345
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4446
github.com/chai2010/gettext-go v1.0.3 // indirect
4547
github.com/charmbracelet/colorprofile v0.4.3 // indirect
46-
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect
48+
github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654 // indirect
4749
github.com/charmbracelet/x/ansi v0.11.7 // indirect
4850
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
4951
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
@@ -63,7 +65,6 @@ require (
6365
github.com/fatih/camelcase v1.0.0 // indirect
6466
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
6567
github.com/go-errors/errors v1.5.1 // indirect
66-
github.com/go-logr/logr v1.4.3 // indirect
6768
github.com/go-openapi/jsonpointer v0.22.4 // indirect
6869
github.com/go-openapi/jsonreference v0.21.4 // indirect
6970
github.com/go-openapi/swag v0.25.4 // indirect
@@ -113,21 +114,20 @@ require (
113114
github.com/x448/float16 v0.8.4 // indirect
114115
github.com/xlab/treeprint v1.2.0 // indirect
115116
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
116-
go.opentelemetry.io/otel v1.40.0 // indirect
117-
go.opentelemetry.io/otel/trace v1.40.0 // indirect
117+
go.opentelemetry.io/otel v1.41.0 // indirect
118+
go.opentelemetry.io/otel/trace v1.41.0 // indirect
118119
go.yaml.in/yaml/v2 v2.4.3 // indirect
119120
go.yaml.in/yaml/v3 v3.0.4 // indirect
120121
golang.org/x/net v0.49.0 // indirect
121122
golang.org/x/sync v0.20.0 // indirect
122-
golang.org/x/sys v0.44.0 // indirect
123+
golang.org/x/sys v0.45.0 // indirect
123124
golang.org/x/text v0.33.0 // indirect
124125
golang.org/x/time v0.15.0 // indirect
125126
google.golang.org/protobuf v1.36.11 // indirect
126127
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
127128
gopkg.in/inf.v0 v0.9.1 // indirect
128129
k8s.io/apiserver v0.35.3 // indirect
129130
k8s.io/component-helpers v0.35.3 // indirect
130-
k8s.io/klog/v2 v2.130.1 // indirect
131131
k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 // indirect
132132
k8s.io/metrics v0.35.3 // indirect
133133
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect

go.sum

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
22
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
33
charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo=
44
charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g=
5+
charm.land/bubbletea/v2 v2.0.7 h1:7qw2tTAVar7m7klOPBYfTB0mniv/RuexsYwMRNxSeL0=
6+
charm.land/bubbletea/v2 v2.0.7/go.mod h1:DGW2q8gvzHnOpMpZTORs0aySVHCox5C+2Svk0fci1qs=
57
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
68
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
79
charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU=
@@ -32,6 +34,8 @@ github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex
3234
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
3335
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac=
3436
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM=
37+
github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654 h1:FpSYhY28ucg9ZRr+2wj67FAQ0Ey5yiK0072PmRDJNek=
38+
github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654/go.mod h1:hFpumms29Smx3LStRfku8vcCTBe1Kq8aCXtHUJa3mjY=
3539
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
3640
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
3741
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
@@ -241,16 +245,16 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
241245
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
242246
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
243247
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
244-
go.miloapis.com/activity v0.6.0 h1:scQ+o+71I41Ba8hZuOLNoyTrr4psdhvAEKgqdA/deVw=
245-
go.miloapis.com/activity v0.6.0/go.mod h1:Sh2Irbq6siJcfq17nLjHvm4JHN/2Csc5YCHB+ycz20c=
246248
go.miloapis.com/activity v0.7.0 h1:Nmc5XzA4oEMTko5/ciJAeERVk18FaSnRpTBo0Sm89YU=
247249
go.miloapis.com/activity v0.7.0/go.mod h1:Sh2Irbq6siJcfq17nLjHvm4JHN/2Csc5YCHB+ycz20c=
248-
go.miloapis.com/milo v0.24.11 h1:rByXDKbP4ZEN0I/z1C2RyUCyQi0NWrITLqoQILSAn2E=
249-
go.miloapis.com/milo v0.24.11/go.mod h1:xOFYvUsvSZV3z6eow5YdB5C/qRQf2s/5/arcfJs5XPg=
250-
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
251-
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
252-
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
253-
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
250+
go.miloapis.com/milo v0.27.0 h1:lbUZJhWBgFmvdINyt6oonWEAW2bwuoW9CVa9D1mCYSI=
251+
go.miloapis.com/milo v0.27.0/go.mod h1:p9O2kk194mvoL8rhqjwb+LWB+GIyY4vQqiTowwibVWo=
252+
go.miloapis.com/milo v0.28.1 h1:30bQS4EwadbOBsn3UC8qSBENN1koQgsQpcvqPjXfmlI=
253+
go.miloapis.com/milo v0.28.1/go.mod h1:p9O2kk194mvoL8rhqjwb+LWB+GIyY4vQqiTowwibVWo=
254+
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
255+
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
256+
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
257+
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
254258
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
255259
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
256260
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -273,10 +277,10 @@ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
273277
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
274278
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
275279
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
276-
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
277-
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
278280
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
279281
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
282+
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
283+
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
280284
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
281285
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
282286
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
@@ -317,6 +321,8 @@ k8s.io/component-helpers v0.35.3 h1:Rl2p3wNMC0YU21rziLkWXavr7MwkB5Td3lNZ/+gYGm8=
317321
k8s.io/component-helpers v0.35.3/go.mod h1:8BkyfcBA6XsCtFYxDB+mCfZqM6P39Aco12AKigNn0C8=
318322
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
319323
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
324+
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
325+
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
320326
k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 h1:V+sn9a/1fEYDGwnllCmqXBk8x7obZ+hl869Q3Abumkg=
321327
k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
322328
k8s.io/kubectl v0.35.3 h1:1KqSYXk/sodU7VeDvK6atX2kAGUZd2QTeR5K7Hb9r9w=

internal/cmd/plugin/helpers.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"crypto/sha256"
1010
"encoding/hex"
1111
"encoding/json"
12+
"errors"
1213
"fmt"
1314
"io"
1415
"net/http"
@@ -23,9 +24,24 @@ import (
2324

2425
"golang.org/x/mod/semver"
2526

27+
customerrors "go.datum.net/datumctl/internal/errors"
2628
"go.datum.net/datumctl/internal/pluginstore"
2729
)
2830

31+
// indexFetchUserError converts a RefreshIndex failure into a user-facing error,
32+
// attaching actionable guidance when the cause is recognizable (e.g. a GitHub
33+
// token in the environment being rejected by the index host).
34+
func indexFetchUserError(err error) error {
35+
msg := "could not fetch the plugin index: " + err.Error()
36+
var fe *pluginstore.IndexFetchError
37+
if errors.As(err, &fe) {
38+
if hint := fe.Hint(); hint != "" {
39+
return customerrors.NewUserErrorWithHint(msg, hint)
40+
}
41+
}
42+
return customerrors.NewUserError(msg)
43+
}
44+
2945
const (
3046
pluginDownloadTimeout = 60 * time.Second
3147
manifestReadTimeout = 5 * time.Second

internal/cmd/plugin/install.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ The plugin binary is written to the managed plugins directory
7676
// Curated index path.
7777
idx, idxErr := loadOrRefreshIndex(cmd)
7878
if idxErr != nil {
79-
return customerrors.NewUserError("could not fetch plugin index: " + idxErr.Error())
79+
return indexFetchUserError(idxErr)
8080
}
8181
entry, pluginName, binaryPath, installErr := installPlugin(cmd.Context(), pluginsDir, arg, "", currentVersion, idx)
8282
if installErr != nil {

internal/cmd/plugin/search.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/spf13/cobra"
99

10-
customerrors "go.datum.net/datumctl/internal/errors"
1110
"go.datum.net/datumctl/internal/pluginstore"
1211
)
1312

@@ -33,7 +32,7 @@ Run 'datumctl plugin install <name>' to install a plugin listed here.`,
3332
idx, err = pluginstore.RefreshIndex(cmd.Context())
3433
if err != nil {
3534
if idx == nil {
36-
return customerrors.NewUserError("could not fetch plugin index: " + err.Error())
35+
return indexFetchUserError(err)
3736
}
3837
fmt.Fprintf(cmd.ErrOrStderr(), "warning: index refresh failed (%v), showing cached results\n", err)
3938
}

internal/cmd/plugin/upgrade.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ compatibility validation.`,
5454
fmt.Fprintf(cmd.ErrOrStderr(), "warning: index refresh failed (%v), using cached index\n", refreshErr)
5555
default:
5656
// No cache at all.
57-
return customerrors.NewUserError(fmt.Sprintf("could not fetch plugin index: %v", refreshErr))
57+
return indexFetchUserError(refreshErr)
5858
}
5959

6060
var newEntry *pluginstore.InstalledPlugin

internal/cmd/root.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/hex"
77
"fmt"
88
"io"
9+
"net/http"
910
"os"
1011
"path/filepath"
1112
"strings"
@@ -14,6 +15,7 @@ import (
1415
"github.com/spf13/cobra"
1516
activity "go.miloapis.com/activity/pkg/cmd"
1617
"k8s.io/cli-runtime/pkg/genericclioptions"
18+
"k8s.io/client-go/transport"
1719
componentversion "k8s.io/component-base/version"
1820
"k8s.io/kubectl/pkg/cmd/apiresources"
1921
"k8s.io/kubectl/pkg/cmd/apply"
@@ -108,6 +110,11 @@ Get started:
108110
// plugin dispatch logic can handle them before Cobra rejects them.
109111
Args: cobra.ArbitraryArgs,
110112
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
113+
// Make the -v flag dump HTTP requests from bare net/http callers,
114+
// as client-go already does for kubectl-backed commands. No-op
115+
// below -v 6.
116+
http.DefaultTransport = transport.DebugWrappers(http.DefaultTransport)
117+
111118
format, _ := cmd.Flags().GetString("error-format")
112119
switch format {
113120
case customerrors.FormatHuman, customerrors.FormatJSON, customerrors.FormatYAML:

internal/pluginstore/index.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ func RefreshIndex(ctx context.Context) (*CachedIndex, error) {
133133
}
134134

135135
// Attach GitHub token if available.
136-
if token := githubToken(); token != "" {
136+
token, tokenSource := githubTokenWithSource()
137+
if token != "" {
137138
req.Header.Set("Authorization", "Bearer "+token)
138139
}
139140
req.Header.Set("User-Agent", "datumctl-plugin-index")
@@ -145,7 +146,12 @@ func RefreshIndex(ctx context.Context) (*CachedIndex, error) {
145146
defer resp.Body.Close()
146147

147148
if resp.StatusCode != http.StatusOK {
148-
return degradedFallback(fmt.Errorf("fetch plugin index: HTTP %s", resp.Status))
149+
return degradedFallback(&IndexFetchError{
150+
URL: IndexURL,
151+
StatusCode: resp.StatusCode,
152+
Status: resp.Status,
153+
TokenSource: tokenSource,
154+
})
149155
}
150156

151157
raw, err := io.ReadAll(resp.Body)
@@ -202,10 +208,45 @@ func degradedFallback(origErr error) (*CachedIndex, error) {
202208
return cached, origErr
203209
}
204210

205-
// githubToken returns a GitHub personal access token from the environment.
206-
func githubToken() string {
211+
// githubTokenWithSource returns a GitHub personal access token from the
212+
// environment along with the name of the variable it came from (empty when no
213+
// token is set).
214+
func githubTokenWithSource() (token, source string) {
207215
if t := os.Getenv("DATUMCTL_GITHUB_TOKEN"); t != "" {
208-
return t
216+
return t, "DATUMCTL_GITHUB_TOKEN"
209217
}
210-
return os.Getenv("GITHUB_TOKEN")
218+
if t := os.Getenv("GITHUB_TOKEN"); t != "" {
219+
return t, "GITHUB_TOKEN"
220+
}
221+
return "", ""
222+
}
223+
224+
// IndexFetchError is returned by RefreshIndex when the index host responds with
225+
// a non-OK HTTP status. It carries enough context for the command layer to
226+
// render actionable guidance via Hint.
227+
type IndexFetchError struct {
228+
URL string
229+
StatusCode int
230+
Status string // HTTP status text, e.g. "404 Not Found"
231+
TokenSource string // env var the Authorization token came from, "" if none
232+
}
233+
234+
func (e *IndexFetchError) Error() string {
235+
return fmt.Sprintf("the plugin index host returned HTTP %s", e.Status)
236+
}
237+
238+
// Hint returns actionable guidance for resolving the failure, or "" when none
239+
// applies. The common case: a GitHub token in the environment is sent to the
240+
// public index host, which rejects it with a 401/403/404.
241+
func (e *IndexFetchError) Hint() string {
242+
switch e.StatusCode {
243+
case http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound:
244+
if e.TokenSource != "" {
245+
return fmt.Sprintf(
246+
"A GitHub token from $%s is being sent to the index host, which is the likely cause. "+
247+
"The public plugin index needs no authentication; unset that variable and retry.",
248+
e.TokenSource)
249+
}
250+
}
251+
return ""
211252
}

0 commit comments

Comments
 (0)