Skip to content

Commit 84a963f

Browse files
committed
fix(entrypoint): escape amp in sed
Escapes ampersand (&) symbol in the entrypoint.sh for the k0s, to avoid incorrect substitutions in case kine data source url has two or more options to connect. Fixes #1299
1 parent 2069e1f commit 84a963f

File tree

2 files changed

+131
-2
lines changed

2 files changed

+131
-2
lines changed

internal/controller/k0smotron.io/k0smotroncluster_entrypoint.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const entrypointTemplate = `
113113
mkdir /etc/k0s && echo "$K0SMOTRON_K0S_YAML" > /etc/k0s/k0s.yaml
114114
115115
# Substitute the kine datasource URL from the env var
116-
sed -i "s {{ .KineDataSourceURLPlaceholder }} ${K0SMOTRON_KINE_DATASOURCE_URL} g" /etc/k0s/k0s.yaml
116+
sed -i "s {{ .KineDataSourceURLPlaceholder }} ${K0SMOTRON_KINE_DATASOURCE_URL//&/\\&} g" /etc/k0s/k0s.yaml
117117
118118
{{if .PrivilegedPortIsUsed}}
119119
apk add --no-cache libcap

internal/controller/k0smotron.io/k0smotroncluster_entrypoint_test.go

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,21 @@ limitations under the License.
1717
package k0smotronio
1818

1919
import (
20+
"os"
21+
"os/exec"
22+
"strings"
2023
"testing"
2124

2225
km "github.com/k0sproject/k0smotron/api/k0smotron.io/v1beta1"
2326
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/require"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2431
)
2532

2633
func TestGetControllerFlags(t *testing.T) {
27-
var tests = []struct {
34+
tests := []struct {
2835
name string
2936
kmc km.Cluster
3037
result string
@@ -60,3 +67,125 @@ func TestGetControllerFlags(t *testing.T) {
6067
assert.Equal(t, test.result, getControllerFlags(&test.kmc), test.name)
6168
}
6269
}
70+
71+
func TestKineDataSourceURLSubstitution(t *testing.T) {
72+
if _, err := exec.LookPath("sed"); err != nil {
73+
t.Skip("sed command not available, skipping test")
74+
}
75+
76+
tests := []struct {
77+
name string
78+
kineDataSourceURL string
79+
expectedSubstitution string
80+
}{
81+
{
82+
name: "URL without ampersands",
83+
kineDataSourceURL: "postgres://user:pass@host:5432/db?sslmode=disable",
84+
expectedSubstitution: "postgres://user:pass@host:5432/db?sslmode=disable",
85+
},
86+
{
87+
name: "URL with single ampersand",
88+
kineDataSourceURL: "postgres://user:pass@host:5432/db?param1=value1&param2=value2",
89+
expectedSubstitution: "postgres://user:pass@host:5432/db?param1=value1&param2=value2",
90+
},
91+
{
92+
name: "URL with multiple ampersands",
93+
kineDataSourceURL: "postgres://user:pass@host:5432/db?param1=value1&param2=value2&param3=value3",
94+
expectedSubstitution: "postgres://user:pass@host:5432/db?param1=value1&param2=value2&param3=value3",
95+
},
96+
{
97+
name: "URL with ampersand in password",
98+
kineDataSourceURL: "postgres://user:pa&ss@host:5432/db",
99+
expectedSubstitution: "postgres://user:pa&ss@host:5432/db",
100+
},
101+
}
102+
103+
for _, tc := range tests {
104+
t.Run(tc.name, func(t *testing.T) {
105+
kmc := &km.Cluster{
106+
ObjectMeta: metav1.ObjectMeta{
107+
Name: "test-cluster",
108+
Namespace: "default",
109+
},
110+
Spec: km.ClusterSpec{
111+
Service: km.ServiceSpec{
112+
APIPort: 6443,
113+
},
114+
KineDataSourceURL: tc.kineDataSourceURL,
115+
},
116+
}
117+
118+
scheme := runtime.NewScheme()
119+
require.NoError(t, km.AddToScheme(scheme))
120+
121+
client := fake.NewClientBuilder().
122+
WithScheme(scheme).
123+
WithObjects(kmc).
124+
Build()
125+
126+
scope := &kmcScope{
127+
client: client,
128+
}
129+
130+
cm, err := scope.generateEntrypointCM(kmc)
131+
require.NoError(t, err)
132+
133+
entrypointScript := cm.Data["k0smotron-entrypoint.sh"]
134+
135+
// Extract the sed command from the script
136+
lines := strings.Split(entrypointScript, "\n")
137+
var sedLine string
138+
for _, line := range lines {
139+
if strings.Contains(line, "sed -i") && strings.Contains(line, "K0SMOTRON_KINE_DATASOURCE_URL") {
140+
sedLine = strings.TrimSpace(line)
141+
break
142+
}
143+
}
144+
require.NotEmpty(t, sedLine, "sed command not found in entrypoint script")
145+
146+
testK0sConfig := `apiVersion: k0s.k0sproject.io/v1beta1
147+
kind: ClusterConfig
148+
metadata:
149+
name: k0s
150+
spec:
151+
storage:
152+
type: kine
153+
kine:
154+
dataSource: ` + kineDataSourceURLPlaceholder + `
155+
api:
156+
port: 6443`
157+
158+
tmpFile, err := os.CreateTemp("", "k0s-test-*.yaml")
159+
require.NoError(t, err)
160+
t.Cleanup(func() { os.Remove(tmpFile.Name()) })
161+
162+
_, err = tmpFile.WriteString(testK0sConfig)
163+
require.NoError(t, err)
164+
require.NoError(t, tmpFile.Close())
165+
166+
os.Setenv("K0SMOTRON_KINE_DATASOURCE_URL", tc.kineDataSourceURL)
167+
t.Cleanup(func() { os.Unsetenv("K0SMOTRON_KINE_DATASOURCE_URL") })
168+
169+
actualSedCmd := strings.Replace(sedLine, "/etc/k0s/k0s.yaml", tmpFile.Name(), 1)
170+
171+
cmd := exec.Command("sh", "-c", actualSedCmd)
172+
cmd.Env = append(os.Environ(), "K0SMOTRON_KINE_DATASOURCE_URL="+tc.kineDataSourceURL)
173+
174+
output, err := cmd.CombinedOutput()
175+
if err != nil {
176+
t.Logf("sed command: %s", actualSedCmd)
177+
t.Logf("sed output: %s", string(output))
178+
}
179+
require.NoError(t, err, "sed command failed: %s", string(output))
180+
181+
modifiedContent, err := os.ReadFile(tmpFile.Name())
182+
require.NoError(t, err)
183+
184+
modifiedStr := string(modifiedContent)
185+
assert.Contains(t, modifiedStr, tc.expectedSubstitution,
186+
"Expected URL %q not found in modified config", tc.expectedSubstitution)
187+
assert.NotContains(t, modifiedStr, kineDataSourceURLPlaceholder,
188+
"Placeholder should be completely replaced")
189+
})
190+
}
191+
}

0 commit comments

Comments
 (0)