1
0
mirror of https://github.com/openshift/installer.git synced 2026-02-05 06:46:36 +01:00

Fix issue CORS-4275 Add Windows support

This PR improves cross-platform compatibility.
It solves two main issues:
1. inconsistent line endings
2. inconsistent path separators

Path separators, in installer, needs to target two different
environments:
1. the OS where the installer runs
2. the OS where the injected files been used

This PR unified path separators used in 2 to be UNIX path separators,
while in 1 to be platform-dependant.

Ref: https://forum.golangbridge.org/t/filepath-join-or-path-join/13479

Known issues:
The spawn processes, including etcd.exe, kube-apiserver.exe,
and openshift-installer.exe, will not exit once installation
aborted or completed. Users need to manually terminate those
processes in task manager.
This commit is contained in:
Bear
2025-10-27 17:08:18 -02:30
parent c44c9564fb
commit 5166c3e4e4
32 changed files with 158 additions and 63 deletions

6
.gitattributes vendored Normal file
View File

@@ -0,0 +1,6 @@
# the following files will be injected into the bootstrap node (CoreOS) as-is,
# which must be in Unix line endings (LF)
data/data/bootstrap/systemd/**/*.service text eol=lf
data/data/bootstrap/systemd/**/*.socket text eol=lf
data/data/bootstrap/systemd/**/*.conf text eol=lf
data/data/bootstrap/files/** text eol=lf

View File

@@ -316,14 +316,14 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err
// add ZTP manifests to manifestPath
for _, file := range agentManifests.FileList {
manifestFile := ignition.FileFromBytes(filepath.Join(manifestPath, filepath.Base(file.Filename)),
manifestFile := ignition.FileFromBytes(path.Join(manifestPath, filepath.Base(file.Filename)),
"root", 0600, file.Data)
config.Storage.Files = append(config.Storage.Files, manifestFile)
}
// add AgentConfig if provided
if agentConfigAsset.Config != nil {
agentConfigFile := ignition.FileFromBytes(filepath.Join(manifestPath, filepath.Base(agentConfigAsset.File.Filename)),
agentConfigFile := ignition.FileFromBytes(path.Join(manifestPath, filepath.Base(agentConfigAsset.File.Filename)),
"root", 0600, agentConfigAsset.File.Data)
config.Storage.Files = append(config.Storage.Files, agentConfigFile)
}
@@ -588,7 +588,7 @@ func addMacAddressToHostnameMappings(
}
for _, host := range agentHostsAsset.Hosts {
if host.Hostname != "" {
file := ignition.FileFromBytes(filepath.Join(hostnamesPath,
file := ignition.FileFromBytes(path.Join(hostnamesPath,
strings.ToLower(filepath.Base(host.Interfaces[0].MacAddress))),
"root", 0600, []byte(host.Hostname))
config.Storage.Files = append(config.Storage.Files, file)
@@ -602,8 +602,8 @@ func addHostConfig(config *igntypes.Config, agentHosts *agentconfig.AgentHosts)
return err
}
for path, content := range confs {
hostConfigFile := ignition.FileFromBytes(filepath.Join("/etc/assisted/hostconfig", path), "root", 0644, content)
for pathName, content := range confs {
hostConfigFile := ignition.FileFromBytes(path.Join("/etc/assisted/hostconfig", pathName), "root", 0644, content)
config.Storage.Files = append(config.Storage.Files, hostConfigFile)
}
return nil

View File

@@ -4,6 +4,7 @@ import (
"context"
"net/url"
"os"
"path"
"path/filepath"
"strings"
@@ -236,7 +237,7 @@ func (a *UnconfiguredIgnition) Generate(_ context.Context, dependencies asset.Pa
}
for _, file := range ztpManifestsToInclude {
manifestFile := ignition.FileFromBytes(filepath.Join(manifestPath, filepath.Base(file.Filename)),
manifestFile := ignition.FileFromBytes(path.Join(manifestPath, filepath.Base(file.Filename)),
"root", 0600, file.Data)
config.Storage.Files = append(config.Storage.Files, manifestFile)
}

View File

@@ -684,7 +684,7 @@ func (a *Common) addParentFiles(dependencies asset.Parents) {
rootCA := &tls.RootCA{}
dependencies.Get(rootCA)
a.Config.Storage.Files = replaceOrAppend(a.Config.Storage.Files, ignition.FileFromBytes(filepath.Join(rootDir, rootCA.CertFile().Filename), "root", 0644, rootCA.Cert()))
a.Config.Storage.Files = replaceOrAppend(a.Config.Storage.Files, ignition.FileFromBytes(path.Join(rootDir, rootCA.CertFile().Filename), "root", 0644, rootCA.Cert()))
}
func replaceOrAppend(files []igntypes.File, file igntypes.File) []igntypes.File {

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"os"
"path"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -18,10 +19,8 @@ import (
var (
_ asset.WritableAsset = (*CVOIgnore)(nil)
)
const (
cvoOverridesFilename = "manifests/cvo-overrides.yaml"
cvoOverridesFilename = path.Join("manifests", "cvo-overrides.yaml")
originalOverridesFilename = "original_cvo_overrides.patch"
)

View File

@@ -2,6 +2,7 @@ package ignition
import (
"fmt"
"path"
"path/filepath"
"github.com/clarketm/json"
@@ -30,7 +31,10 @@ func Marshal(input interface{}) ([]byte, error) {
func FilesFromAsset(pathPrefix string, username string, mode int, asset asset.WritableAsset) []igntypes.File {
var files []igntypes.File
for _, f := range asset.Files() {
files = append(files, FileFromBytes(filepath.Join(pathPrefix, f.Filename), username, mode, f.Data))
// f.Filename is using platform-specific path separators
// while its used in CoreOS, thus we need to convert it to Unix path separators
normalizedFilename := path.Join(pathPrefix, filepath.ToSlash(f.Filename))
files = append(files, FileFromBytes(normalizedFilename, username, mode, f.Data))
}
return files
}

View File

@@ -5,7 +5,7 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"path/filepath"
"path"
"strings"
"github.com/pkg/errors"
@@ -20,7 +20,7 @@ import (
)
var (
additionalTrustBundleConfigFileName = filepath.Join(manifestDir, "user-ca-bundle-config.yaml")
additionalTrustBundleConfigFileName = path.Join(manifestDir, "user-ca-bundle-config.yaml")
)
const (

View File

@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"path"
"github.com/IBM/vpc-go-sdk/vpcv1"
"github.com/pkg/errors"
@@ -43,7 +43,7 @@ import (
)
var (
cloudProviderConfigFileName = filepath.Join(manifestDir, "cloud-provider-config.yaml")
cloudProviderConfigFileName = path.Join(manifestDir, "cloud-provider-config.yaml")
)
const (

View File

@@ -2,7 +2,7 @@ package manifests
import (
"context"
"path/filepath"
"path"
"github.com/pkg/errors"
@@ -19,7 +19,7 @@ import (
)
var (
clusterCSIDriverConfigFileName = filepath.Join(manifestDir, "cluster-csi-driver-config.yaml")
clusterCSIDriverConfigFileName = path.Join(manifestDir, "cluster-csi-driver-config.yaml")
)
// ClusterCSIDriverConfig generates the cluster-csi-driver-config.yaml file.

View File

@@ -3,7 +3,7 @@ package manifests
import (
"context"
"fmt"
"path/filepath"
"path"
"strings"
"github.com/pkg/errors"
@@ -35,7 +35,7 @@ import (
)
var (
dnsCfgFilename = filepath.Join(manifestDir, "cluster-dns-02-config.yml")
dnsCfgFilename = path.Join(manifestDir, "cluster-dns-02-config.yml")
combineGCPZoneInfo = func(project, zoneName string) string {
return fmt.Sprintf("project/%s/managedZones/%s", project, zoneName)

View File

@@ -2,7 +2,7 @@ package manifests
import (
"context"
"path/filepath"
"path"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -14,7 +14,7 @@ import (
"github.com/openshift/installer/pkg/types/featuregates"
)
var fgFileName = filepath.Join(openshiftManifestDir, "99_feature-gate.yaml")
var fgFileName = path.Join(openshiftManifestDir, "99_feature-gate.yaml")
// FeatureGate generates the feature gate manifest.
type FeatureGate struct {

View File

@@ -2,7 +2,7 @@ package manifests
import (
"context"
"path/filepath"
"path"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -49,7 +49,7 @@ func (p *ImageDigestMirrorSet) Generate(_ context.Context, dependencies asset.Pa
return errors.Wrapf(err, "failed to marshal ImageDigestMirrorSet")
}
p.File = &asset.File{
Filename: filepath.Join(manifestDir, imageDigestMirrorSetFilename),
Filename: path.Join(manifestDir, imageDigestMirrorSetFilename),
Data: policyData,
}
}

View File

@@ -2,7 +2,7 @@ package manifests
import (
"context"
"path/filepath"
"path"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -62,7 +62,7 @@ func (p *ImageContentSourcePolicy) Generate(_ context.Context, dependencies asse
return errors.Wrapf(err, "failed to marshal ImageContentSourcePolicy")
}
p.File = &asset.File{
Filename: filepath.Join(manifestDir, imageContentSourcePolicyFilename),
Filename: path.Join(manifestDir, imageContentSourcePolicyFilename),
Data: policyData,
}
}

View File

@@ -3,7 +3,7 @@ package manifests
import (
"context"
"fmt"
"path/filepath"
"path"
"sort"
"github.com/pkg/errors"
@@ -35,8 +35,8 @@ import (
)
var (
infraCfgFilename = filepath.Join(manifestDir, "cluster-infrastructure-02-config.yml")
cloudControllerUIDFilename = filepath.Join(manifestDir, "cloud-controller-uid-config.yml")
infraCfgFilename = path.Join(manifestDir, "cluster-infrastructure-02-config.yml")
cloudControllerUIDFilename = path.Join(manifestDir, "cloud-controller-uid-config.yml")
)
// Infrastructure generates the cluster-infrastructure-*.yml files.

View File

@@ -3,7 +3,7 @@ package manifests
import (
"context"
"fmt"
"path/filepath"
"path"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -18,8 +18,8 @@ import (
)
var (
clusterIngressConfigFile = filepath.Join(manifestDir, "cluster-ingress-02-config.yml")
defaultIngressControllerFile = filepath.Join(manifestDir, "cluster-ingress-default-ingresscontroller.yaml")
clusterIngressConfigFile = path.Join(manifestDir, "cluster-ingress-02-config.yml")
defaultIngressControllerFile = path.Join(manifestDir, "cluster-ingress-default-ingresscontroller.yaml")
)
// Ingress generates the cluster-ingress-*.yml files.

View File

@@ -1,6 +1,7 @@
package manifests
import (
"path"
"path/filepath"
"strings"
@@ -24,7 +25,7 @@ func generateMCOManifest(installConfig *types.InstallConfig, template []*asset.F
mcoCfg := applyTemplateData(template[0].Data, tmplData)
return []*asset.File{
{
Filename: filepath.Join(manifestDir, strings.TrimSuffix(filepath.Base(template[0].Filename), ".template")),
Filename: path.Join(manifestDir, strings.TrimSuffix(filepath.Base(template[0].Filename), ".template")),
Data: mcoCfg,
},
}

View File

@@ -3,7 +3,7 @@ package manifests
import (
"context"
"fmt"
"path/filepath"
"path"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -19,8 +19,8 @@ import (
)
var (
noCfgFilename = filepath.Join(manifestDir, "cluster-network-02-config.yml")
cnoCfgFilename = filepath.Join(manifestDir, "cluster-network-03-config.yml")
noCfgFilename = path.Join(manifestDir, "cluster-network-02-config.yml")
cnoCfgFilename = path.Join(manifestDir, "cluster-network-03-config.yml")
// Cluster Network MTU for AWS Local Zone deployments on edge machine pools.
ovnKubernetesNetworkMtuEdge uint32 = 1200
)

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"os"
"path"
"path/filepath"
"strconv"
"strings"
@@ -286,7 +287,7 @@ func (o *Openshift) Generate(ctx context.Context, dependencies asset.Parents) er
continue
}
o.FileList = append(o.FileList, &asset.File{
Filename: filepath.Join(openshiftManifestDir, name),
Filename: path.Join(openshiftManifestDir, name),
Data: data,
})
}

View File

@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"encoding/base64"
"path"
"path/filepath"
"strings"
"text/template"
@@ -32,7 +33,7 @@ const (
)
var (
kubeSysConfigPath = filepath.Join(manifestDir, "cluster-config.yaml")
kubeSysConfigPath = path.Join(manifestDir, "cluster-config.yaml")
_ asset.WritableAsset = (*Manifests)(nil)
@@ -216,7 +217,7 @@ func (m *Manifests) generateBootKubeManifests(dependencies asset.Parents) []*ass
dependencies.Get(a)
for _, f := range a.Files() {
files = append(files, &asset.File{
Filename: filepath.Join(manifestDir, strings.TrimSuffix(filepath.Base(f.Filename), ".template")),
Filename: path.Join(manifestDir, strings.TrimSuffix(filepath.Base(f.Filename), ".template")),
Data: applyTemplateData(f.Data, templateData),
})
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"fmt"
"net/url"
"path/filepath"
"path"
"strings"
"github.com/pkg/errors"
@@ -22,7 +22,7 @@ import (
"github.com/openshift/installer/pkg/types/openstack"
)
var proxyCfgFilename = filepath.Join(manifestDir, "cluster-proxy-01-config.yaml")
var proxyCfgFilename = path.Join(manifestDir, "cluster-proxy-01-config.yaml")
// Proxy generates the cluster-proxy-*.yml files.
type Proxy struct {

View File

@@ -2,7 +2,7 @@ package manifests
import (
"context"
"path/filepath"
"path"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -16,7 +16,7 @@ import (
var (
// SchedulerCfgFilename is the path of the Scheduler Config file
SchedulerCfgFilename = filepath.Join(manifestDir, "cluster-scheduler-02-config.yml")
SchedulerCfgFilename = path.Join(manifestDir, "cluster-scheduler-02-config.yml")
)
// Scheduler generates the cluster-scheduler-*.yml files.

View File

@@ -3,7 +3,7 @@ package openshiftinstall
import (
"context"
"os"
"path/filepath"
"path"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
@@ -15,7 +15,7 @@ import (
)
var (
configPath = filepath.Join("openshift", "openshift-install-manifests.yaml")
configPath = path.Join("openshift", "openshift-install-manifests.yaml")
)
// Config generates the openshift-install ConfigMap.

View File

@@ -3,7 +3,6 @@ package tls
import (
"context"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -43,7 +42,7 @@ func (sk *BoundSASigningKey) Files() []*asset.File {
// Load reads the private key from the disk.
// It ensures that the key provided is a valid RSA key.
func (sk *BoundSASigningKey) Load(f asset.FileFetcher) (bool, error) {
keyFile, err := f.FetchByName(filepath.Join(tlsDir, "bound-service-account-signing-key.key"))
keyFile, err := f.FetchByName(assetFilePath("bound-service-account-signing-key.key"))
if err != nil {
if os.IsNotExist(err) {
return false, nil
@@ -60,6 +59,6 @@ func (sk *BoundSASigningKey) Load(f asset.FileFetcher) (bool, error) {
if err != nil {
return false, errors.Wrap(err, "failed to extract public key from the key")
}
sk.FileList = []*asset.File{keyFile, {Filename: filepath.Join(tlsDir, "bound-service-account-signing-key.pub"), Data: pubData}}
sk.FileList = []*asset.File{keyFile, {Filename: assetFilePath("bound-service-account-signing-key.pub"), Data: pubData}}
return true, nil
}

View File

@@ -3,7 +3,7 @@ package tls
import (
"fmt"
"net"
"path/filepath"
"path"
"github.com/apparentlymart/go-cidr/cidr"
@@ -15,7 +15,7 @@ const (
)
func assetFilePath(filename string) string {
return filepath.Join(tlsDir, filename)
return path.Join(tlsDir, filename)
}
func apiAddress(cfg *types.InstallConfig) string {

View File

@@ -12,7 +12,6 @@ import (
"path"
"regexp"
"sync"
"syscall"
"time"
"github.com/sirupsen/logrus"
@@ -133,9 +132,7 @@ func (ps *State) Start(ctx context.Context, stdout io.Writer, stderr io.Writer)
ps.Cmd.Stdout = stdout
ps.Cmd.Stderr = stderr
ps.Cmd.Dir = ps.Dir
ps.Cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
setSysProcAttr(ps.Cmd)
ready := make(chan bool)
timedOut := time.After(ps.StartTimeout)
@@ -178,7 +175,7 @@ func (ps *State) Start(ctx context.Context, stdout io.Writer, stderr io.Writer)
close(pollerStopCh)
if ps.Cmd != nil {
// intentionally ignore this -- we might've crashed, failed to start, etc
ps.Cmd.Process.Signal(syscall.SIGTERM) //nolint:errcheck
stopProcess(ps) //nolint:errcheck
}
return fmt.Errorf("timeout waiting for process %s to start", path.Base(ps.Path))
}
@@ -237,7 +234,7 @@ func (ps *State) Stop() error {
}
return nil
}
if err := ps.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
if err := stopProcess(ps); err != nil {
return fmt.Errorf("unable to signal for process %s to stop: %w", ps.Path, err)
}
@@ -246,10 +243,10 @@ func (ps *State) Stop() error {
case <-ps.waitDone:
break
case <-timedOut:
if err := ps.Cmd.Process.Signal(syscall.SIGKILL); err != nil {
return fmt.Errorf("unable to signal for process %s to stop: %w", ps.Path, err)
if err := killProcess(ps); err != nil {
return fmt.Errorf("unable to kill process %s: %w", ps.Path, err)
}
return fmt.Errorf("timeout waiting for process %s to stop, sent SIGKILL", path.Base(ps.Path))
return fmt.Errorf("timeout waiting for process %s to stop, killed process", path.Base(ps.Path))
}
ps.ready = false
return nil

View File

@@ -0,0 +1,23 @@
//go:build !windows
// +build !windows
package process
import (
"os/exec"
"syscall"
)
func setSysProcAttr(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
}
func stopProcess(ps *State) error {
return ps.Cmd.Process.Signal(syscall.SIGTERM)
}
func killProcess(ps *State) error {
return ps.Cmd.Process.Signal(syscall.SIGKILL)
}

View File

@@ -0,0 +1,19 @@
//go:build windows
// +build windows
package process
import (
"os/exec"
)
func setSysProcAttr(cmd *exec.Cmd) {
}
func stopProcess(ps *State) error {
return ps.Cmd.Process.Kill()
}
func killProcess(ps *State) error {
return ps.Cmd.Process.Kill()
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
capnv1 "github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
"github.com/sirupsen/logrus"
@@ -84,6 +85,11 @@ func (c *localControlPlane) Run(ctx context.Context) error {
return fmt.Errorf("failed to create kube-apiserver log file: %w", err)
}
if runtime.GOOS == "windows" {
os.Setenv("TEST_ASSET_ETCD", filepath.Join(c.BinDir, "etcd.exe"))
os.Setenv("TEST_ASSET_KUBE_APISERVER", filepath.Join(c.BinDir, "kube-apiserver.exe"))
}
log.SetLogger(klog.NewKlogr())
logrus.Info("Started local control plane with envtest")
c.Env = &envtest.Environment{

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
@@ -78,7 +79,7 @@ var Mirror embed.FS
// Extract extracts the provider from the embedded data into the specified directory.
func (p Provider) Extract(dir string) error {
f, err := Mirror.Open(filepath.Join("mirror", zipFile))
f, err := Mirror.Open(path.Join("mirror", zipFile))
if err != nil {
return errors.Wrap(err, "failed to open cluster api zip from mirror")
}
@@ -113,9 +114,11 @@ func (p Provider) Extract(dir string) error {
// Extract the files.
for _, f := range r.File {
name := f.Name
if !p.Sources.Has(name) {
nameWithoutExt := strings.TrimSuffix(name, ".exe")
if !p.Sources.Has(name) && !p.Sources.Has(nameWithoutExt) {
continue
}
path, err := sanitizeArchivePath(dir, name)
if err != nil {
return errors.Wrapf(err, "failed to sanitize archive file %q", name)

View File

@@ -18,7 +18,6 @@ import (
"github.com/sirupsen/logrus"
"github.com/thedevsaddam/retry"
"github.com/ulikunitz/xz"
"golang.org/x/sys/unix"
)
const (
@@ -99,12 +98,12 @@ func cacheFile(reader io.Reader, filePath string, sha256Checksum string) (err er
}
}()
err = unix.Flock(int(flock.Fd()), unix.LOCK_EX)
err = flockFile(flock, true)
if err != nil {
return err
}
defer func() {
err2 := unix.Flock(int(flock.Fd()), unix.LOCK_UN)
err2 := flockFile(flock, false)
if err == nil {
err = err2
}

17
pkg/rhcos/cache/flock_unix.go vendored Normal file
View File

@@ -0,0 +1,17 @@
//go:build !windows
// +build !windows
package cache
import (
"os"
"golang.org/x/sys/unix"
)
func flockFile(f *os.File, lock bool) error {
if lock {
return unix.Flock(int(f.Fd()), unix.LOCK_EX)
}
return unix.Flock(int(f.Fd()), unix.LOCK_UN)
}

19
pkg/rhcos/cache/flock_windows.go vendored Normal file
View File

@@ -0,0 +1,19 @@
//go:build windows
// +build windows
package cache
import (
"os"
"golang.org/x/sys/windows"
)
func flockFile(f *os.File, lock bool) error {
if lock {
var overlapped windows.Overlapped
return windows.LockFileEx(windows.Handle(f.Fd()), windows.LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &overlapped)
}
var overlapped windows.Overlapped
return windows.UnlockFileEx(windows.Handle(f.Fd()), 0, 1, 0, &overlapped)
}