Skip to content

Commit fcc82d2

Browse files
authored
fix(entrypoint): escape amp in sed (#1300)
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 Signed-off-by: Michael Morgen <[email protected]>
1 parent 2069e1f commit fcc82d2

File tree

2 files changed

+137
-2
lines changed

2 files changed

+137
-2
lines changed

internal/controller/k0smotron.io/k0smotroncluster_entrypoint.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ 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+
escaped_url=$(printf '%s' "$K0SMOTRON_KINE_DATASOURCE_URL" | sed 's/[&/\]/\\&/g')
117+
sed -i "s {{ .KineDataSourceURLPlaceholder }} $escaped_url g" /etc/k0s/k0s.yaml
117118
118119
{{if .PrivilegedPortIsUsed}}
119120
apk add --no-cache libcap

internal/controller/k0smotron.io/k0smotroncluster_entrypoint_test.go

Lines changed: 135 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,130 @@ 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+
lines := strings.Split(entrypointScript, "\n")
136+
var printfLine, sedLine string
137+
for _, line := range lines {
138+
line = strings.TrimSpace(line)
139+
if strings.Contains(line, "escaped_url=$(printf") && strings.Contains(line, "K0SMOTRON_KINE_DATASOURCE_URL") {
140+
printfLine = line
141+
}
142+
if strings.Contains(line, "sed -i") && strings.Contains(line, "$escaped_url") {
143+
sedLine = line
144+
}
145+
}
146+
require.NotEmpty(t, printfLine, "printf command not found in entrypoint script")
147+
require.NotEmpty(t, sedLine, "sed command not found in entrypoint script")
148+
149+
testK0sConfig := `apiVersion: k0s.k0sproject.io/v1beta1
150+
kind: ClusterConfig
151+
metadata:
152+
name: k0s
153+
spec:
154+
storage:
155+
type: kine
156+
kine:
157+
dataSource: ` + kineDataSourceURLPlaceholder + `
158+
api:
159+
port: 6443`
160+
161+
tmpFile, err := os.CreateTemp("", "k0s-test-*.yaml")
162+
require.NoError(t, err)
163+
t.Cleanup(func() { os.Remove(tmpFile.Name()) })
164+
165+
_, err = tmpFile.WriteString(testK0sConfig)
166+
require.NoError(t, err)
167+
require.NoError(t, tmpFile.Close())
168+
169+
os.Setenv("K0SMOTRON_KINE_DATASOURCE_URL", tc.kineDataSourceURL)
170+
t.Cleanup(func() { os.Unsetenv("K0SMOTRON_KINE_DATASOURCE_URL") })
171+
172+
actualSedCmd := strings.Replace(sedLine, "/etc/k0s/k0s.yaml", tmpFile.Name(), 1)
173+
174+
combinedScript := printfLine + "\n" + actualSedCmd
175+
176+
cmd := exec.Command("sh", "-c", combinedScript)
177+
cmd.Env = append(os.Environ(), "K0SMOTRON_KINE_DATASOURCE_URL="+tc.kineDataSourceURL)
178+
179+
output, err := cmd.CombinedOutput()
180+
if err != nil {
181+
t.Logf("Combined script: %s", combinedScript)
182+
t.Logf("Script output: %s", string(output))
183+
}
184+
require.NoError(t, err, "Shell script failed: %s", string(output))
185+
186+
modifiedContent, err := os.ReadFile(tmpFile.Name())
187+
require.NoError(t, err)
188+
189+
modifiedStr := string(modifiedContent)
190+
assert.Contains(t, modifiedStr, tc.expectedSubstitution,
191+
"Expected URL %q not found in modified config", tc.expectedSubstitution)
192+
assert.NotContains(t, modifiedStr, kineDataSourceURLPlaceholder,
193+
"Placeholder should be completely replaced")
194+
})
195+
}
196+
}

0 commit comments

Comments
 (0)