From b75558d05012a198077fc1984534bdb156f0ba2d Mon Sep 17 00:00:00 2001 From: Mauro Morales Date: Sun, 23 Apr 2023 22:07:56 +0200 Subject: [PATCH] :bug: deep merge of cloud config broken for maps in slices fixes #1341 Signed-off-by: Mauro Morales --- go.mod | 4 +- go.sum | 31 +--- pkg/config/collector/collector.go | 86 ++++++++++- pkg/config/collector/collector_test.go | 201 ++++++++++++++++++++++++- 4 files changed, 284 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index 2f0a61e91..18af582a7 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,7 @@ require ( github.com/avast/retry-go v3.0.0+incompatible github.com/erikgeiser/promptkit v0.8.0 github.com/google/go-github/v40 v40.0.0 - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.3.0 - github.com/imdario/mergo v0.3.15 github.com/itchyny/gojq v0.12.12 github.com/jaypipes/ghw v0.10.0 github.com/kairos-io/kairos-sdk v0.0.2-0.20230414094028-0c9d2bd9e6ae @@ -64,9 +62,11 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gookit/color v1.5.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect diff --git a/go.sum b/go.sum index f5f292249..264541a6d 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzX github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= @@ -30,7 +28,7 @@ github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTx github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/bool64/dev v0.2.25 h1:p6euAfe1zLXb1qzLssm0lJnM5KhfUZp/Qjb2dsPkIKU= +github.com/bool64/dev v0.2.27 h1:mFT+B74mFVgUeUmm/EbfM6ELPA55lEXBjQ/AOHCwCOc= github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= github.com/bramvdbogaerde/go-scp v1.2.1 h1:BKTqrqXiQYovrDlfuVFaEGz0r4Ou6EED8L7jCXw6Buw= github.com/bramvdbogaerde/go-scp v1.2.1/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yPXQTPshACY8vQ0= @@ -117,8 +115,6 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= -github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= -github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -150,8 +146,6 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kairos-io/kairos v1.24.3-56.0.20230329142538-b6ae4b58c07d h1:B01GinEZbowPwbWrqDIb6n2AaHIscJVOqsh0I5gAEXw= github.com/kairos-io/kairos v1.24.3-56.0.20230329142538-b6ae4b58c07d/go.mod h1:2aYSSCHw8csfuqA5g6BpxBJ89kZt84G5okeuJj7PH+w= -github.com/kairos-io/kairos-sdk v0.0.2-0.20230317135804-ad3c0f6cd6dd h1:x3pwiwfj/eAv3OZ8BNFdmpjhh8ZInu7z8Xv/6+dhmFw= -github.com/kairos-io/kairos-sdk v0.0.2-0.20230317135804-ad3c0f6cd6dd/go.mod h1:Wg/jfAQe8seka5VUXtcPvg+sA6GmQEy+DYlJmgKM8Zs= github.com/kairos-io/kairos-sdk v0.0.2-0.20230414094028-0c9d2bd9e6ae h1:u/1QiU5IAJNDPxsBWBQFKQxLJKcDog1aMrN0unaP18w= github.com/kairos-io/kairos-sdk v0.0.2-0.20230414094028-0c9d2bd9e6ae/go.mod h1:Wg/jfAQe8seka5VUXtcPvg+sA6GmQEy+DYlJmgKM8Zs= github.com/kairos-io/kcrypt v0.5.2 h1:F9jbIjk3+nSQYEoSTDXT118Cx8AjmtDrMcU4rq/WBsI= @@ -255,10 +249,6 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= -github.com/pterm/pterm v0.12.57 h1:HTjDUmILmh6hIsEidRdpxQAiqcoHCdvRCxIR3KZ0/XE= -github.com/pterm/pterm v0.12.57/go.mod h1:7rswprkyxYOse1IMh79w42jvReNHxro4z9oHfqjIdzM= -github.com/pterm/pterm v0.12.58 h1:MEImvkbvty8JvoJH64bJ+CvoCkcuRw2iBIJvRwAEgHI= -github.com/pterm/pterm v0.12.58/go.mod h1:Ro9CV954hiaxt3mcpDx4a8XF5EmRDlIIpPdlfCKF9fE= github.com/pterm/pterm v0.12.59 h1:VBStvXiZL+6L+nNYjlXsD/035RJF5crqOvgqAm/Rvns= github.com/pterm/pterm v0.12.59/go.mod h1:Lt90KhnId704siiQtMZiLS7UfoC7TRUM1HufzdM0kjk= github.com/qeesung/image2ascii v1.0.1 h1:Fe5zTnX/v/qNC3OC4P/cfASOXS501Xyw2UUcgrLgtp4= @@ -272,32 +262,23 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= -github.com/santhosh-tekuri/jsonschema/v5 v5.2.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 h1:uIkTLo0AGRc8l7h5l9r+GcYi9qfVPt6lD4/bhmzfiKo= github.com/santhosh-tekuri/jsonschema/v5 v5.3.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/spectrocloud/peg v0.0.0-20230301170947-e1afc769ab73 h1:VzFEC5pUg0FO9FLVC9/muXr6thbp+U6zWsgNurNA2Ss= -github.com/spectrocloud/peg v0.0.0-20230301170947-e1afc769ab73/go.mod h1:L2fIdtZqbQEagjOOXwkwH3t7MjJUd7fbt52cLSQGDBg= github.com/spectrocloud/peg v0.0.0-20230407121159-2e15270c4a46 h1:q2T2RnISqPdZWvUpBQw0n7QWtF4cNo5RpCDTZmV732M= github.com/spectrocloud/peg v0.0.0-20230407121159-2e15270c4a46/go.mod h1:L2fIdtZqbQEagjOOXwkwH3t7MjJUd7fbt52cLSQGDBg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw= -github.com/swaggest/jsonschema-go v0.3.48 h1:zscQIIh2DlUaPTgCntPOq9s9a5QQTeWcs2QTE6P7nEY= -github.com/swaggest/jsonschema-go v0.3.48/go.mod h1:67EkOWKuBlpuvfZW1djhCD4ZSGNdXUjrTEWe16S5uEg= github.com/swaggest/jsonschema-go v0.3.49 h1:0dB6+6/uuU9lH41evLVum9Ui1b1Pkm1mjtsHWYL+y30= github.com/swaggest/jsonschema-go v0.3.49/go.mod h1:fZmC8juuqFTMe4Fc9tHJXwG+Uaf9BKYvF8ygL+asOuY= github.com/swaggest/refl v1.1.0 h1:a+9a75Kv6ciMozPjVbOfcVTEQe81t2R3emvaD9oGQGc= @@ -370,13 +351,9 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -413,16 +390,12 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -430,8 +403,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/config/collector/collector.go b/pkg/config/collector/collector.go index beb1f6d3d..f0b3dc462 100644 --- a/pkg/config/collector/collector.go +++ b/pkg/config/collector/collector.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "path/filepath" + "reflect" "strings" "time" "unicode" @@ -16,7 +17,6 @@ import ( "github.com/kairos-io/kairos-sdk/machine" "github.com/avast/retry-go" - "github.com/imdario/mergo" "github.com/itchyny/gojq" "gopkg.in/yaml.v3" ) @@ -65,7 +65,89 @@ func (c *Config) MergeConfigURL() error { // MergeConfig merges the config passed as parameter back to the receiver Config. func (c *Config) MergeConfig(newConfig *Config) error { - return mergo.Merge(c, newConfig, func(c *mergo.Config) { c.Overwrite = true }) + var err error + var aMap map[string]interface{} + var bMap map[string]interface{} + aData, _ := yaml.Marshal(c) + yaml.Unmarshal(aData, &aMap) + bData, _ := yaml.Marshal(newConfig) + yaml.Unmarshal(bData, &bMap) + cMap, _ := DeepMerge(aMap, bMap) + + cData, _ := yaml.Marshal(cMap) + yaml.Unmarshal(cData, c) + return err +} + +func DeepMerge(a, b interface{}) (interface{}, error) { + if a == nil && b != nil { + return b, nil + } + + typeA := reflect.TypeOf(a) + typeB := reflect.TypeOf(b) + if typeA.Kind() != typeB.Kind() { + return map[string]interface{}{}, fmt.Errorf("Cannot merge %s with %s", typeA.String(), typeB.String()) + } + + if typeA.Kind() == reflect.Slice { + sliceA := a.([]interface{}) + sliceB := b.([]interface{}) + + firstItem := sliceA[0] + if reflect.ValueOf(firstItem).Kind() == reflect.Map { + temp := make(map[string]interface{}) + + for _, item := range sliceA { + for k, v := range item.(map[string]interface{}) { + temp[k] = v + } + } + + for _, item := range sliceB { + for k, v := range item.(map[string]interface{}) { + current, ok := temp[k] + if ok { + dm, _ := DeepMerge(current, v) + temp[k] = dm + } else { + temp[k] = v + } + } + } + + var result []interface{} + for k, v := range temp { + result = append(result, map[string]interface{}{k: v}) + } + + return result, nil + } else { + return append(sliceA, sliceB...), nil + } + } + + if typeA.Kind() == reflect.Map { + aMap := a.(map[string]interface{}) + bMap := b.(map[string]interface{}) + + for k, v := range bMap { + current, ok := aMap[k] + if ok { + res, err := DeepMerge(current, v) + if err != nil { + return aMap, err + } + aMap[k] = res + } else { + aMap[k] = v + } + } + + return aMap, nil + } + + return b, nil } // String returns a string which is a Yaml representation of the Config. diff --git a/pkg/config/collector/collector_test.go b/pkg/config/collector/collector_test.go index 5ed6e322f..610e88ef6 100644 --- a/pkg/config/collector/collector_test.go +++ b/pkg/config/collector/collector_test.go @@ -2,12 +2,11 @@ package collector_test import ( "fmt" + "github.com/kairos-io/kairos/v2/pkg/config" "os" "path" "path/filepath" - "github.com/kairos-io/kairos/v2/pkg/config" - . "github.com/kairos-io/kairos/v2/pkg/config/collector" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -77,7 +76,7 @@ info: }) It("merges the keys", func() { Expect(originalConfig.MergeConfig(newConfig)).ToNot(HaveOccurred()) - info, isMap := (*originalConfig)["info"].(map[interface{}]interface{}) + info, isMap := (*originalConfig)["info"].(Config) Expect(isMap).To(BeTrue()) Expect(info["name"]).To(Equal("Mario")) Expect(info["surname"]).To(Equal("Bros")) @@ -180,7 +179,7 @@ info: Expect(ok).To(BeTrue()) Expect(surname).To(Equal("Bras")) - info, ok := (*originalConfig)["info"].(map[interface{}]interface{}) + info, ok := (*originalConfig)["info"].(Config) Expect(ok).To(BeTrue()) Expect(info["job"]).To(Equal("plumber")) Expect(info["girlfriend"]).To(Equal("princess")) @@ -190,7 +189,201 @@ info: }) }) + Describe("deepMerge", func() { + Context("different types", func() { + a := map[string]interface{}{} + b := []string{} + + It("merges", func() { + _, err := DeepMerge(a, b) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("Cannot merge map[string]interface {} with []string")) + + _, err = DeepMerge(b, a) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("Cannot merge []string with map[string]interface {}")) + }) + }) + + Context("simple slices", func() { + a := []interface{}{"one", "three"} + b := []interface{}{"two", 4} + + It("merges", func() { + c, err := DeepMerge(a, b) + Expect(err).ToNot(HaveOccurred()) + Expect(c).To(Equal([]interface{}{"one", "three", "two", 4})) + }) + }) + + Context("slices containing maps", func() { + a := []interface{}{ + map[string]interface{}{ + "users": []interface{}{ + map[string]interface{}{ + "kairos": map[string]interface{}{ + "passwd": "kairos", + }, + }, + }, + }, + } + b := []interface{}{ + map[string]interface{}{ + "users": []interface{}{ + map[string]interface{}{ + "foo": map[string]interface{}{ + "passwd": "bar", + }, + }, + }, + }, + } + + It("merges", func() { + c, err := DeepMerge(a, b) + Expect(err).ToNot(HaveOccurred()) + users := c.([]interface{})[0].(map[string]interface{})["users"] + Expect(users).To(HaveLen(2)) + Expect(users).To(ContainElement( + map[string]interface{}{ + "kairos": map[string]interface{}{ + "passwd": "kairos", + }, + }, + )) + Expect(users).To(ContainElement( + map[string]interface{}{ + "foo": map[string]interface{}{ + "passwd": "bar", + }, + }, + )) + }) + }) + + Context("empty map", func() { + a := map[string]interface{}{} + b := map[string]interface{}{ + "foo": "bar", + } + + It("merges", func() { + c, err := DeepMerge(a, b) + Expect(err).ToNot(HaveOccurred()) + Expect(c).To(Equal(map[string]interface{}{ + "foo": "bar", + })) + }) + }) + + Context("simple map", func() { + a := map[string]interface{}{ + "es": "uno", + "nl": "een", + "#": 0, + } + b := map[string]interface{}{ + "en": "one", + "nl": "één", + "de": "Eins", + "#": 1, + } + + It("merges", func() { + c, err := DeepMerge(a, b) + Expect(err).ToNot(HaveOccurred()) + Expect(c).To(Equal(map[string]interface{}{ + "#": 1, + "de": "Eins", + "en": "one", + "es": "uno", + "nl": "één", + })) + }) + }) + }) + Describe("Scan", func() { + Context("Deep merge maps within arrays", func() { + var cmdLinePath, tmpDir1 string + var err error + + BeforeEach(func() { + tmpDir1, err = os.MkdirTemp("", "config1") + Expect(err).ToNot(HaveOccurred()) + err := os.WriteFile(path.Join(tmpDir1, "local_config_1.yaml"), []byte(`#cloud-config +install: + auto: true + reboot: false + poweroff: false + grub_options: + extra_cmdline: "console=tty0" +options: + device: /dev/sda +stages: + initramfs: + - users: + kairos: + groups: + - sudo + passwd: kairos +`), os.ModePerm) + Expect(err).ToNot(HaveOccurred()) + err = os.WriteFile(path.Join(tmpDir1, "local_config_2.yaml"), []byte(`#cloud-config +stages: + initramfs: + - users: + foo: + groups: + - sudo + passwd: bar +`), os.ModePerm) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + err = os.RemoveAll(tmpDir1) + Expect(err).ToNot(HaveOccurred()) + }) + + It("merges all the sources accordingly", func() { + o := &Options{} + err := o.Apply( + MergeBootLine, + WithBootCMDLineFile(cmdLinePath), + Directories(tmpDir1), + ) + Expect(err).ToNot(HaveOccurred()) + + c, err := Scan(o, config.FilterKeys) + Expect(err).ToNot(HaveOccurred()) + + Expect(c.String()).To(Equal(`#cloud-config + +install: + auto: true + grub_options: + extra_cmdline: console=tty0 + poweroff: false + reboot: false +options: + device: /dev/sda +stages: + initramfs: + - users: + foo: + groups: + - sudo + passwd: bar + kairos: + groups: + - sudo + passwd: kairos +`)) + }) + }) + Context("multiple sources are defined", func() { var cmdLinePath, serverDir, tmpDir, tmpDir1, tmpDir2 string var err error