diff --git a/v2/client/client.go b/v2/client/client.go index 627ba25..e7669e9 100644 --- a/v2/client/client.go +++ b/v2/client/client.go @@ -3,6 +3,7 @@ package client import ( "fmt" + "github.com/skyflowapi/skyflow-go/v2/internal/helpers" "github.com/skyflowapi/skyflow-go/v2/internal/validation" "github.com/skyflowapi/skyflow-go/v2/internal/vault/controller" vaultutils "github.com/skyflowapi/skyflow-go/v2/utils/common" @@ -37,6 +38,17 @@ func NewSkyflow(opts ...Option) (*Skyflow, *error.SkyflowError) { } } + sdkVersion := helpers.CurrentSDKVersion() + if helpers.IsNonGaVersion(sdkVersion) { + var vaultConfigs []vaultutils.VaultConfig + for _, svc := range client.vaultServices { + vaultConfigs = append(vaultConfigs, *svc.config) + } + if helpers.AnyVaultIsProd(vaultConfigs) { + logger.Warn(fmt.Sprintf(logs.BETA_BUILD_WARNING, sdkVersion)) + } + } + logger.Info(logs.CLIENT_INITIALIZED) return client, nil } diff --git a/v2/client/client_test.go b/v2/client/client_test.go index 8bb63c8..99e168a 100644 --- a/v2/client/client_test.go +++ b/v2/client/client_test.go @@ -1,6 +1,7 @@ package client import ( + "bytes" "fmt" "os" "testing" @@ -2166,3 +2167,35 @@ var _ = Describe("Skyflow lifecycle: after Vault/Detect/Connection activated", f }) }) }) + +// SK-2963: beta-build-in-prod warning. +// +// helpers.CurrentSDKVersion() falls back to the real, GA constants.SDK_VERSION whenever +// go test's own build info isn't a resolved module version (see helpers_test.go's +// "CurrentSDKVersion" spec) - so unlike a real beta-tagged consumer, NewSkyflow() in this +// suite can never actually take the "non-GA" branch. The logic that decides *whether* to +// warn (IsNonGaVersion, AnyVaultIsProd) is covered directly in helpers_test.go; this only +// proves the real wiring stays silent against today's real GA build, which is what every +// test run actually exercises. +var _ = Describe("Beta build warning wiring", func() { + var buf bytes.Buffer + + BeforeEach(func() { + buf.Reset() + logger.SetOutput(&buf) + logger.SetLogLevel(logger.WARN) + }) + AfterEach(func() { + logger.SetOutput(os.Stderr) + logger.SetLogLevel(logger.ERROR) + }) + + It("does not warn for the real GA build even against a PROD vault", func() { + _, err := NewSkyflow( + WithLogLevel(logger.WARN), + WithVaults(common.VaultConfig{VaultId: "v1", ClusterId: "cluster1", Env: common.PROD}), + ) + Expect(err).To(BeNil()) + Expect(buf.String()).ToNot(ContainSubstring("beta/pre-release build")) + }) +}) diff --git a/v2/internal/helpers/helpers.go b/v2/internal/helpers/helpers.go index a392ef4..a1b603e 100644 --- a/v2/internal/helpers/helpers.go +++ b/v2/internal/helpers/helpers.go @@ -16,6 +16,7 @@ import ( "os" "regexp" "runtime" + "runtime/debug" "time" "github.com/skyflowapi/skyflow-go/v2/internal/generated/core" @@ -314,6 +315,63 @@ func GetURLWithEnv(env common.Env, clusterId string) string { return url } +// gaVersionPattern matches a clean public release (e.g. "v2.1.0"); anything else - +// "v2.1.0-beta.1", "v2.1.0-dev.abc1234" - is a non-GA build. +var gaVersionPattern = regexp.MustCompile(`^v?\d+\.\d+\.\d+$`) + +// IsNonGaVersion reports whether version is a beta/dev pre-release build rather than a +// plain public release. +func IsNonGaVersion(version string) bool { + return !gaVersionPattern.MatchString(version) +} + +// AnyVaultIsProd reports whether any of the given vault configs resolves to Env.PROD. +// PROD is Env's zero value (see utils/common.Env), so an unset Env already defaults to +// it, same as GetURLWithEnv's own default case above. +func AnyVaultIsProd(vaultConfigs []common.VaultConfig) bool { + for _, vaultConfig := range vaultConfigs { + if vaultConfig.Env == common.PROD { + return true + } + } + return false +} + +// skyflowGoModulePath must match the module directive in go.mod. +const skyflowGoModulePath = "github.com/skyflowapi/skyflow-go/v2" + +// develVersion is what debug.ReadBuildInfo() reports for Main.Version when the binary +// wasn't built as a proper versioned module dependency (e.g. `go test` run against this +// module's own source, or a local `go build` from within it) - not a meaningful version. +const develVersion = "(devel)" + +// CurrentSDKVersion returns the version of this module as actually resolved by the +// importing consumer's go.mod (e.g. "v2.1.0-beta.1"), read from the running binary's +// embedded build info. constants.SDK_VERSION is a manually maintained literal that isn't +// bumped per release (see internal/constants/constants.go), so on its own it can't be +// trusted to detect a beta/dev build - this reads the ground truth instead, falling back +// to the constant when build info isn't meaningful (e.g. `go test` against this module's +// own source, where Main.Version is always "(devel)", see develVersion below). +func CurrentSDKVersion() string { + info, ok := debug.ReadBuildInfo() + if !ok { + return constants.SDK_VERSION + } + if info.Main.Path == skyflowGoModulePath && isResolvedVersion(info.Main.Version) { + return info.Main.Version + } + for _, dep := range info.Deps { + if dep.Path == skyflowGoModulePath && isResolvedVersion(dep.Version) { + return dep.Version + } + } + return constants.SDK_VERSION +} + +func isResolvedVersion(version string) bool { + return version != "" && version != develVersion +} + func ParseTokenizeResponse(apiResponse vaultapis.V1TokenizeResponse) *common.TokenizeResponse { var tokens []string for _, record := range apiResponse.GetRecords() { diff --git a/v2/internal/helpers/helpers_test.go b/v2/internal/helpers/helpers_test.go index 6b31cc1..8520a9c 100644 --- a/v2/internal/helpers/helpers_test.go +++ b/v2/internal/helpers/helpers_test.go @@ -20,6 +20,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + constants "github.com/skyflowapi/skyflow-go/v2/internal/constants" vaultapis "github.com/skyflowapi/skyflow-go/v2/internal/generated" "github.com/skyflowapi/skyflow-go/v2/internal/generated/core" . "github.com/skyflowapi/skyflow-go/v2/internal/helpers" @@ -1867,3 +1868,56 @@ var _ = Describe("Deprecation warning logs", func() { }) }) }) + +// SK-2963: beta-build-in-prod warning. +var _ = Describe("IsNonGaVersion", func() { + It("treats a plain semver release as GA", func() { + Expect(IsNonGaVersion("v2.1.0")).To(BeFalse()) + Expect(IsNonGaVersion("2.11.3")).To(BeFalse()) + }) + It("treats a beta suffix as non-GA", func() { + Expect(IsNonGaVersion("v2.1.0-beta.1")).To(BeTrue()) + }) + It("treats a dev suffix as non-GA", func() { + Expect(IsNonGaVersion("v2.1.0-dev.abc1234")).To(BeTrue()) + }) + It("treats an empty or garbage string as non-GA", func() { + Expect(IsNonGaVersion("")).To(BeTrue()) + Expect(IsNonGaVersion("not-a-version")).To(BeTrue()) + }) +}) + +var _ = Describe("AnyVaultIsProd", func() { + It("returns false for an empty list", func() { + Expect(AnyVaultIsProd(nil)).To(BeFalse()) + Expect(AnyVaultIsProd([]common.VaultConfig{})).To(BeFalse()) + }) + It("returns false when no vault is PROD", func() { + configs := []common.VaultConfig{ + {VaultId: "v1", Env: common.DEV}, + {VaultId: "v2", Env: common.SANDBOX}, + {VaultId: "v3", Env: common.STAGE}, + } + Expect(AnyVaultIsProd(configs)).To(BeFalse()) + }) + It("returns true when one of several vaults is PROD", func() { + configs := []common.VaultConfig{ + {VaultId: "v1", Env: common.DEV}, + {VaultId: "v2", Env: common.PROD}, + } + Expect(AnyVaultIsProd(configs)).To(BeTrue()) + }) + It("treats an unset Env as PROD, matching GetURLWithEnv's own default", func() { + configs := []common.VaultConfig{{VaultId: "v1"}} + Expect(AnyVaultIsProd(configs)).To(BeTrue()) + }) +}) + +var _ = Describe("CurrentSDKVersion", func() { + It("falls back to constants.SDK_VERSION when build info isn't a resolved module version", func() { + // go test builds this module's own source, so debug.ReadBuildInfo().Main.Version + // is always "(devel)" here - this exercises (and documents) the fallback path; + // the real-consumer path can only be exercised by an actual downstream module. + Expect(CurrentSDKVersion()).To(Equal(constants.SDK_VERSION)) + }) +}) diff --git a/v2/utils/messages/info_logs.go b/v2/utils/messages/info_logs.go index b82fbb7..05856c9 100644 --- a/v2/utils/messages/info_logs.go +++ b/v2/utils/messages/info_logs.go @@ -105,4 +105,6 @@ const ( DEPRECATED_RESPONSE_KEY_SKYFLOW_ID_UPDATE = SDK_LOG_PREFIX + "Deprecated: response key 'skyflowId' is deprecated and will be removed in a future version. Use 'SkyflowId' instead." DEPRECATED_RESPONSE_KEY_TOKENIZED_DATA = SDK_LOG_PREFIX + "Deprecated: response key 'tokenized_data' is deprecated and will be removed in a future version. Use 'TokenizedData' instead." DEPRECATED_FIELD_REQUEST_INDEX = SDK_LOG_PREFIX + "Deprecated: field 'request_index' is deprecated and will be removed in a future version. Use 'RequestIndex' instead." + + BETA_BUILD_WARNING = SDK_LOG_PREFIX + "This is a beta/pre-release build of the Skyflow SDK (%s). Beta builds are intended for acceptance testing only - you appear to be connecting to a Production vault. Contact your Skyflow representative before using this build in Production." )