Add support for windows volume (#1417)

Signed-off-by: aiyijing <aiyijing@live.com>
This commit is contained in:
AiYijing 2021-08-24 10:09:34 +08:00 committed by GitHub
parent e8966d9e2c
commit 473f54fdaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 594 additions and 0 deletions

View File

@ -64,6 +64,13 @@ func CreateOutFile(out string) (*os.File, error) {
// ParseVolume parses a given volume, which might be [name:][host:]container[:access_mode]
func ParseVolume(volume string) (name, host, container, mode string, err error) {
if containWindowsPath(volume) {
return parseWindowsVolume(volume)
}
return parseVolume(volume)
}
func parseVolume(volume string) (name, host, container, mode string, err error) {
separator := ":"
// Parse based on ":"
@ -112,6 +119,88 @@ func ParseVolume(volume string) (name, host, container, mode string, err error)
return
}
// parseVolume parses window volume.
// example: windows host mount to windows container
// volume = dataVolumeName:C:\Users\Data:D:\config:rw
// it can be parsed:
// name=dataVolumeName, host=C:\Users\Data, container=D:\config, mode=rw
// example: windows host mount to linux container
// volume = dataVolumeName:C:\Users\Data:/etc/config:rw
// it can be parsed:
// name=dataVolumeName, host=C:\Users\Data, container=/etc/config, mode=rw
func parseWindowsVolume(volume string) (name, host, container, mode string, err error) {
var (
buffer, volumePaths []string
volumeStrings = strings.Split(volume, ":")
)
// extract path and leave order
for _, fragment := range volumeStrings {
switch {
case containWindowsPath(fragment):
if len(buffer) == 0 {
err = fmt.Errorf("invalid windows volume %s", volume)
return
}
driveLetter := buffer[len(buffer)-1]
if len(driveLetter) != 1 {
err = fmt.Errorf("invalid windows volume %s", volume)
return
}
volumePaths = append(volumePaths, driveLetter+":"+fragment)
buffer = buffer[:len(buffer)-1]
case isPath(fragment):
volumePaths = append(volumePaths, fragment)
default:
buffer = append(buffer, fragment)
}
}
// set name and mode if exist
if len(buffer) == 1 {
if volumeStrings[0] == buffer[0] {
name = buffer[0]
} else if volumeStrings[len(volumeStrings)-1] == buffer[0] {
mode = buffer[0]
}
} else if len(buffer) == 2 {
name = buffer[0]
mode = buffer[1]
} else if len(buffer) > 2 {
err = fmt.Errorf("invalid windows volume %s", volume)
return
}
// Support in pass time
// Check to see if :Z or :z exists. We do not support SELinux relabeling at the moment.
// See https://github.com/kubernetes/kompose/issues/176
// Otherwise, check to see if "rw" or "ro" has been passed
if mode == "z" || mode == "Z" {
log.Warnf("Volume mount \"%s\" will be mounted without labeling support. :z or :Z not supported", volume)
mode = ""
}
// Set host and container if exist
if len(volumePaths) == 1 {
container = volumePaths[0]
} else if len(volumePaths) == 2 {
host = volumePaths[0]
container = volumePaths[1]
} else {
err = fmt.Errorf("invalid windows volume %s", volume)
return
}
return
}
// containWindowsPath check whether it contains windows path.
// windows path's separator is "\"
func containWindowsPath(substring string) bool {
return strings.Contains(substring, "\\")
}
// ParseIngressPath parse path for ingress.
// eg. example.com/org -> example.com org
func ParseIngressPath(url string) (string, string) {

View File

@ -34,6 +34,7 @@ func TestFormatProviderName(t *testing.T) {
// When passing "z" or "Z" we expect "" back.
func TestZParseVolumeLabeling(t *testing.T) {
testCase := "/foobar:/foobar:Z"
windowVolumeTestCase := "C:\\foobar:/foobar:Z"
_, _, _, mode, err := ParseVolume(testCase)
if err != nil {
t.Errorf("In test case %q, returned unexpected error %v", testCase, err)
@ -41,6 +42,204 @@ func TestZParseVolumeLabeling(t *testing.T) {
if mode != "" {
t.Errorf("In test case %q, returned mode %s, expected \"\"", testCase, mode)
}
_, _, _, mode, err = ParseVolume(windowVolumeTestCase)
if err != nil {
t.Errorf("In test case %q, returned unexpected error %v", windowVolumeTestCase, err)
}
if mode != "" {
t.Errorf("In test case %q, returned mode %s, expected \"\"", windowVolumeTestCase, mode)
}
}
func TestParseWindowsVolumeMountLinuxContainer(t *testing.T) {
name := "datavolume"
windowsHosts := "C:\\Users"
linuxContainer := "/etc/configs/"
mode := "rw"
tests := []struct {
test, volume, name, host, container, mode string
}{
{
"name:host:container:mode",
fmt.Sprintf("%s:%s:%s:%s", name, windowsHosts, linuxContainer, mode),
name,
windowsHosts,
linuxContainer,
mode,
},
{
"host:container:mode",
fmt.Sprintf("%s:%s:%s", windowsHosts, linuxContainer, mode),
"",
windowsHosts,
linuxContainer,
mode,
},
{
"name:container:mode",
fmt.Sprintf("%s:%s:%s", name, linuxContainer, mode),
name,
"",
linuxContainer,
mode,
},
{
"name:host:container",
fmt.Sprintf("%s:%s:%s", name, windowsHosts, linuxContainer),
name,
windowsHosts,
linuxContainer,
"",
},
{
"host:container",
fmt.Sprintf("%s:%s", windowsHosts, linuxContainer),
"",
windowsHosts,
linuxContainer,
"",
},
{
"container:mode",
fmt.Sprintf("%s:%s", linuxContainer, mode),
"",
"",
linuxContainer,
mode,
},
{
"name:container",
fmt.Sprintf("%s:%s", name, linuxContainer),
name,
"",
linuxContainer,
"",
},
{
"container",
fmt.Sprintf("%s", linuxContainer),
"",
"",
linuxContainer,
"",
},
}
for _, test := range tests {
name, host, container, mode, err := ParseVolume(test.volume)
if err != nil {
t.Errorf("In test case %q, returned unexpected error %v", test.test, err)
}
if name != test.name {
t.Errorf("In test case %q, returned volume name %s, expected %s", test.test, name, test.name)
}
if host != test.host {
t.Errorf("In test case %q, returned host path %s, expected %s", test.test, host, test.host)
}
if container != test.container {
t.Errorf("In test case %q, returned container path %s, expected %s", test.test, container, test.container)
}
if mode != test.mode {
t.Errorf("In test case %q, returned access mode %s, expected %s", test.test, mode, test.mode)
}
}
}
func TestParseWindowsVolumeMountWindowsContainer(t *testing.T) {
name := "datavolume"
windowsHosts := "C:\\Users"
windowsContainer := "D:\\Users"
mode := "rw"
tests := []struct {
test, volume, name, host, container, mode string
}{
{
"name:host:container:mode",
fmt.Sprintf("%s:%s:%s:%s", name, windowsHosts, windowsContainer, mode),
name,
windowsHosts,
windowsContainer,
mode,
},
{
"host:container:mode",
fmt.Sprintf("%s:%s:%s", windowsHosts, windowsContainer, mode),
"",
windowsHosts,
windowsContainer,
mode,
},
{
"name:container:mode",
fmt.Sprintf("%s:%s:%s", name, windowsContainer, mode),
name,
"",
windowsContainer,
mode,
},
{
"name:host:container",
fmt.Sprintf("%s:%s:%s", name, windowsHosts, windowsContainer),
name,
windowsHosts,
windowsContainer,
"",
},
{
"host:container",
fmt.Sprintf("%s:%s", windowsHosts, windowsContainer),
"",
windowsHosts,
windowsContainer,
"",
},
{
"container:mode",
fmt.Sprintf("%s:%s", windowsContainer, mode),
"",
"",
windowsContainer,
mode,
},
{
"name:container",
fmt.Sprintf("%s:%s", name, windowsContainer),
name,
"",
windowsContainer,
"",
},
{
"container",
fmt.Sprintf("%s", windowsContainer),
"",
"",
windowsContainer,
"",
},
}
for _, test := range tests {
name, host, container, mode, err := ParseVolume(test.volume)
if err != nil {
t.Errorf("In test case %q, returned unexpected error %v", test.test, err)
}
if name != test.name {
t.Errorf("In test case %q, returned volume name %s, expected %s", test.test, name, test.name)
}
if host != test.host {
t.Errorf("In test case %q, returned host path %s, expected %s", test.test, host, test.host)
}
if container != test.container {
t.Errorf("In test case %q, returned container path %s, expected %s", test.test, container, test.container)
}
if mode != test.mode {
t.Errorf("In test case %q, returned access mode %s, expected %s", test.test, mode, test.mode)
}
}
}
func TestParseVolume(t *testing.T) {

View File

@ -106,3 +106,12 @@ convert::expect_warning "$cmd" "Push image registry 'whatever' is specified but
#TEST the kompose.volume.storage-class-name label
convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/storage-class-name/docker-compose.yml convert -o $TEMP_DIR/output-k8s.json -j" "$TEMP_DIR/output-k8s.json"
convert::check_artifacts_generated "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/storage-class-name/docker-compose.yml convert -o $TEMP_DIR/output-os.json -j" "$TEMP_DIR/output-os.json"
# TEST the windows volume
# windows host path to windows container
k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/docker-compose.yaml convert --stdout -j --with-kompose-annotation=false"
os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/docker-compose.yaml convert --stdout -j --with-kompose-annotation=false"
k8s_output="$KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/output-k8s.json"
os_output="$KOMPOSE_ROOT/script/test/fixtures/volume-mounts/windows/output-os.json"
convert::expect_success "$k8s_cmd" "$k8s_output"
convert::expect_success "$os_cmd" "$os_output"

View File

@ -0,0 +1,8 @@
version: '3.4'
services:
db:
image: nginx:latest
ports:
- "80"
volumes:
- C:\Users\data_sux:D:\config:rw

View File

@ -0,0 +1,115 @@
{
"kind": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "db",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db"
}
},
"spec": {
"ports": [
{
"name": "80",
"port": 80,
"targetPort": 80
}
],
"selector": {
"io.kompose.service": "db"
}
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "Deployment",
"apiVersion": "apps/v1",
"metadata": {
"name": "db",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"io.kompose.service": "db"
}
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db"
}
},
"spec": {
"volumes": [
{
"name": "db-claim0",
"persistentVolumeClaim": {
"claimName": "db-claim0"
}
}
],
"containers": [
{
"name": "db",
"image": "nginx:latest",
"ports": [
{
"containerPort": 80
}
],
"resources": {},
"volumeMounts": [
{
"name": "db-claim0",
"mountPath": "D:\\config"
}
]
}
],
"restartPolicy": "Always"
}
},
"strategy": {
"type": "Recreate"
}
},
"status": {}
},
{
"kind": "PersistentVolumeClaim",
"apiVersion": "v1",
"metadata": {
"name": "db-claim0",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db-claim0"
}
},
"spec": {
"accessModes": [
"ReadWriteOnce"
],
"resources": {
"requests": {
"storage": "100Mi"
}
}
},
"status": {}
}
]
}

View File

@ -0,0 +1,174 @@
{
"kind": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "db",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db"
}
},
"spec": {
"ports": [
{
"name": "80",
"port": 80,
"targetPort": 80
}
],
"selector": {
"io.kompose.service": "db"
}
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "DeploymentConfig",
"apiVersion": "v1",
"metadata": {
"name": "db",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db"
}
},
"spec": {
"strategy": {
"type": "Recreate",
"resources": {}
},
"triggers": [
{
"type": "ConfigChange"
},
{
"type": "ImageChange",
"imageChangeParams": {
"automatic": true,
"containerNames": [
"db"
],
"from": {
"kind": "ImageStreamTag",
"name": "db:latest"
}
}
}
],
"replicas": 1,
"test": false,
"selector": {
"io.kompose.service": "db"
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db"
}
},
"spec": {
"volumes": [
{
"name": "db-claim0",
"persistentVolumeClaim": {
"claimName": "db-claim0"
}
}
],
"containers": [
{
"name": "db",
"image": " ",
"ports": [
{
"containerPort": 80
}
],
"resources": {},
"volumeMounts": [
{
"name": "db-claim0",
"mountPath": "D:\\config"
}
]
}
],
"restartPolicy": "Always"
}
}
},
"status": {
"latestVersion": 0,
"observedGeneration": 0,
"replicas": 0,
"updatedReplicas": 0,
"availableReplicas": 0,
"unavailableReplicas": 0
}
},
{
"kind": "ImageStream",
"apiVersion": "v1",
"metadata": {
"name": "db",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db"
}
},
"spec": {
"lookupPolicy": {
"local": false
},
"tags": [
{
"name": "",
"annotations": null,
"from": {
"kind": "DockerImage",
"name": "nginx:latest"
},
"generation": null,
"importPolicy": {},
"referencePolicy": {
"type": ""
}
}
]
},
"status": {
"dockerImageRepository": ""
}
},
{
"kind": "PersistentVolumeClaim",
"apiVersion": "v1",
"metadata": {
"name": "db-claim0",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "db-claim0"
}
},
"spec": {
"accessModes": [
"ReadWriteOnce"
],
"resources": {
"requests": {
"storage": "100Mi"
}
}
},
"status": {}
}
]
}