mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-19 16:27:37 +00:00
Fix RC4 release validation blockers
This commit is contained in:
parent
f149c5d643
commit
96c2e160c9
12 changed files with 118 additions and 65 deletions
|
|
@ -2,7 +2,7 @@ version: '3.8'
|
|||
|
||||
services:
|
||||
pulse:
|
||||
image: ${PULSE_IMAGE:-rcourtman/pulse:6.0.0-rc.3}
|
||||
image: ${PULSE_IMAGE:-rcourtman/pulse:6.0.0-rc.4}
|
||||
container_name: pulse
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
|
|
|
|||
|
|
@ -93,13 +93,36 @@ The current RC packet now points at `v6.0.0-rc.4`:
|
|||
- `docs/release-control/v6/internal/subsystems/deployment-installability.md`
|
||||
- records that RC4 follows the later-corrective-RC packet requirements for
|
||||
rollback, trust-root continuity, and release-control evidence
|
||||
- `docker-compose.yml`
|
||||
- pins the default Pulse image tag to `6.0.0-rc.4`
|
||||
- `scripts/install-docker.sh`
|
||||
- pins the standalone installer fallback image version to `6.0.0-rc.4`
|
||||
- `internal/config/billing_state_test.go`
|
||||
- asserts that billing-state normalization scrubs retired monitored-system
|
||||
limit metadata instead of preserving it as entitlement state
|
||||
- `tests/migration/v5_full_upgrade_test.go` and
|
||||
`tests/migration/v5_real_exchange_upgrade_test.go`
|
||||
- align v5-to-v6 exchange proof with the canonical self-hosted contract:
|
||||
migrated self-hosted plans preserve plan identity and paid continuity
|
||||
without reintroducing monitored-system caps
|
||||
|
||||
## Outcome
|
||||
|
||||
The audit did not identify a new unhandled code blocker from the commit range.
|
||||
It did identify a release-packet currentness gap because the repo still pointed
|
||||
operators at `rc.3` while the branch had moved beyond the published `rc.3`
|
||||
tag. The gap is addressed by the RC4 packet before release workflow dispatch.
|
||||
The audit did not identify a new unhandled code blocker from the feature/runtime
|
||||
commit range. It did identify a release-packet currentness gap because the repo
|
||||
still pointed operators at `rc.3` while the branch had moved beyond the
|
||||
published `rc.3` tag. The initial draft release workflow also exposed two
|
||||
release-validation gaps before publication:
|
||||
|
||||
- Docker Compose and turnkey Docker install defaults still pinned the RC3 image
|
||||
tag.
|
||||
- Backend migration and billing-state tests still expected the retired
|
||||
`max_monitored_systems` contract to survive in self-hosted entitlement state.
|
||||
|
||||
Both gaps are addressed before publishing RC4. The corrected tests assert the
|
||||
canonical root contract: self-hosted v6 plans preserve paid continuity without
|
||||
monitored-system volume caps, and `max_monitored_systems` remains only as
|
||||
legacy metadata to scrub.
|
||||
|
||||
No public issue comment, retitle, closure, or customer-facing message was made
|
||||
as part of this packet update.
|
||||
|
|
@ -112,3 +135,8 @@ as part of this packet update.
|
|||
- `PYTHONPATH=scripts/release_control python3 -m unittest scripts.release_control.release_promotion_policy_test.ReleasePromotionPolicyTest.test_release_notes_index_points_at_current_rc_packet scripts.release_control.release_promotion_policy_test.ReleasePromotionPolicyTest.test_operator_support_packs_keep_free_first_paid_continuity_wording scripts.release_control.release_promotion_policy_test.ReleasePromotionPolicyTest.test_version_file_matches_current_rc_packet scripts.release_control.release_promotion_policy_test.ReleasePromotionPolicyTest.test_upgrade_guide_points_at_current_rc_support_pack -q`
|
||||
- `python3 scripts/release_control/resolve_release_promotion.py --version 6.0.0-rc.4 --rollback-version v5.1.29 --release-notes-file docs/releases/RELEASE_NOTES_v6_RC4_DRAFT.md`
|
||||
- `python3 scripts/release_control/status_audit.py --pretty`
|
||||
- Draft release workflow `25382802275`:
|
||||
- `prepare`, `frontend_checks`, `helm_smoke`, and `docker_build` passed
|
||||
- `backend_tests` failed on stale RC3 Docker defaults and stale retired
|
||||
monitored-system cap expectations
|
||||
- `go test -race ./internal/config ./tests/migration ./scripts/installtests`
|
||||
|
|
|
|||
|
|
@ -8,25 +8,22 @@
|
|||
|
||||
1. The latest shipped Pulse v6 prerelease tag is `v6.0.0-rc.3`.
|
||||
2. That shipped prerelease tag resolves to commit `f1744d36d0bde3c8735ae75a190af45c35087841`.
|
||||
3. The selected remote ref `origin/pulse/v6-release` is still behind the current
|
||||
local governed branch state, so `Release Dry Run` would exercise stale remote
|
||||
control-plane metadata instead of the intended candidate.
|
||||
4. The governed release profile in `docs/release-control/control_plane.json`
|
||||
3. The governed release profile in `docs/release-control/control_plane.json`
|
||||
currently declares both `prerelease_branch` and `stable_branch` as
|
||||
`pulse/v6-release`.
|
||||
5. The active control-plane target is still `v6-product-lane-expansion`, not
|
||||
4. The active control-plane target is still `v6-product-lane-expansion`, not
|
||||
`v6-ga-promotion`.
|
||||
6. The active local `pulse/v6-release` branch currently reports `VERSION=6.0.0-rc.4`, so the
|
||||
5. The active local `pulse/v6-release` branch currently reports `VERSION=6.0.0-rc.4`, so the
|
||||
working line is still prerelease and there is not yet a governed local stable
|
||||
`6.0.0` candidate.
|
||||
7. There is still no governed `Prerelease-to-GA Rehearsal Record` proving a successful
|
||||
6. There is still no governed `Prerelease-to-GA Rehearsal Record` proving a successful
|
||||
non-publish `Release Dry Run` for the eventual stable `6.0.0` candidate.
|
||||
8. `docs/releases/RELEASE_NOTES_v6.md` and
|
||||
7. `docs/releases/RELEASE_NOTES_v6.md` and
|
||||
`docs/release-control/v6/internal/V5_MAINTENANCE_SUPPORT_POLICY.md` now carry the
|
||||
currently proposed exact dates for the eventual GA notice:
|
||||
- `v6` GA date: `2026-04-20`
|
||||
- `v5` end-of-support date: `2026-07-19`
|
||||
9. There is still no governed `Release Dry Run` artifact or rehearsal record
|
||||
8. There is still no governed `Release Dry Run` artifact or rehearsal record
|
||||
exercising stable inputs for:
|
||||
- `version=6.0.0`
|
||||
- `promoted_from_tag=v6.0.0-rc.3`
|
||||
|
|
|
|||
|
|
@ -367,6 +367,13 @@ root `docker-compose.yml` sample and `scripts/install-docker.sh` must default
|
|||
to the governed `VERSION` cut instead of floating `:latest`, so self-hosted
|
||||
operators only move to a newer image when they choose a newer explicit tag or
|
||||
override `PULSE_IMAGE`.
|
||||
For every RC or stable release cut, those Docker defaults must move with the
|
||||
same governed `VERSION` change and the installer proof in
|
||||
`scripts/installtests/install_docker_sh_test.go` must assert both the repo-root
|
||||
compose image default and the standalone installer fallback constant. A draft
|
||||
release workflow failure caused by stale Docker image pins is a release-packet
|
||||
blocker until the defaults, tests, and evidence record are refreshed from the
|
||||
new branch head.
|
||||
|
||||
`internal/updates/` is the live deployment and upgrade planner. It owns
|
||||
deployment-type detection, update-plan generation, adapter selection, server
|
||||
|
|
|
|||
|
|
@ -24,10 +24,13 @@ hardening into a retestable v6 candidate:
|
|||
- Workloads empty-state detection, Patrol mobile header controls, mock-mode
|
||||
legacy sidecar cleanup, and agent-security guidance were refreshed
|
||||
|
||||
This packet was audited against all `51` commits in the exact `rc.3` to `rc.4`
|
||||
candidate range, from the published `v6.0.0-rc.3` tag commit
|
||||
This packet was audited against all `51` feature and runtime commits in the
|
||||
exact `rc.3` to `rc.4` candidate range, from the published `v6.0.0-rc.3` tag commit
|
||||
`f1744d36d0bde3c8735ae75a190af45c35087841` through candidate commit
|
||||
`3f16d7845a92d6bf0c5700728bd70e1f4fe32966`.
|
||||
`3f16d7845a92d6bf0c5700728bd70e1f4fe32966`. The final prerelease target also
|
||||
includes RC4 packet and release-validation commits that set the governed
|
||||
version, pin Docker install defaults to `6.0.0-rc.4`, and align migration tests
|
||||
with the canonical self-hosted licensing contract.
|
||||
|
||||
## Support Stance
|
||||
|
||||
|
|
@ -97,6 +100,8 @@ candidate range, from the published `v6.0.0-rc.3` tag commit
|
|||
- The Agent Security documentation entry now points operators at the current
|
||||
privilege guidance without leaving a stale support-pack reference.
|
||||
- Public demo admin reads stay hidden from the demo surface.
|
||||
- Docker Compose and turnkey Docker installer defaults now pin the RC4 image
|
||||
tag instead of the historical RC3 tag.
|
||||
|
||||
## What Existing v5 Users Should Re-Test In `rc.4`
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ match the current governed v6 architecture before wider RC retesting.
|
|||
|
||||
## Commit Coverage Audit
|
||||
|
||||
The changelog was audited against every commit in the exact release range for
|
||||
the current candidate head:
|
||||
The changelog was audited against every feature/runtime commit in the exact
|
||||
release range for the current candidate head:
|
||||
|
||||
- `v6.0.0-rc.3`: `f1744d36d0bde3c8735ae75a190af45c35087841`
|
||||
- candidate commit: `3f16d7845a92d6bf0c5700728bd70e1f4fe32966`
|
||||
|
|
@ -36,7 +36,9 @@ cleanup, API-first action planning, CLI action and fleet reads, action audit
|
|||
execution proof, self-hosted licensing continuity, root-agent and Proxmox
|
||||
setup hardening, TrueNAS/RAID/Ceph/storage correctness, Workloads empty-state
|
||||
handling, Patrol mobile controls, mock-mode cleanup, and release-control
|
||||
evidence.
|
||||
evidence. The final RC4 prerelease target also includes packet and
|
||||
release-validation commits that pin Docker install defaults to `6.0.0-rc.4` and
|
||||
remove stale migration-test expectations for retired monitored-system caps.
|
||||
|
||||
## Major Changes
|
||||
|
||||
|
|
|
|||
|
|
@ -366,9 +366,8 @@ func TestBillingState_SaveCanonicalizesCloudPlanContract(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.NotNil(t, loaded)
|
||||
assert.Equal(t, "cloud_starter", loaded.PlanVersion)
|
||||
assert.Equal(t, int64(10), loaded.Limits[pkglicensing.MaxMonitoredSystemsLicenseGateKey])
|
||||
_, hasOld := loaded.Limits["max_nodes"]
|
||||
assert.False(t, hasOld)
|
||||
assert.NotContains(t, loaded.Limits, pkglicensing.MaxMonitoredSystemsLicenseGateKey)
|
||||
assert.NotContains(t, loaded.Limits, "max_nodes")
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dir, "billing.json"))
|
||||
require.NoError(t, err)
|
||||
|
|
@ -378,9 +377,8 @@ func TestBillingState_SaveCanonicalizesCloudPlanContract(t *testing.T) {
|
|||
assert.Equal(t, "cloud_starter", raw["plan_version"])
|
||||
rawLimits, ok := raw["limits"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, float64(10), rawLimits[pkglicensing.MaxMonitoredSystemsLicenseGateKey])
|
||||
_, hasOld = rawLimits["max_nodes"]
|
||||
assert.False(t, hasOld)
|
||||
assert.NotContains(t, rawLimits, pkglicensing.MaxMonitoredSystemsLicenseGateKey)
|
||||
assert.NotContains(t, rawLimits, "max_nodes")
|
||||
}
|
||||
|
||||
func TestBillingState_GrandfatheredRecurringSelfHostedPlanStaysUncapped(t *testing.T) {
|
||||
|
|
@ -487,7 +485,7 @@ func TestBillingState_AllFieldsSurviveRoundTrip(t *testing.T) {
|
|||
|
||||
// Every field must survive save → reload → HMAC verify.
|
||||
assert.ElementsMatch(t, []string{"relay", "ai_autofix"}, loaded.Capabilities)
|
||||
assert.Equal(t, map[string]int64{pkglicensing.MaxMonitoredSystemsLicenseGateKey: 50, "max_hosts": 100}, loaded.Limits)
|
||||
assert.Equal(t, map[string]int64{"max_hosts": 100}, loaded.Limits)
|
||||
assert.ElementsMatch(t, []string{"active_agents", "api_calls"}, loaded.MetersEnabled)
|
||||
assert.Equal(t, "pro-v2", loaded.PlanVersion)
|
||||
assert.Equal(t, entitlements.SubStateActive, loaded.SubscriptionState)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ set -euo pipefail
|
|||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
DOCKER_IMAGE_REPO="${DOCKER_IMAGE_REPO:-rcourtman/pulse}"
|
||||
CANONICAL_DEFAULT_PULSE_VERSION="6.0.0-rc.3"
|
||||
CANONICAL_DEFAULT_PULSE_VERSION="6.0.0-rc.4"
|
||||
|
||||
resolve_default_pulse_version() {
|
||||
if [ -n "${PULSE_IMAGE_VERSION:-}" ]; then
|
||||
|
|
|
|||
|
|
@ -97,6 +97,19 @@ func TestRepoDockerComposeDefaultPinsCurrentVersion(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInstallDockerScriptFallbackPinsCurrentVersion(t *testing.T) {
|
||||
version := currentReleaseVersion(t)
|
||||
content, err := os.ReadFile(repoFile("scripts", "install-docker.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read install-docker.sh: %v", err)
|
||||
}
|
||||
|
||||
text := string(content)
|
||||
if !strings.Contains(text, `CANONICAL_DEFAULT_PULSE_VERSION="`+version+`"`) {
|
||||
t.Fatalf("install-docker.sh fallback must pin the current release version:\n%s", text)
|
||||
}
|
||||
}
|
||||
|
||||
func runInstallDockerScript(t *testing.T, workDir string, envVars ...string) {
|
||||
t.Helper()
|
||||
|
||||
|
|
|
|||
|
|
@ -152,6 +152,9 @@ Old metadata section.
|
|||
self.assertIn("API-first action planning", changelog)
|
||||
self.assertIn("monitored-system and child-resource volume unmetered", release_notes)
|
||||
self.assertIn("Pulse Mobile pairing for handoff", support_pack)
|
||||
self.assertIn("pin Docker install defaults to `6.0.0-rc.4`", changelog)
|
||||
self.assertIn("Docker Compose and turnkey Docker installer defaults", release_notes)
|
||||
self.assertIn("release-validation commits", changelog)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -448,15 +448,14 @@ func TestV5FullUpgradeScenario(t *testing.T) {
|
|||
require.NoError(t, persistence.Save(legacyLicense))
|
||||
|
||||
grantJWT, grantPublicKey, err := pkglicensing.GenerateGrantJWTForTesting(pkglicensing.GrantClaims{
|
||||
LicenseID: "lic_v5_migrated",
|
||||
Tier: string(pkglicensing.TierLifetime),
|
||||
PlanKey: "v5_lifetime_grandfathered",
|
||||
State: "active",
|
||||
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierLifetime]...),
|
||||
MaxMonitoredSystems: pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierLifetime],
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Add(72 * time.Hour).Unix(),
|
||||
Email: "legacy-lifetime@example.com",
|
||||
LicenseID: "lic_v5_migrated",
|
||||
Tier: string(pkglicensing.TierLifetime),
|
||||
PlanKey: "v5_lifetime_grandfathered",
|
||||
State: "active",
|
||||
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierLifetime]...),
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Add(72 * time.Hour).Unix(),
|
||||
Email: "legacy-lifetime@example.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
pkglicensing.SetPublicKey(grantPublicKey)
|
||||
|
|
@ -472,11 +471,10 @@ func TestV5FullUpgradeScenario(t *testing.T) {
|
|||
w.WriteHeader(http.StatusCreated)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(pkglicensing.ActivateInstallationResponse{
|
||||
License: pkglicensing.ActivateResponseLicense{
|
||||
LicenseID: "lic_v5_migrated",
|
||||
State: "active",
|
||||
Tier: string(pkglicensing.TierLifetime),
|
||||
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierLifetime]...),
|
||||
MaxMonitoredSystems: pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierLifetime],
|
||||
LicenseID: "lic_v5_migrated",
|
||||
State: "active",
|
||||
Tier: string(pkglicensing.TierLifetime),
|
||||
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierLifetime]...),
|
||||
},
|
||||
Installation: pkglicensing.ActivateResponseInstallation{
|
||||
InstallationID: "inst_v5_migrated",
|
||||
|
|
@ -587,15 +585,14 @@ func TestV5FullUpgradeScenario(t *testing.T) {
|
|||
require.NoError(t, persistence.Save(legacyLicense))
|
||||
|
||||
grantJWT, grantPublicKey, err := pkglicensing.GenerateGrantJWTForTesting(pkglicensing.GrantClaims{
|
||||
LicenseID: tc.licenseID,
|
||||
Tier: string(pkglicensing.TierPro),
|
||||
PlanKey: tc.planKey,
|
||||
State: "active",
|
||||
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierPro]...),
|
||||
MaxMonitoredSystems: pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierPro],
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Add(72 * time.Hour).Unix(),
|
||||
Email: tc.email,
|
||||
LicenseID: tc.licenseID,
|
||||
Tier: string(pkglicensing.TierPro),
|
||||
PlanKey: tc.planKey,
|
||||
State: "active",
|
||||
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierPro]...),
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().Add(72 * time.Hour).Unix(),
|
||||
Email: tc.email,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
pkglicensing.SetPublicKey(grantPublicKey)
|
||||
|
|
@ -611,11 +608,10 @@ func TestV5FullUpgradeScenario(t *testing.T) {
|
|||
w.WriteHeader(http.StatusCreated)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(pkglicensing.ActivateInstallationResponse{
|
||||
License: pkglicensing.ActivateResponseLicense{
|
||||
LicenseID: tc.licenseID,
|
||||
State: "active",
|
||||
Tier: string(pkglicensing.TierPro),
|
||||
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierPro]...),
|
||||
MaxMonitoredSystems: pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierPro],
|
||||
LicenseID: tc.licenseID,
|
||||
State: "active",
|
||||
Tier: string(pkglicensing.TierPro),
|
||||
Features: append([]string(nil), pkglicensing.TierFeatures[pkglicensing.TierPro]...),
|
||||
},
|
||||
Installation: pkglicensing.ActivateResponseInstallation{
|
||||
InstallationID: tc.installID,
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func TestV5PaidLicenseUpgrade_RealLicenseServerExchange(t *testing.T) {
|
|||
assert.NotEqual(t, tc.licenseID, current.Claims.LicenseID, "real exchange should promote the legacy token into a new canonical v6 license id")
|
||||
assert.Equal(t, tc.tier, current.Claims.Tier)
|
||||
assert.Equal(t, tc.planKey, current.Claims.PlanVersion)
|
||||
assert.Equal(t, pkglicensing.TierMonitoredSystemLimits[tc.tier], current.Claims.MaxMonitoredSystems)
|
||||
assertMonitoredSystemLimitAbsent(t, current.Claims.EffectiveLimits())
|
||||
|
||||
activationState, err := persistence.LoadActivationState()
|
||||
require.NoError(t, err)
|
||||
|
|
@ -154,7 +154,6 @@ func TestV5PaidLicenseUpgrade_RealLicenseServerExchange(t *testing.T) {
|
|||
assert.Equal(t, tc.tier, status.Tier)
|
||||
assert.Equal(t, tc.planKey, status.PlanVersion)
|
||||
assert.Equal(t, tc.wantIsLifetime, status.IsLifetime)
|
||||
assert.Equal(t, pkglicensing.TierMonitoredSystemLimits[tc.tier], status.MaxMonitoredSystems)
|
||||
|
||||
entReq := httpRequestWithOrg(http.MethodGet, "/api/license/entitlements", ctx)
|
||||
entRec := responseRecorder()
|
||||
|
|
@ -167,7 +166,7 @@ func TestV5PaidLicenseUpgrade_RealLicenseServerExchange(t *testing.T) {
|
|||
assert.Equal(t, "active", entitlements.SubscriptionState)
|
||||
assert.Equal(t, string(tc.tier), entitlements.Tier)
|
||||
assert.Equal(t, tc.wantIsLifetime, entitlements.IsLifetime)
|
||||
assert.Equal(t, int64(pkglicensing.TierMonitoredSystemLimits[tc.tier]), entitlementLimitByKey(entitlements.Limits, pkglicensing.MaxMonitoredSystemsLicenseGateKey))
|
||||
assertEntitlementLimitAbsent(t, entitlements.Limits, pkglicensing.MaxMonitoredSystemsLicenseGateKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -180,13 +179,18 @@ func responseRecorder() *httptest.ResponseRecorder {
|
|||
return httptest.NewRecorder()
|
||||
}
|
||||
|
||||
func entitlementLimitByKey(limits []pkglicensing.LimitStatus, key string) int64 {
|
||||
func assertMonitoredSystemLimitAbsent(t *testing.T, limits map[string]int64) {
|
||||
t.Helper()
|
||||
assert.NotContains(t, limits, pkglicensing.MaxMonitoredSystemsLicenseGateKey)
|
||||
}
|
||||
|
||||
func assertEntitlementLimitAbsent(t *testing.T, limits []pkglicensing.LimitStatus, key string) {
|
||||
t.Helper()
|
||||
for _, limit := range limits {
|
||||
if limit.Key == key {
|
||||
return limit.Limit
|
||||
t.Fatalf("entitlement limit %q present, want absent: %+v", key, limits)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func managedLicenseServerDir(t *testing.T) string {
|
||||
|
|
@ -229,21 +233,21 @@ func managedLicenseServerPlansJSON(t *testing.T) string {
|
|||
"tier": string(pkglicensing.TierLifetime),
|
||||
"duration_days": 0,
|
||||
"features": pkglicensing.TierFeatures[pkglicensing.TierLifetime],
|
||||
"max_monitored_systems": pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierLifetime],
|
||||
"max_monitored_systems": 0,
|
||||
"max_guests": 5,
|
||||
},
|
||||
"v5_pro_monthly_grandfathered": {
|
||||
"tier": string(pkglicensing.TierPro),
|
||||
"duration_days": 30,
|
||||
"features": pkglicensing.TierFeatures[pkglicensing.TierPro],
|
||||
"max_monitored_systems": pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierPro],
|
||||
"max_monitored_systems": 0,
|
||||
"max_guests": 5,
|
||||
},
|
||||
"v5_pro_annual_grandfathered": {
|
||||
"tier": string(pkglicensing.TierPro),
|
||||
"duration_days": 365,
|
||||
"features": pkglicensing.TierFeatures[pkglicensing.TierPro],
|
||||
"max_monitored_systems": pkglicensing.TierMonitoredSystemLimits[pkglicensing.TierPro],
|
||||
"max_monitored_systems": 0,
|
||||
"max_guests": 5,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue