diff --git a/changelog.md b/changelog.md index 3f53c3c879..0c17fa75d0 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,10 @@ ## Unreleased +## [`v28.11.1`](https://github.com/ignite/cli/releases/tag/v28.11.1) + +- [#4813](https://github.com/ignite/cli/pull/4813) Fetch fallback buf token. + ## [`v28.11.0`](https://github.com/ignite/cli/releases/tag/v28.11.0) ### Changes diff --git a/ignite/cmd/bubblemodel/chain_serve.go b/ignite/cmd/bubblemodel/chain_serve.go index 1a66f124f0..f8b4d2e5f5 100644 --- a/ignite/cmd/bubblemodel/chain_serve.go +++ b/ignite/cmd/bubblemodel/chain_serve.go @@ -7,7 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" - "github.com/ignite/cli/v28/ignite/pkg/announcements" + "github.com/ignite/cli/v28/ignite/internal/announcements" "github.com/ignite/cli/v28/ignite/pkg/cliui/colors" "github.com/ignite/cli/v28/ignite/pkg/cliui/icons" cliuimodel "github.com/ignite/cli/v28/ignite/pkg/cliui/model" diff --git a/ignite/cmd/cmd.go b/ignite/cmd/cmd.go index 7885a27cc9..8d04c2c00e 100644 --- a/ignite/cmd/cmd.go +++ b/ignite/cmd/cmd.go @@ -13,7 +13,7 @@ import ( flag "github.com/spf13/pflag" "github.com/ignite/cli/v28/ignite/config" - "github.com/ignite/cli/v28/ignite/pkg/announcements" + "github.com/ignite/cli/v28/ignite/internal/announcements" "github.com/ignite/cli/v28/ignite/pkg/cache" "github.com/ignite/cli/v28/ignite/pkg/cliui" uilog "github.com/ignite/cli/v28/ignite/pkg/cliui/log" diff --git a/ignite/internal/analytics/analytics.go b/ignite/internal/analytics/analytics.go index fbe618a552..493927251d 100644 --- a/ignite/internal/analytics/analytics.go +++ b/ignite/internal/analytics/analytics.go @@ -13,10 +13,10 @@ import ( "github.com/spf13/cobra" "github.com/ignite/cli/v28/ignite/config" + "github.com/ignite/cli/v28/ignite/internal/sentry" "github.com/ignite/cli/v28/ignite/pkg/gitpod" "github.com/ignite/cli/v28/ignite/pkg/matomo" "github.com/ignite/cli/v28/ignite/pkg/randstr" - "github.com/ignite/cli/v28/ignite/pkg/sentry" "github.com/ignite/cli/v28/ignite/version" ) diff --git a/ignite/pkg/announcements/announcement.go b/ignite/internal/announcements/announcement.go similarity index 78% rename from ignite/pkg/announcements/announcement.go rename to ignite/internal/announcements/announcement.go index 6c66e740f3..78e2879630 100644 --- a/ignite/pkg/announcements/announcement.go +++ b/ignite/internal/announcements/announcement.go @@ -11,14 +11,10 @@ import ( ) var ( - SurveyLink = "https://bit.ly/3WZS2uS" - APIURL = "http://announcements.ignite.com/v1/announcements" + SurveyLink = "https://bit.ly/3WZS2uS" + AnnouncementURL = "https://api.ignite.com/v1/announcements" ) -type api struct { - Announcements []announcement `json:"announcements"` -} - type announcement struct { ID string `json:"id"` Text string `json:"text"` @@ -28,13 +24,16 @@ type announcement struct { // Fetch fetches the latest announcements from the API. func Fetch() string { - resp, err := http.Get(APIURL) //nolint:gosec + resp, err := http.Get(AnnouncementURL) //nolint:gosec if err != nil || resp.StatusCode != 200 { return fallbackData() } defer resp.Body.Close() - var data api + type response struct { + Announcements []announcement `json:"announcements"` + } + var data response if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return fallbackData() } diff --git a/ignite/pkg/announcements/announcement_test.go b/ignite/internal/announcements/announcement_test.go similarity index 87% rename from ignite/pkg/announcements/announcement_test.go rename to ignite/internal/announcements/announcement_test.go index 80e8a1a38a..407cc5df29 100644 --- a/ignite/pkg/announcements/announcement_test.go +++ b/ignite/internal/announcements/announcement_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/ignite/cli/v28/ignite/pkg/announcements" + "github.com/ignite/cli/v28/ignite/internal/announcements" ) func TestFetchAnnouncements(t *testing.T) { @@ -52,9 +52,9 @@ func TestFetchAnnouncements(t *testing.T) { })) defer server.Close() - originalAPI := announcements.APIURL - announcements.APIURL = server.URL - defer func() { announcements.APIURL = originalAPI }() + originalAPI := announcements.AnnouncementURL + announcements.AnnouncementURL = server.URL + defer func() { announcements.AnnouncementURL = originalAPI }() result := announcements.Fetch() if result != tt.expected { diff --git a/ignite/internal/buf/buf.go b/ignite/internal/buf/buf.go new file mode 100644 index 0000000000..c2ee5eec2d --- /dev/null +++ b/ignite/internal/buf/buf.go @@ -0,0 +1,39 @@ +package buf + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/ignite/cli/v28/ignite/pkg/errors" +) + +var BufTokenURL = "https://buf.ignite.com" //nolint:gosec // URL is hardcoded and not user-provided + +// FetchToken fetches the buf token from the Ignite API. +func FetchToken() (string, error) { + client := &http.Client{ + Timeout: 5 * time.Second, + } + + resp, err := client.Get(BufTokenURL) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", errors.Errorf("HTTP request failed with status code: %d", resp.StatusCode) + } + + type tokenResponse struct { + Token string `json:"token"` + } + var tokenResp tokenResponse + + if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { + return "", err + } + + return tokenResp.Token, nil +} diff --git a/ignite/internal/buf/buf_test.go b/ignite/internal/buf/buf_test.go new file mode 100644 index 0000000000..4488eed656 --- /dev/null +++ b/ignite/internal/buf/buf_test.go @@ -0,0 +1,69 @@ +package buf_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/ignite/cli/v28/ignite/internal/buf" + "github.com/stretchr/testify/require" +) + +func TestFetchToken(t *testing.T) { + tests := []struct { + name string + serverResponse string + statusCode int + expectedToken string + expectError bool + }{ + { + name: "successful token fetch", + serverResponse: `{"token":"test_token_123"}`, + statusCode: http.StatusOK, + expectedToken: "test_token_123", + expectError: false, + }, + { + name: "server error", + serverResponse: `{"error":"internal server error"}`, + statusCode: http.StatusInternalServerError, + expectedToken: "", + expectError: true, + }, + { + name: "invalid json response", + serverResponse: `invalid json`, + statusCode: http.StatusOK, + expectedToken: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(tt.statusCode) + w.Write([]byte(tt.serverResponse)) + })) + defer server.Close() + + // Temporarily override the endpoint + originalEndpoint := buf.BufTokenURL + buf.BufTokenURL = server.URL + defer func() { + buf.BufTokenURL = originalEndpoint + }() + + token, err := buf.FetchToken() + if tt.expectError { + require.Error(t, err) + require.Empty(t, token) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedToken, token) + } + }) + } +} diff --git a/ignite/pkg/sentry/sentry.go b/ignite/internal/sentry/sentry.go similarity index 100% rename from ignite/pkg/sentry/sentry.go rename to ignite/internal/sentry/sentry.go diff --git a/ignite/pkg/cosmosgen/generate_typescript.go b/ignite/pkg/cosmosgen/generate_typescript.go index 26b84204f8..027e6eae7c 100644 --- a/ignite/pkg/cosmosgen/generate_typescript.go +++ b/ignite/pkg/cosmosgen/generate_typescript.go @@ -7,19 +7,25 @@ import ( "sort" "strings" - "golang.org/x/sync/errgroup" - + "github.com/ignite/cli/v28/ignite/internal/buf" "github.com/ignite/cli/v28/ignite/pkg/cache" "github.com/ignite/cli/v28/ignite/pkg/cosmosanalysis/module" "github.com/ignite/cli/v28/ignite/pkg/cosmosbuf" "github.com/ignite/cli/v28/ignite/pkg/dirchange" "github.com/ignite/cli/v28/ignite/pkg/gomodulepath" + "golang.org/x/sync/errgroup" ) -var dirchangeCacheNamespace = "generate.typescript.dirchange" +var ( + bufTokenEnvName = "BUF_TOKEN" + + dirchangeCacheNamespace = "generate.typescript.dirchange" +) type tsGenerator struct { g *generator + // hasLocalBufToken indicates whether the user had already a local Buf token. + hasLocalBufToken bool } type generatePayload struct { @@ -28,7 +34,18 @@ type generatePayload struct { } func newTSGenerator(g *generator) *tsGenerator { - return &tsGenerator{g} + tsg := &tsGenerator{g: g} + + if os.Getenv(bufTokenEnvName) == "" { + token, err := buf.FetchToken() + if err == nil { + os.Setenv(bufTokenEnvName, token) + } + } else { + tsg.hasLocalBufToken = true + } + + return tsg } func (g *generator) tsTemplate() string { @@ -55,6 +72,12 @@ func (g *generator) generateTS(ctx context.Context) error { }) tsg := newTSGenerator(g) + defer func() { + // unset ignite buf token from env + if !tsg.hasLocalBufToken { + os.Unsetenv(bufTokenEnvName) + } + }() if err := tsg.generateModuleTemplates(ctx); err != nil { return err } diff --git a/ignite/pkg/errors/xerrors.go b/ignite/pkg/errors/xerrors.go index bf7815d6af..39fd8be589 100644 --- a/ignite/pkg/errors/xerrors.go +++ b/ignite/pkg/errors/xerrors.go @@ -26,7 +26,7 @@ func New(msg string) error { } // Errorf aliases Newf(). -func Errorf(format string, args ...interface{}) error { +func Errorf(format string, args ...any) error { err := errors.Errorf(format, args...) sentry.CaptureException(err) return err @@ -42,16 +42,20 @@ func WithStack(err error) error { // Wrap wraps an error with a message prefix. A stack trace is retained. func Wrap(err error, msg string) error { errWrap := errors.Wrap(err, msg) - sentry.CaptureException(errWrap) + if err != nil { + sentry.CaptureException(errWrap) + } return errWrap } // Wrapf wraps an error with a formatted message prefix. A stack // trace is also retained. If the format is empty, no prefix is added, // but the extra arguments are still processed for reportable strings. -func Wrapf(err error, format string, args ...interface{}) error { +func Wrapf(err error, format string, args ...any) error { errWrap := errors.Wrapf(err, format, args...) - sentry.CaptureException(errWrap) + if err != nil { + sentry.CaptureException(errWrap) + } return errWrap } @@ -73,4 +77,4 @@ func Is(err, reference error) bool { return errors.Is(err, reference) } // matches a type if it is assignable to the target type, or if it has a method // As(interface{}) bool such that As(target) returns true. As will panic if target // is not a non-nil pointer to a type which implements error or is of interface type. -func As(err error, target interface{}) bool { return errors.As(err, target) } +func As(err error, target any) bool { return errors.As(err, target) }