Skip to content

Commit

Permalink
Properly handle windows environment variables
Browse files Browse the repository at this point in the history
  • Loading branch information
HarrisonWAffel committed Oct 25, 2024
1 parent 261bb3c commit ad91ce9
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 24 deletions.
39 changes: 39 additions & 0 deletions pkg/capr/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,3 +677,42 @@ func PreBootstrap(mgmtCluster *v3.Cluster) bool {

return !v3.ClusterConditionPreBootstrapped.IsTrue(mgmtCluster)
}

// FormatWindowsEnvVar accepts a corev1.EnvVar and returns a string to be used in either
// a Powershell script or the Rancher planner, indicated by the isPlanVariable parameter.
// This function automatically configures the '$env:' prefix for a given environment variable,
// automatically prefixes boolean values with '$', and surrounds string variables with double quotes as
// needed. If the provided variable name incorrectly uses either '$env:' or '$' for the given isPlanVariable
// value, it will be removed.
func FormatWindowsEnvVar(envVar corev1.EnvVar, isPlanVariable bool) string {
lowerValue := strings.ToLower(envVar.Value)
isBool := lowerValue == "$true" || lowerValue == "$false" ||
lowerValue == "true" || lowerValue == "false"

// remove any user provided prefixes and quotations
envVar.Name = strings.TrimPrefix(envVar.Name, "$env:")
envVar.Value = strings.Trim(envVar.Value, "\"")

if !isBool {
format := ""
// Non-boolean variables are always treated as strings,
// even numbers
format = "$env:%s=\"%s\""
if isPlanVariable {
format = "%s=%s"
}

return fmt.Sprintf(format, envVar.Name, envVar.Value)
}

if !isPlanVariable {
if !strings.HasPrefix(envVar.Value, "$") {
envVar.Value = "$" + envVar.Value
}
return fmt.Sprintf("$env:%s=%s", envVar.Name, envVar.Value)
}

envVar.Value = strings.Trim(envVar.Value, "$")

return fmt.Sprintf("%s=%s", envVar.Name, envVar.Value)
}
92 changes: 92 additions & 0 deletions pkg/capr/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
rkev1 "github.com/rancher/rancher/pkg/apis/rke.cattle.io/v1"
"github.com/rancher/wrangler/v3/pkg/generic/fake"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
capi "sigs.k8s.io/cluster-api/api/v1beta1"
Expand Down Expand Up @@ -313,3 +314,94 @@ func TestCompressInterface(t *testing.T) {
})
}
}

func TestFormatWindowsEnvVar(t *testing.T) {
tests := []struct {
Name string
EnvVar corev1.EnvVar
IsPlanVar bool
ExpectedString string
}{
{
Name: "Basic String",
EnvVar: corev1.EnvVar{
Name: "BASIC_STRING",
Value: "ABC123",
},
IsPlanVar: false,
ExpectedString: "$env:BASIC_STRING=\"ABC123\"",
},
{
Name: "Basic Bool",
EnvVar: corev1.EnvVar{
Name: "BASIC_BOOL",
Value: "true",
},
IsPlanVar: false,
ExpectedString: "$env:BASIC_BOOL=$true",
},
{
Name: "Basic Plan String",
EnvVar: corev1.EnvVar{
Name: "PLAN_STRING",
Value: "VALUE",
},
IsPlanVar: true,
ExpectedString: "PLAN_STRING=VALUE",
},
{
Name: "Basic Plan Bool",
EnvVar: corev1.EnvVar{
Name: "PLAN_BOOL",
Value: "true",
},
IsPlanVar: true,
ExpectedString: "PLAN_BOOL=true",
},
{
Name: "Plan Name Mistakenly Includes $env:",
EnvVar: corev1.EnvVar{
Name: "$env:PLAN_BOOL",
Value: "true",
},
IsPlanVar: true,
ExpectedString: "PLAN_BOOL=true",
},
{
Name: "Plan Bool Mistakenly Includes $",
EnvVar: corev1.EnvVar{
Name: "PLAN_BOOL",
Value: "$true",
},
IsPlanVar: true,
ExpectedString: "PLAN_BOOL=true",
},
{
Name: "Non-Plan String Value Includes $",
EnvVar: corev1.EnvVar{
Name: "PLAN_BOOL",
Value: "\"$true\"",
},
IsPlanVar: false,
ExpectedString: "$env:PLAN_BOOL=\"$true\"",
},
{
Name: "Plan String Value Includes $",
EnvVar: corev1.EnvVar{
Name: "PLAN_BOOL",
Value: "\"$true\"",
},
IsPlanVar: true,
ExpectedString: "PLAN_BOOL=$true",
},
}

for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
out := FormatWindowsEnvVar(tc.EnvVar, tc.IsPlanVar)
if out != tc.ExpectedString {
t.Fatalf("Expected %s, got %s", tc.ExpectedString, out)
}
})
}
}
39 changes: 32 additions & 7 deletions pkg/capr/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"strings"

"github.com/rancher/rancher/pkg/capr"
"github.com/rancher/rancher/pkg/settings"
"github.com/rancher/rancher/pkg/systemtemplate"
"github.com/rancher/rancher/pkg/tls"
Expand Down Expand Up @@ -135,9 +136,15 @@ func WindowsInstallScript(ctx context.Context, token string, envVars []corev1.En
binaryURL := ""
if settings.WinsAgentVersion.Get() != "" {
if settings.ServerURL.Get() != "" {
binaryURL = fmt.Sprintf("$env:CATTLE_AGENT_BINARY_BASE_URL=\"%s/assets\"", settings.ServerURL.Get())
binaryURL = capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: "CATTLE_AGENT_BINARY_BASE_URL",
Value: fmt.Sprintf("%s/assets", settings.ServerURL.Get()),
}, false)
} else if defaultHost != "" {
binaryURL = fmt.Sprintf("$env:CATTLE_AGENT_BINARY_BASE_URL=\"https://%s/assets\"", defaultHost)
binaryURL = capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: "CATTLE_AGENT_BINARY_BASE_URL",
Value: fmt.Sprintf("https://%s/assets", defaultHost),
}, false)
}
}

Expand All @@ -156,22 +163,39 @@ func WindowsInstallScript(ctx context.Context, token string, envVars []corev1.En
if v, ok := ctx.Value(tls.InternalAPI).(bool); ok && v {
ca = systemtemplate.InternalCAChecksum()
}

if ca != "" {
ca = "$env:CATTLE_CA_CHECKSUM=\"" + ca + "\""
ca = capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: "CATTLE_CA_CHECKSUM",
Value: ca,
}, false)
}

var tokenEnvVar, cattleRoleNone string
if token != "" {
token = "$env:CATTLE_ROLE_NONE=\"true\"\n$env:CATTLE_TOKEN=\"" + token + "\""
tokenEnvVar = capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: "CATTLE_TOKEN",
Value: token,
}, false)
cattleRoleNone = capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: "CATTLE_ROLE_NONE",
Value: "\"true\"",
}, false)
}

envVarBuf := &strings.Builder{}
for _, envVar := range envVars {
if envVar.Value == "" {
continue
}
envVarBuf.WriteString(fmt.Sprintf("$env:%s=\"%s\"\n", envVar.Name, envVar.Value))
envVarBuf.WriteString(capr.FormatWindowsEnvVar(envVar, false))
}
server := ""
if settings.ServerURL.Get() != "" {
server = fmt.Sprintf("$env:CATTLE_SERVER=\"%s\"", settings.ServerURL.Get())
server = capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: "CATTLE_SERVER",
Value: settings.ServerURL.Get(),
}, false)
}

strictVerify := "false"
Expand All @@ -192,8 +216,9 @@ $env:CSI_PROXY_URL = "%s"
$env:CSI_PROXY_VERSION = "%s"
$env:CSI_PROXY_KUBELET_PATH = "C:%s/bin/kubelet.exe"
$env:STRICT_VERIFY = "%s"
%s
Invoke-WinsInstaller @PSBoundParameters
exit 0
`, data, envVarBuf.String(), binaryURL, server, ca, token, csiProxyURL, csiProxyVersion, dataDir, strictVerify)), nil
`, data, envVarBuf.String(), binaryURL, server, ca, tokenEnvVar, csiProxyURL, csiProxyVersion, dataDir, strictVerify, cattleRoleNone)), nil
}
26 changes: 22 additions & 4 deletions pkg/capr/planner/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
rkev1 "github.com/rancher/rancher/pkg/apis/rke.cattle.io/v1"
"github.com/rancher/rancher/pkg/apis/rke.cattle.io/v1/plan"
"github.com/rancher/rancher/pkg/capr"
corev1 "k8s.io/api/core/v1"
)

const (
Expand All @@ -26,14 +27,22 @@ func (p *Planner) generateInstallInstruction(controlPlane *rkev1.RKEControlPlane
}
switch cattleOS {
case capr.WindowsMachineOS:
env = append(env, fmt.Sprintf("$env:%s=\"%s\"", arg.Name, arg.Value))
env = append(env, capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: arg.Name,
Value: arg.Value,
}, true))
default:
env = append(env, fmt.Sprintf("%s=%s", arg.Name, arg.Value))
}
}
switch cattleOS {
case capr.WindowsMachineOS:
// TODO: Properly format the data dir when adding full support for Windows nodes
env = append(env, fmt.Sprintf("$env:%s_DATA_DIR=\"c:%s\"", strings.ToUpper(capr.GetRuntime(controlPlane.Spec.KubernetesVersion)), capr.GetDistroDataDir(controlPlane)))
env = append(env, capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: "INSTALL_RKE2_VERSION",
Value: controlPlane.Spec.KubernetesVersion,
}, true))
default:
env = append(env, fmt.Sprintf("%s_DATA_DIR=%s", strings.ToUpper(capr.GetRuntime(controlPlane.Spec.KubernetesVersion)), capr.GetDistroDataDir(controlPlane)))
}
Expand All @@ -60,7 +69,10 @@ func (p *Planner) generateInstallInstruction(controlPlane *rkev1.RKEControlPlane
if isOnlyWorker(entry) {
switch cattleOS {
case capr.WindowsMachineOS:
instruction.Env = append(instruction.Env, fmt.Sprintf("$env:INSTALL_%s_EXEC=\"agent\"", capr.GetRuntimeEnv(controlPlane.Spec.KubernetesVersion)))
instruction.Env = append(instruction.Env, capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: fmt.Sprintf("INSTALL_%s_EXEC", capr.GetRuntimeEnv(controlPlane.Spec.KubernetesVersion)),
Value: "agent",
}, true))
default:
instruction.Env = append(instruction.Env, fmt.Sprintf("INSTALL_%s_EXEC=agent", capr.GetRuntimeEnv(controlPlane.Spec.KubernetesVersion)))
}
Expand All @@ -77,7 +89,10 @@ func (p *Planner) addInstallInstructionWithRestartStamp(nodePlan plan.NodePlan,
stamp := restartStamp(nodePlan, controlPlane, p.getInstallerImage(controlPlane))
switch entry.Metadata.Labels[capr.CattleOSLabel] {
case capr.WindowsMachineOS:
restartStampEnv = "$env:RESTART_STAMP=\"" + stamp + "\""
restartStampEnv = capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: "WINS_RESTART_STAMP",
Value: stamp,
}, true)
default:
restartStampEnv = "RESTART_STAMP=" + stamp
}
Expand All @@ -93,7 +108,10 @@ func (p *Planner) generateInstallInstructionWithSkipStart(controlPlane *rkev1.RK
var skipStartEnv string
switch entry.Metadata.Labels[capr.CattleOSLabel] {
case capr.WindowsMachineOS:
skipStartEnv = fmt.Sprintf("$env:INSTALL_%s_SKIP_START=\"true\"", strings.ToUpper(capr.GetRuntime(controlPlane.Spec.KubernetesVersion)))
skipStartEnv = capr.FormatWindowsEnvVar(corev1.EnvVar{
Name: fmt.Sprintf("INSTALL_%s_SKIP_START", strings.ToUpper(capr.GetRuntime(controlPlane.Spec.KubernetesVersion))),
Value: "true",
}, true)
default:
skipStartEnv = fmt.Sprintf("INSTALL_%s_SKIP_START=true", strings.ToUpper(capr.GetRuntime(controlPlane.Spec.KubernetesVersion)))
}
Expand Down
18 changes: 9 additions & 9 deletions pkg/capr/planner/instructions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestPlanner_generateInstallInstruction(t *testing.T) {
command: "powershell.exe",
scriptName: "run.ps1",
envs: []string{},
expectedEnvsLen: 2,
expectedEnvsLen: 3,
image: "my/custom-image-",
expectedImage: "my/custom-image-rke2:v1.21.5-rke2r2",
},
Expand All @@ -77,8 +77,8 @@ func TestPlanner_generateInstallInstruction(t *testing.T) {
os: "windows",
command: "powershell.exe",
scriptName: "run.ps1",
envs: []string{"$env:HTTP_PROXY", "$env:HTTPS_PROXY", "$env:INSTALL_RKE2_EXEC"},
expectedEnvsLen: 4,
envs: []string{"HTTP_PROXY", "HTTPS_PROXY", "INSTALL_RKE2_EXEC"},
expectedEnvsLen: 5,
image: "my/custom-image-",
expectedImage: "my/custom-image-rke2:v1.21.5-rke2r2",
},
Expand Down Expand Up @@ -106,7 +106,7 @@ func TestPlanner_generateInstallInstruction(t *testing.T) {
a.Equal(p.Image, tt.args.expectedImage)
a.Equal(tt.args.expectedEnvsLen, len(p.Env))
for _, e := range tt.args.envs {
a.True(findEnv(p.Env, e), "couldn't find %s in environment", e)
a.True(findEnvName(p.Env, e), "couldn't find %s in environment", e)
}
})
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func TestPlanner_addInstallInstructionWithRestartStamp(t *testing.T) {
os: "windows",
command: "powershell.exe",
scriptName: "run.ps1",
envs: []string{"$env:RESTART_STAMP"},
envs: []string{"WINS_RESTART_STAMP"},
image: "my/custom-image-",
expectedImage: "my/custom-image-rke2:v1.21.5-rke2r2",
},
Expand Down Expand Up @@ -181,7 +181,7 @@ func TestPlanner_addInstallInstructionWithRestartStamp(t *testing.T) {
a.Contains(instruction.Args, tt.args.scriptName)
a.GreaterOrEqual(len(instruction.Env), 1)
for _, e := range tt.args.envs {
a.True(findEnv(instruction.Env, e), "couldn't find %s in environment", e)
a.True(findEnvName(instruction.Env, e), "couldn't find %s in environment", e)
}
})
}
Expand Down Expand Up @@ -211,7 +211,7 @@ func TestPlanner_generateInstallInstructionWithSkipStart(t *testing.T) {
os: "linux",
command: "sh",
scriptName: "run.sh",
envs: []string{"INSTALL_RKE2_SKIP_START=true"},
envs: []string{"INSTALL_RKE2_SKIP_START"},
image: "my/custom-image-",
expectedImage: "my/custom-image-rke2:v1.21.5-rke2r2",
},
Expand All @@ -224,7 +224,7 @@ func TestPlanner_generateInstallInstructionWithSkipStart(t *testing.T) {
os: "windows",
command: "powershell.exe",
scriptName: "run.ps1",
envs: []string{"$env:INSTALL_RKE2_SKIP_START=\"true\""},
envs: []string{"INSTALL_RKE2_SKIP_START"},
image: "my/custom-image-",
expectedImage: "my/custom-image-rke2:v1.21.5-rke2r2",
},
Expand Down Expand Up @@ -253,7 +253,7 @@ func TestPlanner_generateInstallInstructionWithSkipStart(t *testing.T) {
a.Contains(p.Args, tt.args.scriptName)
a.GreaterOrEqual(len(p.Env), 1)
for _, e := range tt.args.envs {
a.True(findEnv(p.Env, e), "couldn't find %s in environment", e)
a.True(findEnvName(p.Env, e), "couldn't find %s in environment", e)
}
})
}
Expand Down
Loading

0 comments on commit ad91ce9

Please sign in to comment.