Skip to content

Commit 8588e3b

Browse files
authored
fix: upgrade service panic in embedded cluster without REPLICATED_APP_ENDPOINT (#5770)
* fix: upgrade service panic in embedded cluster without REPLICATED_APP_ENDPOINT Fixes issue where KOTS upgrades fail in Embedded Cluster environments when REPLICATED_APP_ENDPOINT is not set in the kotsadm container environment. Problem: When upgrading from KOTS 1.19.0 (K8s 1.30) to 2.13.2 (K8s 1.31), the upgrade service would panic because util.ReplicatedAppEndpoint() requires the REPLICATED_APP_ENDPOINT environment variable to be set in embedded cluster environments. During upgrades, the new KOTS binary runs in the old kotsadm pod's environment which doesn't have this variable set. Solution: Instead of calling util.ReplicatedAppEndpoint() (which panics if the env var is missing in EC), directly implement the fallback logic in bootstrap.go with the following precedence (lowest to highest): 1. Default: "https://replicated.app" 2. License endpoint (if license exists and has endpoint) 3. REPLICATED_API_ENDPOINT env var (deprecated, highest precedence if set) Then sets REPLICATED_APP_ENDPOINT for the upgrade service process. This allows the upgrade service to bootstrap successfully even when running in an older kotsadm environment that doesn't have REPLICATED_APP_ENDPOINT set. Related: https://app.shortcut.com/replicated/story/132824 Signed-off-by: Evans Mungai <evans@replicated.com> * Add test for ensureReplicatedAppEndpointSet Signed-off-by: Evans Mungai <evans@replicated.com> * Remove unnecessary lines Signed-off-by: Evans Mungai <evans@replicated.com> * fix: ReplicatedAppEndpoint should check if license has empty endpoint Signed-off-by: Evans Mungai <evans@replicated.com> --------- Signed-off-by: Evans Mungai <evans@replicated.com>
1 parent 81e6f5f commit 8588e3b

4 files changed

Lines changed: 100 additions & 8 deletions

File tree

pkg/upgradeservice/bootstrap.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,7 @@ func pullArchive(params types.UpgradeServiceParams, pullOptions pull.PullOptions
8181
return errors.Wrap(err, "failed to load license from bytes")
8282
}
8383

84-
// In the upgrade service, it may be the case that the environment variables do not exist in
85-
// the container, as we are running in a previous release of the helm chart. If this is the
86-
// case, we fall back to the previous behavior and get the endpoint from the license.
87-
if val := os.Getenv("REPLICATED_APP_ENDPOINT"); val == "" {
88-
endpoint := util.ReplicatedAppEndpoint(&licenseWrapper)
89-
os.Setenv("REPLICATED_APP_ENDPOINT", endpoint)
90-
}
84+
ensureReplicatedAppEndpointSet(licenseWrapper)
9185

9286
identityConfigFile, err := getIdentityConfigFile(params)
9387
if err != nil {
@@ -167,3 +161,21 @@ func getIdentityConfigFile(params types.UpgradeServiceParams) (string, error) {
167161
}
168162
return identityConfigFile, nil
169163
}
164+
165+
// ensureReplicatedAppEndpointSet ensures that the REPLICATED_APP_ENDPOINT environment variable is set.
166+
// When running in a previous release of the helm chart, the environment variable may not exist in
167+
// the container, so we fall back to getting the endpoint from the license.
168+
func ensureReplicatedAppEndpointSet(licenseWrapper licensewrapper.LicenseWrapper) {
169+
if val := os.Getenv("REPLICATED_APP_ENDPOINT"); val == "" {
170+
endpoint := "https://replicated.app"
171+
if !licenseWrapper.IsEmpty() {
172+
if ep := licenseWrapper.GetEndpoint(); ep != "" {
173+
endpoint = ep
174+
}
175+
}
176+
if ep := os.Getenv("REPLICATED_API_ENDPOINT"); ep != "" {
177+
endpoint = ep
178+
}
179+
os.Setenv("REPLICATED_APP_ENDPOINT", endpoint)
180+
}
181+
}

pkg/upgradeservice/bootstrap_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package upgradeservice
22

33
import (
4+
"fmt"
5+
"os"
46
"testing"
57

68
"github.com/replicatedhq/kots/pkg/upgradeservice/types"
9+
"github.com/replicatedhq/kotskinds/pkg/licensewrapper"
710
"github.com/stretchr/testify/assert"
811
"github.com/stretchr/testify/require"
912
)
@@ -74,3 +77,68 @@ func TestBootstrapUpdateWithinKubeRange(t *testing.T) {
7477
})
7578
}
7679
}
80+
81+
func TestEnsureReplicatedAppEndpointSet(t *testing.T) {
82+
tests := []struct {
83+
name string
84+
existingEnv string
85+
licenseEndpoint string
86+
expectedSet string
87+
}{
88+
{
89+
name: "sets endpoint from license",
90+
existingEnv: "",
91+
licenseEndpoint: "https://replicated.app",
92+
expectedSet: "https://replicated.app",
93+
},
94+
{
95+
name: "sets default endpoint when license has no endpoint",
96+
existingEnv: "",
97+
licenseEndpoint: "",
98+
expectedSet: "https://replicated.app",
99+
},
100+
{
101+
name: "does not change when REPLICATED_APP_ENDPOINT already set",
102+
existingEnv: "https://existing.replicated.app",
103+
licenseEndpoint: "https://license.replicated.app",
104+
expectedSet: "https://existing.replicated.app",
105+
},
106+
}
107+
108+
for _, tt := range tests {
109+
t.Run(tt.name, func(t *testing.T) {
110+
// Setup initial REPLICATED_APP_ENDPOINT state
111+
t.Setenv("REPLICATED_APP_ENDPOINT", tt.existingEnv)
112+
113+
// Create license
114+
licenseData := `apiVersion: kots.io/v1beta1
115+
kind: License
116+
metadata:
117+
name: test
118+
spec:
119+
appSlug: test-app
120+
licenseID: test-id`
121+
122+
if tt.licenseEndpoint != "" {
123+
licenseData = fmt.Sprintf(`apiVersion: kots.io/v1beta1
124+
kind: License
125+
metadata:
126+
name: test
127+
spec:
128+
endpoint: %s
129+
appSlug: test-app
130+
licenseID: test-id`, tt.licenseEndpoint)
131+
}
132+
133+
licenseWrapper, err := licensewrapper.LoadLicenseFromBytes([]byte(licenseData))
134+
require.NoError(t, err)
135+
136+
require.NotPanics(t, func() {
137+
ensureReplicatedAppEndpointSet(licenseWrapper)
138+
})
139+
140+
// Verify env var was set correctly
141+
assert.Equal(t, tt.expectedSet, os.Getenv("REPLICATED_APP_ENDPOINT"))
142+
})
143+
}
144+
}

pkg/util/util.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ func ReplicatedAppEndpoint(license *licensewrapper.LicenseWrapper) string {
201201
}
202202

203203
if !license.IsEmpty() {
204-
return license.GetEndpoint()
204+
if ep := license.GetEndpoint(); ep != "" {
205+
return ep
206+
}
205207
}
206208

207209
return "https://replicated.app"

pkg/util/util_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,16 @@ func Test_ReplicatedAppEndpoint(t *testing.T) {
392392
isEmbedded: true,
393393
wantPanic: true,
394394
},
395+
{
396+
name: "empty endpoint",
397+
license: &kotsv1beta1.License{
398+
Spec: kotsv1beta1.LicenseSpec{
399+
Endpoint: "",
400+
},
401+
},
402+
isEmbedded: false,
403+
want: "https://replicated.app",
404+
},
395405
}
396406

397407
for _, tt := range tests {

0 commit comments

Comments
 (0)