Skip to content

Commit 0cd4502

Browse files
Refactor
1 parent 5c7459f commit 0cd4502

2 files changed

Lines changed: 145 additions & 82 deletions

File tree

pkg/project/preflight.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package project
2+
3+
import (
4+
"log/slog"
5+
"strings"
6+
7+
"github.com/Masterminds/semver/v3"
8+
)
9+
10+
type ProjectPreflight struct{}
11+
12+
func (p *Project) getProvidersUpgrading(workdir *PulumiWorkdir) map[string]string {
13+
log := slog.Default().With("service", "project.preflight")
14+
result := make(map[string]string)
15+
checkpoint, err := workdir.Export()
16+
17+
if err != nil || checkpoint == nil || checkpoint.Latest == nil {
18+
return result
19+
}
20+
21+
// looking at provider resources (type = "pulumi:providers:*")
22+
providerVersions := make(map[string]string) // URN -> version
23+
for _, resource := range checkpoint.Latest.Resources {
24+
resourceType := string(resource.Type)
25+
if !strings.HasPrefix(resourceType, "pulumi:providers:") {
26+
continue
27+
}
28+
29+
if resource.Inputs == nil {
30+
continue
31+
}
32+
33+
versionInterface, ok := resource.Inputs["version"]
34+
if !ok {
35+
continue
36+
}
37+
38+
version, ok := versionInterface.(string)
39+
if !ok {
40+
continue
41+
}
42+
43+
providerVersions[string(resource.URN)] = version
44+
}
45+
46+
// Track which providers are actually used by resources
47+
providersInUse := make(map[string]string) // provider name -> version
48+
49+
for _, resource := range checkpoint.Latest.Resources {
50+
if resource.Provider == "" {
51+
continue
52+
}
53+
54+
// The provider field has a UUID at the end that we need to strip
55+
// Resource provider: "urn:pulumi:james::aws-full::pulumi:providers:random::default_4_18_2::61adb215-aad5-4981-a591-ef3b50cb5dcc"
56+
// Provider URN: "urn:pulumi:james::aws-full::pulumi:providers:random::default_4_18_2"
57+
// We need to strip the last "::" and UUID
58+
providerRef := string(resource.Provider)
59+
providerRefParts := strings.Split(providerRef, "::")
60+
if len(providerRefParts) < 4 {
61+
continue
62+
}
63+
// Remove the UUID (last part) to get the provider URN
64+
providerURN := strings.Join(providerRefParts[:len(providerRefParts)-1], "::")
65+
66+
// Look up the provider version from the map we built
67+
version, ok := providerVersions[providerURN]
68+
if !ok {
69+
continue
70+
}
71+
72+
// Extract provider type from the URN (e.g., "pulumi:providers:aws")
73+
if len(providerRefParts) < 3 {
74+
continue
75+
}
76+
providerType := providerRefParts[2]
77+
if !strings.HasPrefix(providerType, "pulumi:providers:") {
78+
continue
79+
}
80+
81+
providerName := strings.TrimPrefix(providerType, "pulumi:providers:")
82+
83+
// Track provider usage - handle multiple versions
84+
if _, ok := providersInUse[providerName]; !ok {
85+
providersInUse[providerName] = version
86+
}
87+
}
88+
89+
// Step 3: Compare versions in state with lock file
90+
for _, lockEntry := range p.lock {
91+
currProviderVersion, exists := providersInUse[lockEntry.Name]
92+
if !exists {
93+
continue
94+
}
95+
96+
// Check each version in use for this provider
97+
log.Info("comparing versions",
98+
"provider", lockEntry.Name,
99+
"state", currProviderVersion,
100+
"lock", lockEntry.Version)
101+
102+
stateVer, err := semver.NewVersion(currProviderVersion)
103+
lockVer, err2 := semver.NewVersion(lockEntry.Version)
104+
105+
if err == nil && err2 == nil {
106+
upgradeType := ""
107+
if stateVer.Major() < lockVer.Major() {
108+
upgradeType = "major"
109+
log.Warn("major provider version upgrade detected",
110+
"provider", lockEntry.Name,
111+
"state_version", currProviderVersion,
112+
"lock_version", lockEntry.Version,
113+
"state_major", stateVer.Major(),
114+
"lock_major", lockVer.Major())
115+
} else if stateVer.Minor() < lockVer.Minor() {
116+
upgradeType = "minor"
117+
log.Info("minor provider version upgrade detected",
118+
"provider", lockEntry.Name,
119+
"state_version", currProviderVersion,
120+
"lock_version", lockEntry.Version)
121+
} else if stateVer.Patch() < lockVer.Patch() {
122+
upgradeType = "patch"
123+
log.Info("patch provider version upgrade detected",
124+
"provider", lockEntry.Name,
125+
"state_version", currProviderVersion,
126+
"lock_version", lockEntry.Version)
127+
}
128+
129+
if upgradeType != "" {
130+
if existing, ok := result[lockEntry.Name]; ok {
131+
if existing == "major" {
132+
continue
133+
} else if existing == "minor" && upgradeType == "patch" {
134+
continue
135+
}
136+
}
137+
result[lockEntry.Name] = upgradeType
138+
}
139+
}
140+
}
141+
142+
return result
143+
}

pkg/project/run.go

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"syscall"
1717
"time"
1818

19-
"github.com/Masterminds/semver/v3"
2019
"github.com/pulumi/pulumi/sdk/v3/go/auto/events"
2120
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
2221
"github.com/sst/sst/v3/internal/util"
@@ -41,81 +40,6 @@ func (p *Project) Run(ctx context.Context, input *StackInput) error {
4140
return p.RunNext(ctx, input)
4241
}
4342

44-
func (p *Project) checkProviderUpgrading(workdir *PulumiWorkdir) map[string]string {
45-
log := slog.Default().With("service", "project.run")
46-
result := make(map[string]string)
47-
48-
checkpoint, err := workdir.Export()
49-
if err != nil {
50-
log.Info("could not export checkpoint for provider version comparison", "err", err)
51-
return result
52-
}
53-
54-
if checkpoint == nil || checkpoint.Latest == nil {
55-
return result
56-
}
57-
58-
stateProviders := make(map[string]string)
59-
60-
for _, resource := range checkpoint.Latest.Resources {
61-
// "urn:pulumi:stage::app::pulumi:providers:random::default_4_18_2::61adb215-aad5-4981-a591-ef3b50cb5dcc
62-
providerUrnParts := strings.Split(string(resource.Provider), ":")
63-
if len(providerUrnParts) != 13 {
64-
continue
65-
}
66-
provider, version := providerUrnParts[8], providerUrnParts[10]
67-
68-
if versionStr, ok := strings.CutPrefix(version, "default_"); ok {
69-
versionSemVer := strings.ReplaceAll(versionStr, "_", ".")
70-
stateProviders[provider] = versionSemVer
71-
log.Info("found provider in state", "provider", provider, "version", versionSemVer)
72-
}
73-
}
74-
75-
for _, lockEntry := range p.lock {
76-
if stateVersion, exists := stateProviders[lockEntry.Name]; exists {
77-
log.Info("comparing versions",
78-
"provider", lockEntry.Name,
79-
"state", stateVersion,
80-
"lock", lockEntry.Version)
81-
82-
stateVer, err := semver.NewVersion(stateVersion)
83-
lockVer, err2 := semver.NewVersion(lockEntry.Version)
84-
85-
if err == nil && err2 == nil {
86-
upgradeType := ""
87-
if stateVer.Major() < lockVer.Major() {
88-
upgradeType = "major"
89-
log.Warn("major provider version upgrade detected",
90-
"provider", lockEntry.Name,
91-
"state_version", stateVersion,
92-
"lock_version", lockEntry.Version,
93-
"state_major", stateVer.Major(),
94-
"lock_major", lockVer.Major())
95-
} else if stateVer.Minor() < lockVer.Minor() {
96-
upgradeType = "minor"
97-
log.Info("minor provider version upgrade detected",
98-
"provider", lockEntry.Name,
99-
"state_version", stateVersion,
100-
"lock_version", lockEntry.Version)
101-
} else if stateVer.Patch() < lockVer.Patch() {
102-
upgradeType = "patch"
103-
log.Info("patch provider version upgrade detected",
104-
"provider", lockEntry.Name,
105-
"state_version", stateVersion,
106-
"lock_version", lockEntry.Version)
107-
}
108-
109-
if upgradeType != "" {
110-
result[lockEntry.Name] = upgradeType
111-
}
112-
}
113-
}
114-
}
115-
116-
return result
117-
}
118-
11943
func (p *Project) RunNext(ctx context.Context, input *StackInput) error {
12044
log := slog.Default().With("service", "project.run")
12145
log.Info("running stack command", "cmd", input.Command)
@@ -365,25 +289,21 @@ func (p *Project) RunNext(ctx context.Context, input *StackInput) error {
365289
}
366290

367291
if input.Command == "deploy" || input.Command == "diff" {
368-
// Compare provider versions between state and lock file
369-
providerVersions := p.checkProviderUpgrading(workdir)
292+
providersUpgrading := p.getProvidersUpgrading(workdir)
370293

371-
// Check for major version upgrades and collect affected providers
372294
majorUpgrades := []string{}
373-
for provider, upgradeType := range providerVersions {
295+
for provider, upgradeType := range providersUpgrading {
374296
if upgradeType == "major" {
375297
majorUpgrades = append(majorUpgrades, provider)
376298
}
377299
}
378300

379-
// Return error if major upgrades detected
380301
if len(majorUpgrades) > 0 {
381302
return util.NewReadableError(nil, fmt.Sprintf("Major version upgrade detected for provider(s): %s. Run `sst refresh` to migrate your state to this version. Then run `sst diff` to verify there are no unexpected changes. For more information, visit <LINK HERE TO DOCS>", strings.Join(majorUpgrades, ", ")))
382303
}
383304

384305
for provider, opts := range p.app.Providers {
385306
for key, value := range opts.(map[string]interface{}) {
386-
log.Info("setting provider config", "provider", provider, "key", key, "value", value)
387307
switch v := value.(type) {
388308
case map[string]interface{}:
389309
bytes, err := json.Marshal(v)

0 commit comments

Comments
 (0)