From 95d669b651d8d64ab5c6161f11250d226dfd860d Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sat, 5 Feb 2022 23:54:49 -0500 Subject: [PATCH 01/13] providers/vmware: add constants for guestinfo and OVF property names --- internal/providers/vmware/vmware_amd64.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/internal/providers/vmware/vmware_amd64.go b/internal/providers/vmware/vmware_amd64.go index 6447987de..e307f8ae8 100644 --- a/internal/providers/vmware/vmware_amd64.go +++ b/internal/providers/vmware/vmware_amd64.go @@ -29,6 +29,16 @@ import ( "github.com/vmware/vmw-ovflib" ) +const ( + GUESTINFO_OVF = "ovfenv" + GUESTINFO_USERDATA = "ignition.config.data" + GUESTINFO_USERDATA_ENCODING = "ignition.config.data.encoding" + + OVF_PREFIX = "guestinfo." + OVF_USERDATA = OVF_PREFIX + GUESTINFO_USERDATA + OVF_USERDATA_ENCODING = OVF_PREFIX + GUESTINFO_USERDATA_ENCODING +) + func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) { if isVM, err := vmcheck.IsVirtualWorld(true); err != nil { return types.Config{}, report.Report{}, err @@ -57,7 +67,7 @@ func fetchRawConfig(f *resource.Fetcher) (config, error) { var ovfData string var ovfEncoding string - ovfEnv, err := info.String("ovfenv", "") + ovfEnv, err := info.String(GUESTINFO_OVF, "") if err != nil { f.Logger.Warning("failed to fetch ovfenv: %v. Continuing...", err) } else if ovfEnv != "" { @@ -67,17 +77,17 @@ func fetchRawConfig(f *resource.Fetcher) (config, error) { f.Logger.Warning("failed to parse OVF environment: %v. Continuing...", err) } - ovfData = env.Properties["guestinfo.ignition.config.data"] - ovfEncoding = env.Properties["guestinfo.ignition.config.data.encoding"] + ovfData = env.Properties[OVF_USERDATA] + ovfEncoding = env.Properties[OVF_USERDATA_ENCODING] } - data, err := info.String("ignition.config.data", ovfData) + data, err := info.String(GUESTINFO_USERDATA, ovfData) if err != nil { f.Logger.Debug("failed to fetch config: %v", err) return config{}, err } - encoding, err := info.String("ignition.config.data.encoding", ovfEncoding) + encoding, err := info.String(GUESTINFO_USERDATA_ENCODING, ovfEncoding) if err != nil { f.Logger.Debug("failed to fetch config encoding: %v", err) return config{}, err From d870a6bdaa97676eb90156c3abc65228a522c4f1 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 6 Feb 2022 00:00:58 -0500 Subject: [PATCH 02/13] providers/vmware: add verbatim copy of vmw-ovflib Import vmware-archive/vmw-ovflib@53a0e9f7a4 verbatim; it's no longer maintained upstream. This is identical to the vendored vmw-ovflib code. --- internal/providers/vmware/vmw-ovflib/LICENSE | 202 ++++++++++++++++++ internal/providers/vmware/vmw-ovflib/README | 2 + internal/providers/vmware/vmw-ovflib/ovf.go | 54 +++++ .../providers/vmware/vmw-ovflib/ovf_test.go | 128 +++++++++++ 4 files changed, 386 insertions(+) create mode 100644 internal/providers/vmware/vmw-ovflib/LICENSE create mode 100644 internal/providers/vmware/vmw-ovflib/README create mode 100644 internal/providers/vmware/vmw-ovflib/ovf.go create mode 100644 internal/providers/vmware/vmw-ovflib/ovf_test.go diff --git a/internal/providers/vmware/vmw-ovflib/LICENSE b/internal/providers/vmware/vmw-ovflib/LICENSE new file mode 100644 index 000000000..e06d20818 --- /dev/null +++ b/internal/providers/vmware/vmw-ovflib/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/internal/providers/vmware/vmw-ovflib/README b/internal/providers/vmware/vmw-ovflib/README new file mode 100644 index 000000000..ae5e17a37 --- /dev/null +++ b/internal/providers/vmware/vmw-ovflib/README @@ -0,0 +1,2 @@ +VMware has ended active development of this project, this repository will no longer be updated. +minimal support for parsing OVF environment diff --git a/internal/providers/vmware/vmw-ovflib/ovf.go b/internal/providers/vmware/vmw-ovflib/ovf.go new file mode 100644 index 000000000..d0f9163d6 --- /dev/null +++ b/internal/providers/vmware/vmw-ovflib/ovf.go @@ -0,0 +1,54 @@ +// Copyright 2014-2015 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ovf + +import ( + "encoding/xml" +) + +type environment struct { + Platform platform `xml:"PlatformSection"` + Properties []property `xml:"PropertySection>Property"` +} + +type platform struct { + Kind string `xml:"Kind"` + Version string `xml:"Version"` + Vendor string `xml:"Vendor"` + Locale string `xml:"Locale"` +} + +type property struct { + Key string `xml:"key,attr"` + Value string `xml:"value,attr"` +} + +type OvfEnvironment struct { + Platform platform + Properties map[string]string +} + +func ReadEnvironment(doc []byte) (OvfEnvironment, error) { + var env environment + if err := xml.Unmarshal(doc, &env); err != nil { + return OvfEnvironment{}, err + } + + dict := make(map[string]string) + for _, p := range env.Properties { + dict[p.Key] = p.Value + } + return OvfEnvironment{Properties: dict, Platform: env.Platform}, nil +} diff --git a/internal/providers/vmware/vmw-ovflib/ovf_test.go b/internal/providers/vmware/vmw-ovflib/ovf_test.go new file mode 100644 index 000000000..b0af4867f --- /dev/null +++ b/internal/providers/vmware/vmw-ovflib/ovf_test.go @@ -0,0 +1,128 @@ +// Copyright 2014-2015 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ovf + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +var data_vsphere = []byte(` + + + VMware ESXi + 5.5.0 + VMware, Inc. + en + + + + + + + + +`) + +var data_vapprun = []byte(` + + + vapprun + 1.0 + VMware, Inc. + en_US + + + + + + + + + +`) + +func TestOvfEnvProperties(t *testing.T) { + + var testerFunc = func(env_str []byte) func() { + return func() { + env, err := ReadEnvironment(env_str) + So(err, ShouldBeNil) + props := env.Properties + + var val string + var ok bool + Convey(`Property "foo"`, func() { + val, ok = props["foo"] + So(ok, ShouldBeTrue) + So(val, ShouldEqual, "42") + }) + + Convey(`Property "bar"`, func() { + val, ok = props["bar"] + So(ok, ShouldBeTrue) + So(val, ShouldEqual, "0") + }) + } + } + + Convey("With vAppRun environment", t, testerFunc(data_vapprun)) + Convey("With vSphere environment", t, testerFunc(data_vsphere)) +} + +func TestOvfEnvPlatform(t *testing.T) { + Convey("With vSphere environment", t, func() { + env, err := ReadEnvironment(data_vsphere) + So(err, ShouldBeNil) + platform := env.Platform + + So(platform.Kind, ShouldEqual, "VMware ESXi") + So(platform.Version, ShouldEqual, "5.5.0") + So(platform.Vendor, ShouldEqual, "VMware, Inc.") + So(platform.Locale, ShouldEqual, "en") + }) +} + +func TestVappRunUserDataUrl(t *testing.T) { + Convey("With vAppRun environment", t, func() { + env, err := ReadEnvironment(data_vapprun) + So(err, ShouldBeNil) + props := env.Properties + + var val string + var ok bool + + val, ok = props["guestinfo.user_data.url"] + So(ok, ShouldBeTrue) + So(val, ShouldEqual, "https://gist.githubusercontent.com/sigma/5a64aac1693da9ca70d2/raw/plop.yaml") + }) +} + +func TestInvalidData(t *testing.T) { + Convey("With invalid data", t, func() { + _, err := ReadEnvironment(append(data_vsphere, []byte("garbage")...)) + So(err, ShouldBeNil) + }) +} From 9b03d398d3c56a6eb8a72d67af9a848b8cc7f7a8 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 6 Feb 2022 00:02:26 -0500 Subject: [PATCH 03/13] providers/vmware: drop vmw-ovflib docs The license file is identical to the one in the root of the repo. --- internal/providers/vmware/vmw-ovflib/LICENSE | 202 ------------------- internal/providers/vmware/vmw-ovflib/README | 2 - 2 files changed, 204 deletions(-) delete mode 100644 internal/providers/vmware/vmw-ovflib/LICENSE delete mode 100644 internal/providers/vmware/vmw-ovflib/README diff --git a/internal/providers/vmware/vmw-ovflib/LICENSE b/internal/providers/vmware/vmw-ovflib/LICENSE deleted file mode 100644 index e06d20818..000000000 --- a/internal/providers/vmware/vmw-ovflib/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/internal/providers/vmware/vmw-ovflib/README b/internal/providers/vmware/vmw-ovflib/README deleted file mode 100644 index ae5e17a37..000000000 --- a/internal/providers/vmware/vmw-ovflib/README +++ /dev/null @@ -1,2 +0,0 @@ -VMware has ended active development of this project, this repository will no longer be updated. -minimal support for parsing OVF environment From 2ec630edccfecc765fbe7aeb8ea5c79398a01af2 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 6 Feb 2022 00:20:58 -0500 Subject: [PATCH 04/13] providers/vmware: convert OVF tests to testify --- .../providers/vmware/vmw-ovflib/ovf_test.go | 79 ++++++++----------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/internal/providers/vmware/vmw-ovflib/ovf_test.go b/internal/providers/vmware/vmw-ovflib/ovf_test.go index b0af4867f..14fe6bb2d 100644 --- a/internal/providers/vmware/vmw-ovflib/ovf_test.go +++ b/internal/providers/vmware/vmw-ovflib/ovf_test.go @@ -17,7 +17,7 @@ package ovf import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) var data_vsphere = []byte(` @@ -65,64 +65,51 @@ var data_vapprun = []byte(` `) func TestOvfEnvProperties(t *testing.T) { + var testOne = func(env_str []byte) { + env, err := ReadEnvironment(env_str) + assert.Nil(t, err) + props := env.Properties - var testerFunc = func(env_str []byte) func() { - return func() { - env, err := ReadEnvironment(env_str) - So(err, ShouldBeNil) - props := env.Properties - - var val string - var ok bool - Convey(`Property "foo"`, func() { - val, ok = props["foo"] - So(ok, ShouldBeTrue) - So(val, ShouldEqual, "42") - }) + var val string + var ok bool + val, ok = props["foo"] + assert.True(t, ok) + assert.Equal(t, val, "42") - Convey(`Property "bar"`, func() { - val, ok = props["bar"] - So(ok, ShouldBeTrue) - So(val, ShouldEqual, "0") - }) - } + val, ok = props["bar"] + assert.True(t, ok) + assert.Equal(t, val, "0") } - Convey("With vAppRun environment", t, testerFunc(data_vapprun)) - Convey("With vSphere environment", t, testerFunc(data_vsphere)) + testOne(data_vapprun) + testOne(data_vsphere) } func TestOvfEnvPlatform(t *testing.T) { - Convey("With vSphere environment", t, func() { - env, err := ReadEnvironment(data_vsphere) - So(err, ShouldBeNil) - platform := env.Platform - - So(platform.Kind, ShouldEqual, "VMware ESXi") - So(platform.Version, ShouldEqual, "5.5.0") - So(platform.Vendor, ShouldEqual, "VMware, Inc.") - So(platform.Locale, ShouldEqual, "en") - }) + env, err := ReadEnvironment(data_vsphere) + assert.Nil(t, err) + platform := env.Platform + + assert.Equal(t, platform.Kind, "VMware ESXi") + assert.Equal(t, platform.Version, "5.5.0") + assert.Equal(t, platform.Vendor, "VMware, Inc.") + assert.Equal(t, platform.Locale, "en") } func TestVappRunUserDataUrl(t *testing.T) { - Convey("With vAppRun environment", t, func() { - env, err := ReadEnvironment(data_vapprun) - So(err, ShouldBeNil) - props := env.Properties + env, err := ReadEnvironment(data_vapprun) + assert.Nil(t, err) + props := env.Properties - var val string - var ok bool + var val string + var ok bool - val, ok = props["guestinfo.user_data.url"] - So(ok, ShouldBeTrue) - So(val, ShouldEqual, "https://gist.githubusercontent.com/sigma/5a64aac1693da9ca70d2/raw/plop.yaml") - }) + val, ok = props["guestinfo.user_data.url"] + assert.True(t, ok) + assert.Equal(t, val, "https://gist.githubusercontent.com/sigma/5a64aac1693da9ca70d2/raw/plop.yaml") } func TestInvalidData(t *testing.T) { - Convey("With invalid data", t, func() { - _, err := ReadEnvironment(append(data_vsphere, []byte("garbage")...)) - So(err, ShouldBeNil) - }) + _, err := ReadEnvironment(append(data_vsphere, []byte("garbage")...)) + assert.Nil(t, err) } From 0b651f9190e18be3c70e53e5fe10e3ba41e73139 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 6 Feb 2022 00:24:11 -0500 Subject: [PATCH 05/13] providers/vmware: switch to internal copy of OVF parser --- go.mod | 2 - go.sum | 10 - .../providers/vmware/{vmw-ovflib => }/ovf.go | 6 +- .../vmware/{vmw-ovflib => }/ovf_test.go | 12 +- internal/providers/vmware/vmware_amd64.go | 3 +- vendor/github.com/vmware/vmw-ovflib/LICENSE | 202 ------------------ vendor/github.com/vmware/vmw-ovflib/README | 1 - vendor/github.com/vmware/vmw-ovflib/ovf.go | 54 ----- vendor/modules.txt | 3 - 9 files changed, 12 insertions(+), 281 deletions(-) rename internal/providers/vmware/{vmw-ovflib => }/ovf.go (90%) rename internal/providers/vmware/{vmw-ovflib => }/ovf_test.go (92%) delete mode 100644 vendor/github.com/vmware/vmw-ovflib/LICENSE delete mode 100644 vendor/github.com/vmware/vmw-ovflib/README delete mode 100644 vendor/github.com/vmware/vmw-ovflib/ovf.go diff --git a/go.mod b/go.mod index a9c3c5298..45f4e771c 100644 --- a/go.mod +++ b/go.mod @@ -12,12 +12,10 @@ require ( github.com/google/renameio v0.1.0 github.com/google/uuid v1.1.1 github.com/pin/tftp v2.1.0+incompatible - github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa // indirect github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace github.com/stretchr/testify v1.7.0 github.com/vincent-petithory/dataurl v1.0.0 github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 - github.com/vmware/vmw-ovflib v0.0.0-20170608004843-1f217b9dc714 go.opencensus.io v0.22.5 // indirect golang.org/x/net v0.0.0-20200602114024-627f9648deb9 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d diff --git a/go.sum b/go.sum index d57d45094..04a7a6d72 100644 --- a/go.sum +++ b/go.sum @@ -115,8 +115,6 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -125,8 +123,6 @@ github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeY github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -140,10 +136,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa h1:E+gaaifzi2xF65PbDmuKI3PhLWY6G5opMLniFq8vmXA= -github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA= github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -155,8 +147,6 @@ github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8A github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 h1:v6jG/tdl4O07LNVp74Nt7/OyL+1JsIW1M2f/nSvQheY= github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3/go.mod h1:CSBTxrhePCm0cmXNKDGeu+6bOQzpaEklfCqEpn89JWk= -github.com/vmware/vmw-ovflib v0.0.0-20170608004843-1f217b9dc714 h1:wJqF3m4Tj8I4beSi6vGxIyNtsq6wwGqhK3UnA99ltL4= -github.com/vmware/vmw-ovflib v0.0.0-20170608004843-1f217b9dc714/go.mod h1:jiPk45kn7klhByRvUq5i2vo1RtHKBHj+iWGFpxbXuuI= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= diff --git a/internal/providers/vmware/vmw-ovflib/ovf.go b/internal/providers/vmware/ovf.go similarity index 90% rename from internal/providers/vmware/vmw-ovflib/ovf.go rename to internal/providers/vmware/ovf.go index d0f9163d6..ad163560e 100644 --- a/internal/providers/vmware/vmw-ovflib/ovf.go +++ b/internal/providers/vmware/ovf.go @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ovf +// Originally from https://github.com/vmware-archive/vmw-ovflib + +package vmware import ( "encoding/xml" @@ -40,7 +42,7 @@ type OvfEnvironment struct { Properties map[string]string } -func ReadEnvironment(doc []byte) (OvfEnvironment, error) { +func ReadOvfEnvironment(doc []byte) (OvfEnvironment, error) { var env environment if err := xml.Unmarshal(doc, &env); err != nil { return OvfEnvironment{}, err diff --git a/internal/providers/vmware/vmw-ovflib/ovf_test.go b/internal/providers/vmware/ovf_test.go similarity index 92% rename from internal/providers/vmware/vmw-ovflib/ovf_test.go rename to internal/providers/vmware/ovf_test.go index 14fe6bb2d..e403afaba 100644 --- a/internal/providers/vmware/vmw-ovflib/ovf_test.go +++ b/internal/providers/vmware/ovf_test.go @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ovf +// Originally from https://github.com/vmware-archive/vmw-ovflib + +package vmware import ( "testing" @@ -66,7 +68,7 @@ var data_vapprun = []byte(` func TestOvfEnvProperties(t *testing.T) { var testOne = func(env_str []byte) { - env, err := ReadEnvironment(env_str) + env, err := ReadOvfEnvironment(env_str) assert.Nil(t, err) props := env.Properties @@ -86,7 +88,7 @@ func TestOvfEnvProperties(t *testing.T) { } func TestOvfEnvPlatform(t *testing.T) { - env, err := ReadEnvironment(data_vsphere) + env, err := ReadOvfEnvironment(data_vsphere) assert.Nil(t, err) platform := env.Platform @@ -97,7 +99,7 @@ func TestOvfEnvPlatform(t *testing.T) { } func TestVappRunUserDataUrl(t *testing.T) { - env, err := ReadEnvironment(data_vapprun) + env, err := ReadOvfEnvironment(data_vapprun) assert.Nil(t, err) props := env.Properties @@ -110,6 +112,6 @@ func TestVappRunUserDataUrl(t *testing.T) { } func TestInvalidData(t *testing.T) { - _, err := ReadEnvironment(append(data_vsphere, []byte("garbage")...)) + _, err := ReadOvfEnvironment(append(data_vsphere, []byte("garbage")...)) assert.Nil(t, err) } diff --git a/internal/providers/vmware/vmware_amd64.go b/internal/providers/vmware/vmware_amd64.go index e307f8ae8..f1fac0fa1 100644 --- a/internal/providers/vmware/vmware_amd64.go +++ b/internal/providers/vmware/vmware_amd64.go @@ -26,7 +26,6 @@ import ( "github.com/coreos/vcontext/report" "github.com/vmware/vmw-guestinfo/rpcvmx" "github.com/vmware/vmw-guestinfo/vmcheck" - "github.com/vmware/vmw-ovflib" ) const ( @@ -72,7 +71,7 @@ func fetchRawConfig(f *resource.Fetcher) (config, error) { f.Logger.Warning("failed to fetch ovfenv: %v. Continuing...", err) } else if ovfEnv != "" { f.Logger.Debug("using OVF environment from guestinfo") - env, err := ovf.ReadEnvironment([]byte(ovfEnv)) + env, err := ReadOvfEnvironment([]byte(ovfEnv)) if err != nil { f.Logger.Warning("failed to parse OVF environment: %v. Continuing...", err) } diff --git a/vendor/github.com/vmware/vmw-ovflib/LICENSE b/vendor/github.com/vmware/vmw-ovflib/LICENSE deleted file mode 100644 index e06d20818..000000000 --- a/vendor/github.com/vmware/vmw-ovflib/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/vendor/github.com/vmware/vmw-ovflib/README b/vendor/github.com/vmware/vmw-ovflib/README deleted file mode 100644 index 0ae7be7cb..000000000 --- a/vendor/github.com/vmware/vmw-ovflib/README +++ /dev/null @@ -1 +0,0 @@ -minimal support for parsing OVF environment diff --git a/vendor/github.com/vmware/vmw-ovflib/ovf.go b/vendor/github.com/vmware/vmw-ovflib/ovf.go deleted file mode 100644 index d0f9163d6..000000000 --- a/vendor/github.com/vmware/vmw-ovflib/ovf.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2014-2015 VMware, Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ovf - -import ( - "encoding/xml" -) - -type environment struct { - Platform platform `xml:"PlatformSection"` - Properties []property `xml:"PropertySection>Property"` -} - -type platform struct { - Kind string `xml:"Kind"` - Version string `xml:"Version"` - Vendor string `xml:"Vendor"` - Locale string `xml:"Locale"` -} - -type property struct { - Key string `xml:"key,attr"` - Value string `xml:"value,attr"` -} - -type OvfEnvironment struct { - Platform platform - Properties map[string]string -} - -func ReadEnvironment(doc []byte) (OvfEnvironment, error) { - var env environment - if err := xml.Unmarshal(doc, &env); err != nil { - return OvfEnvironment{}, err - } - - dict := make(map[string]string) - for _, p := range env.Properties { - dict[p.Key] = p.Value - } - return OvfEnvironment{Properties: dict, Platform: env.Platform}, nil -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8b22eaa22..164d3db3c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -128,9 +128,6 @@ github.com/vmware/vmw-guestinfo/message github.com/vmware/vmw-guestinfo/rpcout github.com/vmware/vmw-guestinfo/rpcvmx github.com/vmware/vmw-guestinfo/vmcheck -# github.com/vmware/vmw-ovflib v0.0.0-20170608004843-1f217b9dc714 -## explicit -github.com/vmware/vmw-ovflib # go.opencensus.io v0.22.5 ## explicit go.opencensus.io From addd34c7ae8e511286683d928210c222cc858cb3 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Fri, 18 Mar 2022 00:03:56 -0400 Subject: [PATCH 06/13] go.mod: add github.com/beevik/etree We need to round-trip OVF environment XML, which uses XML namespace prefixes. encoding/xml can't handle those [1] and bad things happen if you try [2]. Use an external package instead. [1]: https://github.com/golang/go/issues/9519 [2]: https://github.com/mattermost/xml-roundtrip-validator/blob/master/advisories/unstable-elements.md --- go.mod | 1 + go.sum | 2 + vendor/github.com/beevik/etree/.travis.yml | 18 + vendor/github.com/beevik/etree/CONTRIBUTORS | 10 + vendor/github.com/beevik/etree/LICENSE | 24 + vendor/github.com/beevik/etree/README.md | 205 +++ .../github.com/beevik/etree/RELEASE_NOTES.md | 109 ++ vendor/github.com/beevik/etree/etree.go | 1511 +++++++++++++++++ vendor/github.com/beevik/etree/go.mod | 3 + vendor/github.com/beevik/etree/helpers.go | 276 +++ vendor/github.com/beevik/etree/path.go | 580 +++++++ vendor/modules.txt | 3 + 12 files changed, 2742 insertions(+) create mode 100644 vendor/github.com/beevik/etree/.travis.yml create mode 100644 vendor/github.com/beevik/etree/CONTRIBUTORS create mode 100644 vendor/github.com/beevik/etree/LICENSE create mode 100644 vendor/github.com/beevik/etree/README.md create mode 100644 vendor/github.com/beevik/etree/RELEASE_NOTES.md create mode 100644 vendor/github.com/beevik/etree/etree.go create mode 100644 vendor/github.com/beevik/etree/go.mod create mode 100644 vendor/github.com/beevik/etree/helpers.go create mode 100644 vendor/github.com/beevik/etree/path.go diff --git a/go.mod b/go.mod index 45f4e771c..06d80e65b 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( cloud.google.com/go v0.58.0 cloud.google.com/go/storage v1.9.0 github.com/aws/aws-sdk-go v1.30.28 + github.com/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c github.com/coreos/go-semver v0.3.0 github.com/coreos/go-systemd/v22 v22.0.0 github.com/coreos/vcontext v0.0.0-20211021162308-f1dbbca7bef4 diff --git a/go.sum b/go.sum index 04a7a6d72..097b98c77 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/aws/aws-sdk-go v1.30.28 h1:SaPM7dlmp7h3Lj1nJ4jdzOkTdom08+g20k7AU5heZYg= github.com/aws/aws-sdk-go v1.30.28/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c h1:uYq6BD31fkfeNKQmfLj7ODcEfkb5JLsKrXVSqgnfGg8= +github.com/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c/go.mod h1:0yGO2rna3S9DkITDWHY1bMtcY4IJ4w+4S+EooZUR0bE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= diff --git a/vendor/github.com/beevik/etree/.travis.yml b/vendor/github.com/beevik/etree/.travis.yml new file mode 100644 index 000000000..89b02e224 --- /dev/null +++ b/vendor/github.com/beevik/etree/.travis.yml @@ -0,0 +1,18 @@ +language: go +sudo: false + +env: + - GO111MODULE=on + +go: + - 1.11.x + - 1.14.x + - tip + +matrix: + allow_failures: + - go: tip + +script: + - go vet ./... + - go test -v ./... diff --git a/vendor/github.com/beevik/etree/CONTRIBUTORS b/vendor/github.com/beevik/etree/CONTRIBUTORS new file mode 100644 index 000000000..03211a85e --- /dev/null +++ b/vendor/github.com/beevik/etree/CONTRIBUTORS @@ -0,0 +1,10 @@ +Brett Vickers (beevik) +Felix Geisendörfer (felixge) +Kamil Kisiel (kisielk) +Graham King (grahamking) +Matt Smith (ma314smith) +Michal Jemala (michaljemala) +Nicolas Piganeau (npiganeau) +Chris Brown (ccbrown) +Earncef Sequeira (earncef) +Gabriel de Labachelerie (wuzuf) diff --git a/vendor/github.com/beevik/etree/LICENSE b/vendor/github.com/beevik/etree/LICENSE new file mode 100644 index 000000000..26f1f7751 --- /dev/null +++ b/vendor/github.com/beevik/etree/LICENSE @@ -0,0 +1,24 @@ +Copyright 2015-2019 Brett Vickers. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/beevik/etree/README.md b/vendor/github.com/beevik/etree/README.md new file mode 100644 index 000000000..08ec26b0a --- /dev/null +++ b/vendor/github.com/beevik/etree/README.md @@ -0,0 +1,205 @@ +[![Build Status](https://travis-ci.org/beevik/etree.svg?branch=master)](https://travis-ci.org/beevik/etree) +[![GoDoc](https://godoc.org/github.com/beevik/etree?status.svg)](https://godoc.org/github.com/beevik/etree) + +etree +===== + +The etree package is a lightweight, pure go package that expresses XML in +the form of an element tree. Its design was inspired by the Python +[ElementTree](http://docs.python.org/2/library/xml.etree.elementtree.html) +module. + +Some of the package's capabilities and features: + +* Represents XML documents as trees of elements for easy traversal. +* Imports, serializes, modifies or creates XML documents from scratch. +* Writes and reads XML to/from files, byte slices, strings and io interfaces. +* Performs simple or complex searches with lightweight XPath-like query APIs. +* Auto-indents XML using spaces or tabs for better readability. +* Implemented in pure go; depends only on standard go libraries. +* Built on top of the go [encoding/xml](http://golang.org/pkg/encoding/xml) + package. + +### Creating an XML document + +The following example creates an XML document from scratch using the etree +package and outputs its indented contents to stdout. +```go +doc := etree.NewDocument() +doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`) +doc.CreateProcInst("xml-stylesheet", `type="text/xsl" href="style.xsl"`) + +people := doc.CreateElement("People") +people.CreateComment("These are all known people") + +jon := people.CreateElement("Person") +jon.CreateAttr("name", "Jon") + +sally := people.CreateElement("Person") +sally.CreateAttr("name", "Sally") + +doc.Indent(2) +doc.WriteTo(os.Stdout) +``` + +Output: +```xml + + + + + + + +``` + +### Reading an XML file + +Suppose you have a file on disk called `bookstore.xml` containing the +following data: + +```xml + + + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + + Harry Potter + J K. Rowling + 2005 + 29.99 + + + + XQuery Kick Start + James McGovern + Per Bothner + Kurt Cagle + James Linn + Vaidyanathan Nagarajan + 2003 + 49.99 + + + + Learning XML + Erik T. Ray + 2003 + 39.95 + + + +``` + +This code reads the file's contents into an etree document. +```go +doc := etree.NewDocument() +if err := doc.ReadFromFile("bookstore.xml"); err != nil { + panic(err) +} +``` + +You can also read XML from a string, a byte slice, or an `io.Reader`. + +### Processing elements and attributes + +This example illustrates several ways to access elements and attributes using +etree selection queries. +```go +root := doc.SelectElement("bookstore") +fmt.Println("ROOT element:", root.Tag) + +for _, book := range root.SelectElements("book") { + fmt.Println("CHILD element:", book.Tag) + if title := book.SelectElement("title"); title != nil { + lang := title.SelectAttrValue("lang", "unknown") + fmt.Printf(" TITLE: %s (%s)\n", title.Text(), lang) + } + for _, attr := range book.Attr { + fmt.Printf(" ATTR: %s=%s\n", attr.Key, attr.Value) + } +} +``` +Output: +``` +ROOT element: bookstore +CHILD element: book + TITLE: Everyday Italian (en) + ATTR: category=COOKING +CHILD element: book + TITLE: Harry Potter (en) + ATTR: category=CHILDREN +CHILD element: book + TITLE: XQuery Kick Start (en) + ATTR: category=WEB +CHILD element: book + TITLE: Learning XML (en) + ATTR: category=WEB +``` + +### Path queries + +This example uses etree's path functions to select all book titles that fall +into the category of 'WEB'. The double-slash prefix in the path causes the +search for book elements to occur recursively; book elements may appear at any +level of the XML hierarchy. +```go +for _, t := range doc.FindElements("//book[@category='WEB']/title") { + fmt.Println("Title:", t.Text()) +} +``` + +Output: +``` +Title: XQuery Kick Start +Title: Learning XML +``` + +This example finds the first book element under the root bookstore element and +outputs the tag and text of each of its child elements. +```go +for _, e := range doc.FindElements("./bookstore/book[1]/*") { + fmt.Printf("%s: %s\n", e.Tag, e.Text()) +} +``` + +Output: +``` +title: Everyday Italian +author: Giada De Laurentiis +year: 2005 +price: 30.00 +``` + +This example finds all books with a price of 49.99 and outputs their titles. +```go +path := etree.MustCompilePath("./bookstore/book[p:price='49.99']/title") +for _, e := range doc.FindElementsPath(path) { + fmt.Println(e.Text()) +} +``` + +Output: +``` +XQuery Kick Start +``` + +Note that this example uses the FindElementsPath function, which takes as an +argument a pre-compiled path object. Use precompiled paths when you plan to +search with the same path more than once. + +### Other features + +These are just a few examples of the things the etree package can do. See the +[documentation](http://godoc.org/github.com/beevik/etree) for a complete +description of its capabilities. + +### Contributing + +This project accepts contributions. Just fork the repo and submit a pull +request! diff --git a/vendor/github.com/beevik/etree/RELEASE_NOTES.md b/vendor/github.com/beevik/etree/RELEASE_NOTES.md new file mode 100644 index 000000000..ee59d7abf --- /dev/null +++ b/vendor/github.com/beevik/etree/RELEASE_NOTES.md @@ -0,0 +1,109 @@ +Release v1.1.0 +============== + +**New Features** + +* New attribute helpers. + * Added the `Element.SortAttrs` method, which lexicographically sorts an + element's attributes by key. +* New `ReadSettings` properties. + * Added `Entity` for the support of custom entity maps. +* New `WriteSettings` properties. + * Added `UseCRLF` to allow the output of CR-LF newlines instead of the + default LF newlines. This is useful on Windows systems. +* Additional support for text and CDATA sections. + * The `Element.Text` method now returns the concatenation of all consecutive + character data tokens immediately following an element's opening tag. + * Added `Element.SetCData` to replace the character data immediately + following an element's opening tag with a CDATA section. + * Added `Element.CreateCData` to create and add a CDATA section child + `CharData` token to an element. + * Added `Element.CreateText` to create and add a child text `CharData` token + to an element. + * Added `NewCData` to create a parentless CDATA section `CharData` token. + * Added `NewText` to create a parentless text `CharData` + token. + * Added `CharData.IsCData` to detect if the token contains a CDATA section. + * Added `CharData.IsWhitespace` to detect if the token contains whitespace + inserted by one of the document Indent functions. + * Modified `Element.SetText` so that it replaces a run of consecutive + character data tokens following the element's opening tag (instead of just + the first one). +* New "tail text" support. + * Added the `Element.Tail` method, which returns the text immediately + following an element's closing tag. + * Added the `Element.SetTail` method, which modifies the text immediately + following an element's closing tag. +* New element child insertion and removal methods. + * Added the `Element.InsertChildAt` method, which inserts a new child token + before the specified child token index. + * Added the `Element.RemoveChildAt` method, which removes the child token at + the specified child token index. +* New element and attribute queries. + * Added the `Element.Index` method, which returns the element's index within + its parent element's child token list. + * Added the `Element.NamespaceURI` method to return the namespace URI + associated with an element. + * Added the `Attr.NamespaceURI` method to return the namespace URI + associated with an element. + * Added the `Attr.Element` method to return the element that an attribute + belongs to. +* New Path filter functions. + * Added `[local-name()='val']` to keep elements whose unprefixed tag matches + the desired value. + * Added `[name()='val']` to keep elements whose full tag matches the desired + value. + * Added `[namespace-prefix()='val']` to keep elements whose namespace prefix + matches the desired value. + * Added `[namespace-uri()='val']` to keep elements whose namespace URI + matches the desired value. + +**Bug Fixes** + +* A default XML `CharSetReader` is now used to prevent failed parsing of XML + documents using certain encodings. + ([Issue](https://github.com/beevik/etree/issues/53)). +* All characters are now properly escaped according to XML parsing rules. + ([Issue](https://github.com/beevik/etree/issues/55)). +* The `Document.Indent` and `Document.IndentTabs` functions no longer insert + empty string `CharData` tokens. + +**Deprecated** + +* `Element` + * The `InsertChild` method is deprecated. Use `InsertChildAt` instead. + * The `CreateCharData` method is deprecated. Use `CreateText` instead. +* `CharData` + * The `NewCharData` method is deprecated. Use `NewText` instead. + + +Release v1.0.1 +============== + +**Changes** + +* Added support for absolute etree Path queries. An absolute path begins with + `/` or `//` and begins its search from the element's document root. +* Added [`GetPath`](https://godoc.org/github.com/beevik/etree#Element.GetPath) + and [`GetRelativePath`](https://godoc.org/github.com/beevik/etree#Element.GetRelativePath) + functions to the [`Element`](https://godoc.org/github.com/beevik/etree#Element) + type. + +**Breaking changes** + +* A path starting with `//` is now interpreted as an absolute path. + Previously, it was interpreted as a relative path starting from the element + whose + [`FindElement`](https://godoc.org/github.com/beevik/etree#Element.FindElement) + method was called. To remain compatible with this release, all paths + prefixed with `//` should be prefixed with `.//` when called from any + element other than the document's root. +* [**edit 2/1/2019**]: Minor releases should not contain breaking changes. + Even though this breaking change was very minor, it was a mistake to include + it in this minor release. In the future, all breaking changes will be + limited to major releases (e.g., version 2.0.0). + +Release v1.0.0 +============== + +Initial release. diff --git a/vendor/github.com/beevik/etree/etree.go b/vendor/github.com/beevik/etree/etree.go new file mode 100644 index 000000000..319076b04 --- /dev/null +++ b/vendor/github.com/beevik/etree/etree.go @@ -0,0 +1,1511 @@ +// Copyright 2015-2019 Brett Vickers. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package etree provides XML services through an Element Tree +// abstraction. +package etree + +import ( + "bufio" + "bytes" + "encoding/xml" + "errors" + "io" + "os" + "sort" + "strings" +) + +const ( + // NoIndent is used with the Document Indent function to disable all + // indenting. + NoIndent = -1 +) + +// ErrXML is returned when XML parsing fails due to incorrect formatting. +var ErrXML = errors.New("etree: invalid XML format") + +// ReadSettings determine the default behavior of the Document's ReadFrom* +// methods. +type ReadSettings struct { + // CharsetReader to be passed to standard xml.Decoder. Default: nil. + CharsetReader func(charset string, input io.Reader) (io.Reader, error) + + // Permissive allows input containing common mistakes such as missing tags + // or attribute values. Default: false. + Permissive bool + + // Entity to be passed to standard xml.Decoder. Default: nil. + Entity map[string]string +} + +// newReadSettings creates a default ReadSettings record. +func newReadSettings() ReadSettings { + return ReadSettings{ + CharsetReader: func(label string, input io.Reader) (io.Reader, error) { + return input, nil + }, + Permissive: false, + } +} + +// dup creates a duplicate of the ReadSettings object. +func (s *ReadSettings) dup() ReadSettings { + var entityCopy map[string]string + if s.Entity != nil { + entityCopy = make(map[string]string) + for k, v := range s.Entity { + entityCopy[k] = v + } + } + return ReadSettings{ + CharsetReader: s.CharsetReader, + Permissive: s.Permissive, + Entity: entityCopy, + } +} + +// WriteSettings determine the behavior of the Document's WriteTo* and +// Indent* methods. +type WriteSettings struct { + // CanonicalEndTags forces the production of XML end tags, even for + // elements that have no child elements. Default: false. + CanonicalEndTags bool + + // CanonicalText forces the production of XML character references for + // text data characters &, <, and >. If false, XML character references + // are also produced for " and '. Default: false. + CanonicalText bool + + // CanonicalAttrVal forces the production of XML character references for + // attribute value characters &, < and ". If false, XML character + // references are also produced for > and '. Default: false. + CanonicalAttrVal bool + + // UseCRLF causes the document's indentation methods to use a carriage + // return followed by a linefeed ("\r\n") when outputting a newline. If + // false, only a linefeed is used ("\n"). Default: false. + UseCRLF bool +} + +// newWriteSettings creates a default WriteSettings record. +func newWriteSettings() WriteSettings { + return WriteSettings{ + CanonicalEndTags: false, + CanonicalText: false, + CanonicalAttrVal: false, + UseCRLF: false, + } +} + +// dup creates a dulicate of the WriteSettings object. +func (s *WriteSettings) dup() WriteSettings { + return *s +} + +// A Token is an interface type used to represent XML elements, character +// data, CDATA sections, XML comments, XML directives, and XML processing +// instructions. +type Token interface { + Parent() *Element + Index() int + dup(parent *Element) Token + setParent(parent *Element) + setIndex(index int) + writeTo(w *bufio.Writer, s *WriteSettings) +} + +// A Document is a container holding a complete XML tree. +// +// A document has a single embedded element, which contains zero or more child +// tokens, one of which is usually the root element. The embedded element may +// include other children such as processing instruction tokens or character +// data tokens. The document's embedded element is never directly serialized; +// only its children are. +// +// A document also contains read and write settings, which influence the way +// the document is deserialized, serialized, and indented. +type Document struct { + Element + ReadSettings ReadSettings + WriteSettings WriteSettings +} + +// An Element represents an XML element, its attributes, and its child tokens. +type Element struct { + Space, Tag string // namespace prefix and tag + Attr []Attr // key-value attribute pairs + Child []Token // child tokens (elements, comments, etc.) + parent *Element // parent element + index int // token index in parent's children +} + +// An Attr represents a key-value attribute within an XML element. +type Attr struct { + Space, Key string // The attribute's namespace prefix and key + Value string // The attribute value string + element *Element // element containing the attribute +} + +// charDataFlags are used with CharData tokens to store additional settings. +type charDataFlags uint8 + +const ( + // The CharData contains only whitespace. + whitespaceFlag charDataFlags = 1 << iota + + // The CharData contains a CDATA section. + cdataFlag +) + +// CharData may be used to represent simple text data or a CDATA section +// within an XML document. The Data property should never be modified +// directly; use the SetData method instead. +type CharData struct { + Data string // the simple text or CDATA section content + parent *Element + index int + flags charDataFlags +} + +// A Comment represents an XML comment. +type Comment struct { + Data string // the comment's text + parent *Element + index int +} + +// A Directive represents an XML directive. +type Directive struct { + Data string // the directive string + parent *Element + index int +} + +// A ProcInst represents an XML processing instruction. +type ProcInst struct { + Target string // the processing instruction target + Inst string // the processing instruction value + parent *Element + index int +} + +// NewDocument creates an XML document without a root element. +func NewDocument() *Document { + return &Document{ + Element: Element{Child: make([]Token, 0)}, + ReadSettings: newReadSettings(), + WriteSettings: newWriteSettings(), + } +} + +// NewDocumentWithRoot creates an XML document and sets the element 'e' as its +// root element. If the element 'e' is already part of another document, it is +// first removed from its existing document. +func NewDocumentWithRoot(e *Element) *Document { + d := NewDocument() + d.SetRoot(e) + return d +} + +// Copy returns a recursive, deep copy of the document. +func (d *Document) Copy() *Document { + return &Document{ + Element: *(d.Element.dup(nil).(*Element)), + ReadSettings: d.ReadSettings.dup(), + WriteSettings: d.WriteSettings.dup(), + } +} + +// Root returns the root element of the document. It returns nil if there is +// no root element. +func (d *Document) Root() *Element { + for _, t := range d.Child { + if c, ok := t.(*Element); ok { + return c + } + } + return nil +} + +// SetRoot replaces the document's root element with the element 'e'. If the +// document already has a root element when this function is called, then the +// existing root element is unbound from the document. If the element 'e' is +// part of another document, then it is unbound from the other document. +func (d *Document) SetRoot(e *Element) { + if e.parent != nil { + e.parent.RemoveChild(e) + } + + // If there is already a root element, replace it. + p := &d.Element + for i, t := range p.Child { + if _, ok := t.(*Element); ok { + t.setParent(nil) + t.setIndex(-1) + p.Child[i] = e + e.setParent(p) + e.setIndex(i) + return + } + } + + // No existing root element, so add it. + p.addChild(e) +} + +// ReadFrom reads XML from the reader 'r' into this document. The function +// returns the number of bytes read and any error encountered. +func (d *Document) ReadFrom(r io.Reader) (n int64, err error) { + return d.Element.readFrom(r, d.ReadSettings) +} + +// ReadFromFile reads XML from a local file at path 'filepath' into this +// document. +func (d *Document) ReadFromFile(filepath string) error { + f, err := os.Open(filepath) + if err != nil { + return err + } + defer f.Close() + _, err = d.ReadFrom(f) + return err +} + +// ReadFromBytes reads XML from the byte slice 'b' into the this document. +func (d *Document) ReadFromBytes(b []byte) error { + _, err := d.ReadFrom(bytes.NewReader(b)) + return err +} + +// ReadFromString reads XML from the string 's' into this document. +func (d *Document) ReadFromString(s string) error { + _, err := d.ReadFrom(strings.NewReader(s)) + return err +} + +// WriteTo serializes the document out to the writer 'w'. The function returns +// the number of bytes written and any error encountered. +func (d *Document) WriteTo(w io.Writer) (n int64, err error) { + cw := newCountWriter(w) + b := bufio.NewWriter(cw) + for _, c := range d.Child { + c.writeTo(b, &d.WriteSettings) + } + err, n = b.Flush(), cw.bytes + return +} + +// WriteToFile serializes the document out to the file at path 'filepath'. +func (d *Document) WriteToFile(filepath string) error { + f, err := os.Create(filepath) + if err != nil { + return err + } + defer f.Close() + _, err = d.WriteTo(f) + return err +} + +// WriteToBytes serializes this document into a slice of bytes. +func (d *Document) WriteToBytes() (b []byte, err error) { + var buf bytes.Buffer + if _, err = d.WriteTo(&buf); err != nil { + return + } + return buf.Bytes(), nil +} + +// WriteToString serializes this document into a string. +func (d *Document) WriteToString() (s string, err error) { + var b []byte + if b, err = d.WriteToBytes(); err != nil { + return + } + return string(b), nil +} + +type indentFunc func(depth int) string + +// Indent modifies the document's element tree by inserting character data +// tokens containing newlines and indentation. The amount of indentation per +// depth level is given by the 'spaces' parameter. Pass etree.NoIndent for +// 'spaces' if you want no indentation at all. +func (d *Document) Indent(spaces int) { + var indent indentFunc + switch { + case spaces < 0: + indent = func(depth int) string { return "" } + case d.WriteSettings.UseCRLF: + indent = func(depth int) string { return indentCRLF(depth*spaces, indentSpaces) } + default: + indent = func(depth int) string { return indentLF(depth*spaces, indentSpaces) } + } + d.Element.indent(0, indent) +} + +// IndentTabs modifies the document's element tree by inserting CharData +// tokens containing newlines and tabs for indentation. One tab is used per +// indentation level. +func (d *Document) IndentTabs() { + var indent indentFunc + switch d.WriteSettings.UseCRLF { + case true: + indent = func(depth int) string { return indentCRLF(depth, indentTabs) } + default: + indent = func(depth int) string { return indentLF(depth, indentTabs) } + } + d.Element.indent(0, indent) +} + +// NewElement creates an unparented element with the specified tag (i.e., +// name). The tag may include a namespace prefix followed by a colon. +func NewElement(tag string) *Element { + space, stag := spaceDecompose(tag) + return newElement(space, stag, nil) +} + +// newElement is a helper function that creates an element and binds it to +// a parent element if possible. +func newElement(space, tag string, parent *Element) *Element { + e := &Element{ + Space: space, + Tag: tag, + Attr: make([]Attr, 0), + Child: make([]Token, 0), + parent: parent, + index: -1, + } + if parent != nil { + parent.addChild(e) + } + return e +} + +// Copy creates a recursive, deep copy of the element and all its attributes +// and children. The returned element has no parent but can be parented to a +// another element using AddChild, or added to a document with SetRoot or +// NewDocumentWithRoot. +func (e *Element) Copy() *Element { + return e.dup(nil).(*Element) +} + +// FullTag returns the element e's complete tag, including namespace prefix if +// present. +func (e *Element) FullTag() string { + if e.Space == "" { + return e.Tag + } + return e.Space + ":" + e.Tag +} + +// NamespaceURI returns the XML namespace URI associated with the element. If +// the element is part of the XML default namespace, NamespaceURI returns the +// empty string. +func (e *Element) NamespaceURI() string { + if e.Space == "" { + return e.findDefaultNamespaceURI() + } + return e.findLocalNamespaceURI(e.Space) +} + +// findLocalNamespaceURI finds the namespace URI corresponding to the +// requested prefix. +func (e *Element) findLocalNamespaceURI(prefix string) string { + for _, a := range e.Attr { + if a.Space == "xmlns" && a.Key == prefix { + return a.Value + } + } + + if e.parent == nil { + return "" + } + + return e.parent.findLocalNamespaceURI(prefix) +} + +// findDefaultNamespaceURI finds the default namespace URI of the element. +func (e *Element) findDefaultNamespaceURI() string { + for _, a := range e.Attr { + if a.Space == "" && a.Key == "xmlns" { + return a.Value + } + } + + if e.parent == nil { + return "" + } + + return e.parent.findDefaultNamespaceURI() +} + +// namespacePrefix returns the namespace prefix associated with the element. +func (e *Element) namespacePrefix() string { + return e.Space +} + +// name returns the tag associated with the element. +func (e *Element) name() string { + return e.Tag +} + +// Text returns all character data immediately following the element's opening +// tag. +func (e *Element) Text() string { + if len(e.Child) == 0 { + return "" + } + + text := "" + for _, ch := range e.Child { + if cd, ok := ch.(*CharData); ok { + if text == "" { + text = cd.Data + } else { + text += cd.Data + } + } else { + break + } + } + return text +} + +// SetText replaces all character data immediately following an element's +// opening tag with the requested string. +func (e *Element) SetText(text string) { + e.replaceText(0, text, 0) +} + +// SetCData replaces all character data immediately following an element's +// opening tag with a CDATA section. +func (e *Element) SetCData(text string) { + e.replaceText(0, text, cdataFlag) +} + +// Tail returns all character data immediately following the element's end +// tag. +func (e *Element) Tail() string { + if e.Parent() == nil { + return "" + } + + p := e.Parent() + i := e.Index() + + text := "" + for _, ch := range p.Child[i+1:] { + if cd, ok := ch.(*CharData); ok { + if text == "" { + text = cd.Data + } else { + text += cd.Data + } + } else { + break + } + } + return text +} + +// SetTail replaces all character data immediately following the element's end +// tag with the requested string. +func (e *Element) SetTail(text string) { + if e.Parent() == nil { + return + } + + p := e.Parent() + p.replaceText(e.Index()+1, text, 0) +} + +// replaceText is a helper function that replaces a series of chardata tokens +// starting at index i with the requested text. +func (e *Element) replaceText(i int, text string, flags charDataFlags) { + end := e.findTermCharDataIndex(i) + + switch { + case end == i: + if text != "" { + // insert a new chardata token at index i + cd := newCharData(text, flags, nil) + e.InsertChildAt(i, cd) + } + + case end == i+1: + if text == "" { + // remove the chardata token at index i + e.RemoveChildAt(i) + } else { + // replace the first and only character token at index i + cd := e.Child[i].(*CharData) + cd.Data, cd.flags = text, flags + } + + default: + if text == "" { + // remove all chardata tokens starting from index i + copy(e.Child[i:], e.Child[end:]) + removed := end - i + e.Child = e.Child[:len(e.Child)-removed] + for j := i; j < len(e.Child); j++ { + e.Child[j].setIndex(j) + } + } else { + // replace the first chardata token at index i and remove all + // subsequent chardata tokens + cd := e.Child[i].(*CharData) + cd.Data, cd.flags = text, flags + copy(e.Child[i+1:], e.Child[end:]) + removed := end - (i + 1) + e.Child = e.Child[:len(e.Child)-removed] + for j := i + 1; j < len(e.Child); j++ { + e.Child[j].setIndex(j) + } + } + } +} + +// findTermCharDataIndex finds the index of the first child token that isn't +// a CharData token. It starts from the requested start index. +func (e *Element) findTermCharDataIndex(start int) int { + for i := start; i < len(e.Child); i++ { + if _, ok := e.Child[i].(*CharData); !ok { + return i + } + } + return len(e.Child) +} + +// CreateElement creates a new element with the specified tag (i.e., name) and +// adds it as the last child token of this element. The tag may include a +// prefix followed by a colon. +func (e *Element) CreateElement(tag string) *Element { + space, stag := spaceDecompose(tag) + return newElement(space, stag, e) +} + +// AddChild adds the token 't' as the last child of the element. If token 't' +// was already the child of another element, it is first removed from its +// parent element. +func (e *Element) AddChild(t Token) { + if t.Parent() != nil { + t.Parent().RemoveChild(t) + } + e.addChild(t) +} + +// InsertChild inserts the token 't' into this element's list of children just +// before the element's existing child token 'ex'. If the existing element +// 'ex' does not appear in this element's list of child tokens, then 't' is +// added to the end of this element's list of child tokens. If token 't' is +// already the child of another element, it is first removed from the other +// element's list of child tokens. +// +// Deprecated: InsertChild is deprecated. Use InsertChildAt instead. +func (e *Element) InsertChild(ex Token, t Token) { + if ex == nil || ex.Parent() != e { + e.AddChild(t) + return + } + + if t.Parent() != nil { + t.Parent().RemoveChild(t) + } + + t.setParent(e) + + i := ex.Index() + e.Child = append(e.Child, nil) + copy(e.Child[i+1:], e.Child[i:]) + e.Child[i] = t + + for j := i; j < len(e.Child); j++ { + e.Child[j].setIndex(j) + } +} + +// InsertChildAt inserts the token 't' into this element's list of child +// tokens just before the requested 'index'. If the index is greater than or +// equal to the length of the list of child tokens, then the token 't' is +// added to the end of the list of child tokens. +func (e *Element) InsertChildAt(index int, t Token) { + if index >= len(e.Child) { + e.AddChild(t) + return + } + + if t.Parent() != nil { + if t.Parent() == e && t.Index() > index { + index-- + } + t.Parent().RemoveChild(t) + } + + t.setParent(e) + + e.Child = append(e.Child, nil) + copy(e.Child[index+1:], e.Child[index:]) + e.Child[index] = t + + for j := index; j < len(e.Child); j++ { + e.Child[j].setIndex(j) + } +} + +// RemoveChild attempts to remove the token 't' from this element's list of +// child tokens. If the token 't' was a child of this element, then it is +// removed and returned. Otherwise, nil is returned. +func (e *Element) RemoveChild(t Token) Token { + if t.Parent() != e { + return nil + } + return e.RemoveChildAt(t.Index()) +} + +// RemoveChildAt removes the child token appearing in slot 'index' of this +// element's list of child tokens. The removed child token is then returned. +// If the index is out of bounds, no child is removed and nil is returned. +func (e *Element) RemoveChildAt(index int) Token { + if index >= len(e.Child) { + return nil + } + + t := e.Child[index] + for j := index + 1; j < len(e.Child); j++ { + e.Child[j].setIndex(j - 1) + } + e.Child = append(e.Child[:index], e.Child[index+1:]...) + t.setIndex(-1) + t.setParent(nil) + return t +} + +// ReadFrom reads XML from the reader 'ri' and stores the result as a new +// child of this element. +func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err error) { + r := newCountReader(ri) + dec := xml.NewDecoder(r) + dec.CharsetReader = settings.CharsetReader + dec.Strict = !settings.Permissive + dec.Entity = settings.Entity + var stack stack + stack.push(e) + for { + t, err := dec.RawToken() + switch { + case err == io.EOF: + if len(stack.data) != 1 { + return r.bytes, ErrXML + } + return r.bytes, nil + case err != nil: + return r.bytes, err + case stack.empty(): + return r.bytes, ErrXML + } + + top := stack.peek().(*Element) + + switch t := t.(type) { + case xml.StartElement: + e := newElement(t.Name.Space, t.Name.Local, top) + for _, a := range t.Attr { + e.createAttr(a.Name.Space, a.Name.Local, a.Value, e) + } + stack.push(e) + case xml.EndElement: + if top.Tag != t.Name.Local || top.Space != t.Name.Space { + return r.bytes, ErrXML + } + stack.pop() + case xml.CharData: + data := string(t) + var flags charDataFlags + if isWhitespace(data) { + flags = whitespaceFlag + } + newCharData(data, flags, top) + case xml.Comment: + newComment(string(t), top) + case xml.Directive: + newDirective(string(t), top) + case xml.ProcInst: + newProcInst(t.Target, string(t.Inst), top) + } + } +} + +// SelectAttr finds an element attribute matching the requested 'key' and, if +// found, returns a pointer to the matching attribute. The function returns +// nil if no matching attribute is found. The key may include a namespace +// prefix followed by a colon. +func (e *Element) SelectAttr(key string) *Attr { + space, skey := spaceDecompose(key) + for i, a := range e.Attr { + if spaceMatch(space, a.Space) && skey == a.Key { + return &e.Attr[i] + } + } + return nil +} + +// SelectAttrValue finds an element attribute matching the requested 'key' and +// returns its value if found. If no matching attribute is found, the function +// returns the 'dflt' value instead. The key may include a namespace prefix +// followed by a colon. +func (e *Element) SelectAttrValue(key, dflt string) string { + space, skey := spaceDecompose(key) + for _, a := range e.Attr { + if spaceMatch(space, a.Space) && skey == a.Key { + return a.Value + } + } + return dflt +} + +// ChildElements returns all elements that are children of this element. +func (e *Element) ChildElements() []*Element { + var elements []*Element + for _, t := range e.Child { + if c, ok := t.(*Element); ok { + elements = append(elements, c) + } + } + return elements +} + +// SelectElement returns the first child element with the given 'tag' (i.e., +// name). The function returns nil if no child element matching the tag is +// found. The tag may include a namespace prefix followed by a colon. +func (e *Element) SelectElement(tag string) *Element { + space, stag := spaceDecompose(tag) + for _, t := range e.Child { + if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag { + return c + } + } + return nil +} + +// SelectElements returns a slice of all child elements with the given 'tag' +// (i.e., name). The tag may include a namespace prefix followed by a colon. +func (e *Element) SelectElements(tag string) []*Element { + space, stag := spaceDecompose(tag) + var elements []*Element + for _, t := range e.Child { + if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag { + elements = append(elements, c) + } + } + return elements +} + +// FindElement returns the first element matched by the XPath-like 'path' +// string. The function returns nil if no child element is found using the +// path. It panics if an invalid path string is supplied. +func (e *Element) FindElement(path string) *Element { + return e.FindElementPath(MustCompilePath(path)) +} + +// FindElementPath returns the first element matched by the 'path' object. The +// function returns nil if no element is found using the path. +func (e *Element) FindElementPath(path Path) *Element { + p := newPather() + elements := p.traverse(e, path) + if len(elements) > 0 { + return elements[0] + } + return nil +} + +// FindElements returns a slice of elements matched by the XPath-like 'path' +// string. The function returns nil if no child element is found using the +// path. It panics if an invalid path string is supplied. +func (e *Element) FindElements(path string) []*Element { + return e.FindElementsPath(MustCompilePath(path)) +} + +// FindElementsPath returns a slice of elements matched by the 'path' object. +func (e *Element) FindElementsPath(path Path) []*Element { + p := newPather() + return p.traverse(e, path) +} + +// GetPath returns the absolute path of the element. The absolute path is the +// full path from the document's root. +func (e *Element) GetPath() string { + path := []string{} + for seg := e; seg != nil; seg = seg.Parent() { + if seg.Tag != "" { + path = append(path, seg.Tag) + } + } + + // Reverse the path. + for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 { + path[i], path[j] = path[j], path[i] + } + + return "/" + strings.Join(path, "/") +} + +// GetRelativePath returns the path of this element relative to the 'source' +// element. If the two elements are not part of the same element tree, then +// the function returns the empty string. +func (e *Element) GetRelativePath(source *Element) string { + var path []*Element + + if source == nil { + return "" + } + + // Build a reverse path from the element toward the root. Stop if the + // source element is encountered. + var seg *Element + for seg = e; seg != nil && seg != source; seg = seg.Parent() { + path = append(path, seg) + } + + // If we found the source element, reverse the path and compose the + // string. + if seg == source { + if len(path) == 0 { + return "." + } + parts := []string{} + for i := len(path) - 1; i >= 0; i-- { + parts = append(parts, path[i].Tag) + } + return "./" + strings.Join(parts, "/") + } + + // The source wasn't encountered, so climb from the source element toward + // the root of the tree until an element in the reversed path is + // encountered. + + findPathIndex := func(e *Element, path []*Element) int { + for i, ee := range path { + if e == ee { + return i + } + } + return -1 + } + + climb := 0 + for seg = source; seg != nil; seg = seg.Parent() { + i := findPathIndex(seg, path) + if i >= 0 { + path = path[:i] // truncate at found segment + break + } + climb++ + } + + // No element in the reversed path was encountered, so the two elements + // must not be part of the same tree. + if seg == nil { + return "" + } + + // Reverse the (possibly truncated) path and prepend ".." segments to + // climb. + parts := []string{} + for i := 0; i < climb; i++ { + parts = append(parts, "..") + } + for i := len(path) - 1; i >= 0; i-- { + parts = append(parts, path[i].Tag) + } + return strings.Join(parts, "/") +} + +// indent recursively inserts proper indentation between an XML element's +// child tokens. +func (e *Element) indent(depth int, indent indentFunc) { + e.stripIndent() + n := len(e.Child) + if n == 0 { + return + } + + oldChild := e.Child + e.Child = make([]Token, 0, n*2+1) + isCharData, firstNonCharData := false, true + for _, c := range oldChild { + // Insert NL+indent before child if it's not character data. + // Exceptions: when it's the first non-character-data child, or when + // the child is at root depth. + _, isCharData = c.(*CharData) + if !isCharData { + if !firstNonCharData || depth > 0 { + s := indent(depth) + if s != "" { + newCharData(s, whitespaceFlag, e) + } + } + firstNonCharData = false + } + + e.addChild(c) + + // Recursively process child elements. + if ce, ok := c.(*Element); ok { + ce.indent(depth+1, indent) + } + } + + // Insert NL+indent before the last child. + if !isCharData { + if !firstNonCharData || depth > 0 { + s := indent(depth - 1) + if s != "" { + newCharData(s, whitespaceFlag, e) + } + } + } +} + +// stripIndent removes any previously inserted indentation. +func (e *Element) stripIndent() { + // Count the number of non-indent child tokens + n := len(e.Child) + for _, c := range e.Child { + if cd, ok := c.(*CharData); ok && cd.IsWhitespace() { + n-- + } + } + if n == len(e.Child) { + return + } + + // Strip out indent CharData + newChild := make([]Token, n) + j := 0 + for _, c := range e.Child { + if cd, ok := c.(*CharData); ok && cd.IsWhitespace() { + continue + } + newChild[j] = c + newChild[j].setIndex(j) + j++ + } + e.Child = newChild +} + +// dup duplicates the element. +func (e *Element) dup(parent *Element) Token { + ne := &Element{ + Space: e.Space, + Tag: e.Tag, + Attr: make([]Attr, len(e.Attr)), + Child: make([]Token, len(e.Child)), + parent: parent, + index: e.index, + } + for i, t := range e.Child { + ne.Child[i] = t.dup(ne) + } + copy(ne.Attr, e.Attr) + return ne +} + +// Parent returns this element's parent element. It returns nil if this +// element has no parent. +func (e *Element) Parent() *Element { + return e.parent +} + +// Index returns the index of this element within its parent element's +// list of child tokens. If this element has no parent, then the function +// returns -1. +func (e *Element) Index() int { + return e.index +} + +// setParent replaces this element token's parent. +func (e *Element) setParent(parent *Element) { + e.parent = parent +} + +// setIndex sets this element token's index within its parent's Child slice. +func (e *Element) setIndex(index int) { + e.index = index +} + +// writeTo serializes the element to the writer w. +func (e *Element) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteByte('<') + w.WriteString(e.FullTag()) + for _, a := range e.Attr { + w.WriteByte(' ') + a.writeTo(w, s) + } + if len(e.Child) > 0 { + w.WriteByte('>') + for _, c := range e.Child { + c.writeTo(w, s) + } + w.Write([]byte{'<', '/'}) + w.WriteString(e.FullTag()) + w.WriteByte('>') + } else { + if s.CanonicalEndTags { + w.Write([]byte{'>', '<', '/'}) + w.WriteString(e.FullTag()) + w.WriteByte('>') + } else { + w.Write([]byte{'/', '>'}) + } + } +} + +// addChild adds a child token to the element e. +func (e *Element) addChild(t Token) { + t.setParent(e) + t.setIndex(len(e.Child)) + e.Child = append(e.Child, t) +} + +// CreateAttr creates an attribute with the specified 'key' and 'value' and +// adds it to this element. If an attribute with same key already exists on +// this element, then its value is replaced. The key may include a namespace +// prefix followed by a colon. +func (e *Element) CreateAttr(key, value string) *Attr { + space, skey := spaceDecompose(key) + return e.createAttr(space, skey, value, e) +} + +// createAttr is a helper function that creates attributes. +func (e *Element) createAttr(space, key, value string, parent *Element) *Attr { + for i, a := range e.Attr { + if space == a.Space && key == a.Key { + e.Attr[i].Value = value + return &e.Attr[i] + } + } + a := Attr{ + Space: space, + Key: key, + Value: value, + element: parent, + } + e.Attr = append(e.Attr, a) + return &e.Attr[len(e.Attr)-1] +} + +// RemoveAttr removes the first attribute of this element whose key matches +// 'key'. It returns a copy of the removed attribute if a match is found. If +// no match is found, it returns nil. The key may include a namespace prefix +// followed by a colon. +func (e *Element) RemoveAttr(key string) *Attr { + space, skey := spaceDecompose(key) + for i, a := range e.Attr { + if space == a.Space && skey == a.Key { + e.Attr = append(e.Attr[0:i], e.Attr[i+1:]...) + return &Attr{ + Space: a.Space, + Key: a.Key, + Value: a.Value, + element: nil, + } + } + } + return nil +} + +// SortAttrs sorts this element's attributes lexicographically by key. +func (e *Element) SortAttrs() { + sort.Sort(byAttr(e.Attr)) +} + +type byAttr []Attr + +func (a byAttr) Len() int { + return len(a) +} + +func (a byAttr) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a byAttr) Less(i, j int) bool { + sp := strings.Compare(a[i].Space, a[j].Space) + if sp == 0 { + return strings.Compare(a[i].Key, a[j].Key) < 0 + } + return sp < 0 +} + +// FullKey returns this attribute's complete key, including namespace prefix +// if present. +func (a *Attr) FullKey() string { + if a.Space == "" { + return a.Key + } + return a.Space + ":" + a.Key +} + +// Element returns a pointer to the element containing this attribute. +func (a *Attr) Element() *Element { + return a.element +} + +// NamespaceURI returns the XML namespace URI associated with this attribute. +// The function returns the empty string if the attribute is unprefixed or +// if the attribute is part of the XML default namespace. +func (a *Attr) NamespaceURI() string { + if a.Space == "" { + return "" + } + return a.element.findLocalNamespaceURI(a.Space) +} + +// writeTo serializes the attribute to the writer. +func (a *Attr) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteString(a.FullKey()) + w.WriteString(`="`) + var m escapeMode + if s.CanonicalAttrVal { + m = escapeCanonicalAttr + } else { + m = escapeNormal + } + escapeString(w, a.Value, m) + w.WriteByte('"') +} + +// NewText creates an unparented CharData token containing simple text data. +func NewText(text string) *CharData { + return newCharData(text, 0, nil) +} + +// NewCData creates an unparented XML character CDATA section with 'data' as +// its content. +func NewCData(data string) *CharData { + return newCharData(data, cdataFlag, nil) +} + +// NewCharData creates an unparented CharData token containing simple text +// data. +// +// Deprecated: NewCharData is deprecated. Instead, use NewText, which does the +// same thing. +func NewCharData(data string) *CharData { + return newCharData(data, 0, nil) +} + +// newCharData creates a character data token and binds it to a parent +// element. If parent is nil, the CharData token remains unbound. +func newCharData(data string, flags charDataFlags, parent *Element) *CharData { + c := &CharData{ + Data: data, + parent: nil, + index: -1, + flags: flags, + } + if parent != nil { + parent.addChild(c) + } + return c +} + +// CreateText creates a CharData token simple text data and adds it to the +// end of this element's list of child tokens. +func (e *Element) CreateText(text string) *CharData { + return newCharData(text, 0, e) +} + +// CreateCData creates a CharData token containing a CDATA section with 'data' +// as its content and adds it to the end of this element's list of child +// tokens. +func (e *Element) CreateCData(data string) *CharData { + return newCharData(data, cdataFlag, e) +} + +// CreateCharData creates a CharData token simple text data and adds it to the +// end of this element's list of child tokens. +// +// Deprecated: CreateCharData is deprecated. Instead, use CreateText, which +// does the same thing. +func (e *Element) CreateCharData(data string) *CharData { + return newCharData(data, 0, e) +} + +// SetData modifies the content of the CharData token. In the case of a +// CharData token containing simple text, the simple text is modified. In the +// case of a CharData token containing a CDATA section, the CDATA section's +// content is modified. +func (c *CharData) SetData(text string) { + c.Data = text + if isWhitespace(text) { + c.flags |= whitespaceFlag + } else { + c.flags &= ^whitespaceFlag + } +} + +// IsCData returns true if this CharData token is contains a CDATA section. It +// returns false if the CharData token contains simple text. +func (c *CharData) IsCData() bool { + return (c.flags & cdataFlag) != 0 +} + +// IsWhitespace returns true if this CharData token contains only whitespace. +func (c *CharData) IsWhitespace() bool { + return (c.flags & whitespaceFlag) != 0 +} + +// Parent returns this CharData token's parent element, or nil if it has no +// parent. +func (c *CharData) Parent() *Element { + return c.parent +} + +// Index returns the index of this CharData token within its parent element's +// list of child tokens. If this CharData token has no parent, then the +// function returns -1. +func (c *CharData) Index() int { + return c.index +} + +// dup duplicates the character data. +func (c *CharData) dup(parent *Element) Token { + return &CharData{ + Data: c.Data, + flags: c.flags, + parent: parent, + index: c.index, + } +} + +// setParent replaces the character data token's parent. +func (c *CharData) setParent(parent *Element) { + c.parent = parent +} + +// setIndex sets the CharData token's index within its parent element's Child +// slice. +func (c *CharData) setIndex(index int) { + c.index = index +} + +// writeTo serializes character data to the writer. +func (c *CharData) writeTo(w *bufio.Writer, s *WriteSettings) { + if c.IsCData() { + w.WriteString(``) + } else { + var m escapeMode + if s.CanonicalText { + m = escapeCanonicalText + } else { + m = escapeNormal + } + escapeString(w, c.Data, m) + } +} + +// NewComment creates an unparented comment token. +func NewComment(comment string) *Comment { + return newComment(comment, nil) +} + +// NewComment creates a comment token and sets its parent element to 'parent'. +func newComment(comment string, parent *Element) *Comment { + c := &Comment{ + Data: comment, + parent: nil, + index: -1, + } + if parent != nil { + parent.addChild(c) + } + return c +} + +// CreateComment creates a comment token using the specified 'comment' string +// and adds it as the last child token of this element. +func (e *Element) CreateComment(comment string) *Comment { + return newComment(comment, e) +} + +// dup duplicates the comment. +func (c *Comment) dup(parent *Element) Token { + return &Comment{ + Data: c.Data, + parent: parent, + index: c.index, + } +} + +// Parent returns comment token's parent element, or nil if it has no parent. +func (c *Comment) Parent() *Element { + return c.parent +} + +// Index returns the index of this Comment token within its parent element's +// list of child tokens. If this Comment token has no parent, then the +// function returns -1. +func (c *Comment) Index() int { + return c.index +} + +// setParent replaces the comment token's parent. +func (c *Comment) setParent(parent *Element) { + c.parent = parent +} + +// setIndex sets the Comment token's index within its parent element's Child +// slice. +func (c *Comment) setIndex(index int) { + c.index = index +} + +// writeTo serialies the comment to the writer. +func (c *Comment) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteString("") +} + +// NewDirective creates an unparented XML directive token. +func NewDirective(data string) *Directive { + return newDirective(data, nil) +} + +// newDirective creates an XML directive and binds it to a parent element. If +// parent is nil, the Directive remains unbound. +func newDirective(data string, parent *Element) *Directive { + d := &Directive{ + Data: data, + parent: nil, + index: -1, + } + if parent != nil { + parent.addChild(d) + } + return d +} + +// CreateDirective creates an XML directive token with the specified 'data' +// value and adds it as the last child token of this element. +func (e *Element) CreateDirective(data string) *Directive { + return newDirective(data, e) +} + +// dup duplicates the directive. +func (d *Directive) dup(parent *Element) Token { + return &Directive{ + Data: d.Data, + parent: parent, + index: d.index, + } +} + +// Parent returns directive token's parent element, or nil if it has no +// parent. +func (d *Directive) Parent() *Element { + return d.parent +} + +// Index returns the index of this Directive token within its parent element's +// list of child tokens. If this Directive token has no parent, then the +// function returns -1. +func (d *Directive) Index() int { + return d.index +} + +// setParent replaces the directive token's parent. +func (d *Directive) setParent(parent *Element) { + d.parent = parent +} + +// setIndex sets the Directive token's index within its parent element's Child +// slice. +func (d *Directive) setIndex(index int) { + d.index = index +} + +// writeTo serializes the XML directive to the writer. +func (d *Directive) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteString("") +} + +// NewProcInst creates an unparented XML processing instruction. +func NewProcInst(target, inst string) *ProcInst { + return newProcInst(target, inst, nil) +} + +// newProcInst creates an XML processing instruction and binds it to a parent +// element. If parent is nil, the ProcInst remains unbound. +func newProcInst(target, inst string, parent *Element) *ProcInst { + p := &ProcInst{ + Target: target, + Inst: inst, + parent: nil, + index: -1, + } + if parent != nil { + parent.addChild(p) + } + return p +} + +// CreateProcInst creates an XML processing instruction token with the +// sepcified 'target' and instruction 'inst'. It is then added as the last +// child token of this element. +func (e *Element) CreateProcInst(target, inst string) *ProcInst { + return newProcInst(target, inst, e) +} + +// dup duplicates the procinst. +func (p *ProcInst) dup(parent *Element) Token { + return &ProcInst{ + Target: p.Target, + Inst: p.Inst, + parent: parent, + index: p.index, + } +} + +// Parent returns processing instruction token's parent element, or nil if it +// has no parent. +func (p *ProcInst) Parent() *Element { + return p.parent +} + +// Index returns the index of this ProcInst token within its parent element's +// list of child tokens. If this ProcInst token has no parent, then the +// function returns -1. +func (p *ProcInst) Index() int { + return p.index +} + +// setParent replaces the processing instruction token's parent. +func (p *ProcInst) setParent(parent *Element) { + p.parent = parent +} + +// setIndex sets the processing instruction token's index within its parent +// element's Child slice. +func (p *ProcInst) setIndex(index int) { + p.index = index +} + +// writeTo serializes the processing instruction to the writer. +func (p *ProcInst) writeTo(w *bufio.Writer, s *WriteSettings) { + w.WriteString("") +} diff --git a/vendor/github.com/beevik/etree/go.mod b/vendor/github.com/beevik/etree/go.mod new file mode 100644 index 000000000..2a269faff --- /dev/null +++ b/vendor/github.com/beevik/etree/go.mod @@ -0,0 +1,3 @@ +module github.com/beevik/etree + +go 1.12 diff --git a/vendor/github.com/beevik/etree/helpers.go b/vendor/github.com/beevik/etree/helpers.go new file mode 100644 index 000000000..825e14e91 --- /dev/null +++ b/vendor/github.com/beevik/etree/helpers.go @@ -0,0 +1,276 @@ +// Copyright 2015-2019 Brett Vickers. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package etree + +import ( + "bufio" + "io" + "strings" + "unicode/utf8" +) + +// A simple stack +type stack struct { + data []interface{} +} + +func (s *stack) empty() bool { + return len(s.data) == 0 +} + +func (s *stack) push(value interface{}) { + s.data = append(s.data, value) +} + +func (s *stack) pop() interface{} { + value := s.data[len(s.data)-1] + s.data[len(s.data)-1] = nil + s.data = s.data[:len(s.data)-1] + return value +} + +func (s *stack) peek() interface{} { + return s.data[len(s.data)-1] +} + +// A fifo is a simple first-in-first-out queue. +type fifo struct { + data []interface{} + head, tail int +} + +func (f *fifo) add(value interface{}) { + if f.len()+1 >= len(f.data) { + f.grow() + } + f.data[f.tail] = value + if f.tail++; f.tail == len(f.data) { + f.tail = 0 + } +} + +func (f *fifo) remove() interface{} { + value := f.data[f.head] + f.data[f.head] = nil + if f.head++; f.head == len(f.data) { + f.head = 0 + } + return value +} + +func (f *fifo) len() int { + if f.tail >= f.head { + return f.tail - f.head + } + return len(f.data) - f.head + f.tail +} + +func (f *fifo) grow() { + c := len(f.data) * 2 + if c == 0 { + c = 4 + } + buf, count := make([]interface{}, c), f.len() + if f.tail >= f.head { + copy(buf[0:count], f.data[f.head:f.tail]) + } else { + hindex := len(f.data) - f.head + copy(buf[0:hindex], f.data[f.head:]) + copy(buf[hindex:count], f.data[:f.tail]) + } + f.data, f.head, f.tail = buf, 0, count +} + +// countReader implements a proxy reader that counts the number of +// bytes read from its encapsulated reader. +type countReader struct { + r io.Reader + bytes int64 +} + +func newCountReader(r io.Reader) *countReader { + return &countReader{r: r} +} + +func (cr *countReader) Read(p []byte) (n int, err error) { + b, err := cr.r.Read(p) + cr.bytes += int64(b) + return b, err +} + +// countWriter implements a proxy writer that counts the number of +// bytes written by its encapsulated writer. +type countWriter struct { + w io.Writer + bytes int64 +} + +func newCountWriter(w io.Writer) *countWriter { + return &countWriter{w: w} +} + +func (cw *countWriter) Write(p []byte) (n int, err error) { + b, err := cw.w.Write(p) + cw.bytes += int64(b) + return b, err +} + +// isWhitespace returns true if the byte slice contains only +// whitespace characters. +func isWhitespace(s string) bool { + for i := 0; i < len(s); i++ { + if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' { + return false + } + } + return true +} + +// spaceMatch returns true if namespace a is the empty string +// or if namespace a equals namespace b. +func spaceMatch(a, b string) bool { + switch { + case a == "": + return true + default: + return a == b + } +} + +// spaceDecompose breaks a namespace:tag identifier at the ':' +// and returns the two parts. +func spaceDecompose(str string) (space, key string) { + colon := strings.IndexByte(str, ':') + if colon == -1 { + return "", str + } + return str[:colon], str[colon+1:] +} + +// Strings used by indentCRLF and indentLF +const ( + indentSpaces = "\r\n " + indentTabs = "\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t" +) + +// indentCRLF returns a CRLF newline followed by n copies of the first +// non-CRLF character in the source string. +func indentCRLF(n int, source string) string { + switch { + case n < 0: + return source[:2] + case n < len(source)-1: + return source[:n+2] + default: + return source + strings.Repeat(source[2:3], n-len(source)+2) + } +} + +// indentLF returns a LF newline followed by n copies of the first non-LF +// character in the source string. +func indentLF(n int, source string) string { + switch { + case n < 0: + return source[1:2] + case n < len(source)-1: + return source[1 : n+2] + default: + return source[1:] + strings.Repeat(source[2:3], n-len(source)+2) + } +} + +// nextIndex returns the index of the next occurrence of sep in s, +// starting from offset. It returns -1 if the sep string is not found. +func nextIndex(s, sep string, offset int) int { + switch i := strings.Index(s[offset:], sep); i { + case -1: + return -1 + default: + return offset + i + } +} + +// isInteger returns true if the string s contains an integer. +func isInteger(s string) bool { + for i := 0; i < len(s); i++ { + if (s[i] < '0' || s[i] > '9') && !(i == 0 && s[i] == '-') { + return false + } + } + return true +} + +type escapeMode byte + +const ( + escapeNormal escapeMode = iota + escapeCanonicalText + escapeCanonicalAttr +) + +// escapeString writes an escaped version of a string to the writer. +func escapeString(w *bufio.Writer, s string, m escapeMode) { + var esc []byte + last := 0 + for i := 0; i < len(s); { + r, width := utf8.DecodeRuneInString(s[i:]) + i += width + switch r { + case '&': + esc = []byte("&") + case '<': + esc = []byte("<") + case '>': + if m == escapeCanonicalAttr { + continue + } + esc = []byte(">") + case '\'': + if m != escapeNormal { + continue + } + esc = []byte("'") + case '"': + if m == escapeCanonicalText { + continue + } + esc = []byte(""") + case '\t': + if m != escapeCanonicalAttr { + continue + } + esc = []byte(" ") + case '\n': + if m != escapeCanonicalAttr { + continue + } + esc = []byte(" ") + case '\r': + if m == escapeNormal { + continue + } + esc = []byte(" ") + default: + if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) { + esc = []byte("\uFFFD") + break + } + continue + } + w.WriteString(s[last : i-width]) + w.Write(esc) + last = i + } + w.WriteString(s[last:]) +} + +func isInCharacterRange(r rune) bool { + return r == 0x09 || + r == 0x0A || + r == 0x0D || + r >= 0x20 && r <= 0xD7FF || + r >= 0xE000 && r <= 0xFFFD || + r >= 0x10000 && r <= 0x10FFFF +} diff --git a/vendor/github.com/beevik/etree/path.go b/vendor/github.com/beevik/etree/path.go new file mode 100644 index 000000000..d183c8944 --- /dev/null +++ b/vendor/github.com/beevik/etree/path.go @@ -0,0 +1,580 @@ +// Copyright 2015-2019 Brett Vickers. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package etree + +import ( + "strconv" + "strings" +) + +/* +A Path is a string that represents a search path through an etree starting +from the document root or an arbitrary element. Paths are used with the +Element object's Find* methods to locate and return desired elements. + +A Path consists of a series of slash-separated "selectors", each of which may +be modified by one or more bracket-enclosed "filters". Selectors are used to +traverse the etree from element to element, while filters are used to narrow +the list of candidate elements at each node. + +Although etree Path strings are structurally and behaviorally similar to XPath +strings (https://www.w3.org/TR/1999/REC-xpath-19991116/), they have a more +limited set of selectors and filtering options. + +The following selectors are supported by etree paths: + + . Select the current element. + .. Select the parent of the current element. + * Select all child elements of the current element. + / Select the root element when used at the start of a path. + // Select all descendants of the current element. + tag Select all child elements with a name matching the tag. + +The following basic filters are supported: + + [@attrib] Keep elements with an attribute named attrib. + [@attrib='val'] Keep elements with an attribute named attrib and value matching val. + [tag] Keep elements with a child element named tag. + [tag='val'] Keep elements with a child element named tag and text matching val. + [n] Keep the n-th element, where n is a numeric index starting from 1. + +The following function-based filters are supported: + + [text()] Keep elements with non-empty text. + [text()='val'] Keep elements whose text matches val. + [local-name()='val'] Keep elements whose un-prefixed tag matches val. + [name()='val'] Keep elements whose full tag exactly matches val. + [namespace-prefix()] Keep elements with non-empty namespace prefixes. + [namespace-prefix()='val'] Keep elements whose namespace prefix matches val. + [namespace-uri()] Keep elements with non-empty namespace URIs. + [namespace-uri()='val'] Keep elements whose namespace URI matches val. + +Below are some examples of etree path strings. + +Select the bookstore child element of the root element: + /bookstore + +Beginning from the root element, select the title elements of all descendant +book elements having a 'category' attribute of 'WEB': + //book[@category='WEB']/title + +Beginning from the current element, select the first descendant book element +with a title child element containing the text 'Great Expectations': + .//book[title='Great Expectations'][1] + +Beginning from the current element, select all child elements of book elements +with an attribute 'language' set to 'english': + ./book/*[@language='english'] + +Beginning from the current element, select all child elements of book elements +containing the text 'special': + ./book/*[text()='special'] + +Beginning from the current element, select all descendant book elements whose +title child element has a 'language' attribute of 'french': + .//book/title[@language='french']/.. + +Beginning from the current element, select all descendant book elements +belonging to the http://www.w3.org/TR/html4/ namespace: + .//book[namespace-uri()='http://www.w3.org/TR/html4/'] + +*/ +type Path struct { + segments []segment +} + +// ErrPath is returned by path functions when an invalid etree path is provided. +type ErrPath string + +// Error returns the string describing a path error. +func (err ErrPath) Error() string { + return "etree: " + string(err) +} + +// CompilePath creates an optimized version of an XPath-like string that +// can be used to query elements in an element tree. +func CompilePath(path string) (Path, error) { + var comp compiler + segments := comp.parsePath(path) + if comp.err != ErrPath("") { + return Path{nil}, comp.err + } + return Path{segments}, nil +} + +// MustCompilePath creates an optimized version of an XPath-like string that +// can be used to query elements in an element tree. Panics if an error +// occurs. Use this function to create Paths when you know the path is +// valid (i.e., if it's hard-coded). +func MustCompilePath(path string) Path { + p, err := CompilePath(path) + if err != nil { + panic(err) + } + return p +} + +// A segment is a portion of a path between "/" characters. +// It contains one selector and zero or more [filters]. +type segment struct { + sel selector + filters []filter +} + +func (seg *segment) apply(e *Element, p *pather) { + seg.sel.apply(e, p) + for _, f := range seg.filters { + f.apply(p) + } +} + +// A selector selects XML elements for consideration by the +// path traversal. +type selector interface { + apply(e *Element, p *pather) +} + +// A filter pares down a list of candidate XML elements based +// on a path filter in [brackets]. +type filter interface { + apply(p *pather) +} + +// A pather is helper object that traverses an element tree using +// a Path object. It collects and deduplicates all elements matching +// the path query. +type pather struct { + queue fifo + results []*Element + inResults map[*Element]bool + candidates []*Element + scratch []*Element // used by filters +} + +// A node represents an element and the remaining path segments that +// should be applied against it by the pather. +type node struct { + e *Element + segments []segment +} + +func newPather() *pather { + return &pather{ + results: make([]*Element, 0), + inResults: make(map[*Element]bool), + candidates: make([]*Element, 0), + scratch: make([]*Element, 0), + } +} + +// traverse follows the path from the element e, collecting +// and then returning all elements that match the path's selectors +// and filters. +func (p *pather) traverse(e *Element, path Path) []*Element { + for p.queue.add(node{e, path.segments}); p.queue.len() > 0; { + p.eval(p.queue.remove().(node)) + } + return p.results +} + +// eval evalutes the current path node by applying the remaining +// path's selector rules against the node's element. +func (p *pather) eval(n node) { + p.candidates = p.candidates[0:0] + seg, remain := n.segments[0], n.segments[1:] + seg.apply(n.e, p) + + if len(remain) == 0 { + for _, c := range p.candidates { + if in := p.inResults[c]; !in { + p.inResults[c] = true + p.results = append(p.results, c) + } + } + } else { + for _, c := range p.candidates { + p.queue.add(node{c, remain}) + } + } +} + +// A compiler generates a compiled path from a path string. +type compiler struct { + err ErrPath +} + +// parsePath parses an XPath-like string describing a path +// through an element tree and returns a slice of segment +// descriptors. +func (c *compiler) parsePath(path string) []segment { + // If path ends with //, fix it + if strings.HasSuffix(path, "//") { + path += "*" + } + + var segments []segment + + // Check for an absolute path + if strings.HasPrefix(path, "/") { + segments = append(segments, segment{new(selectRoot), []filter{}}) + path = path[1:] + } + + // Split path into segments + for _, s := range splitPath(path) { + segments = append(segments, c.parseSegment(s)) + if c.err != ErrPath("") { + break + } + } + return segments +} + +func splitPath(path string) []string { + var pieces []string + start := 0 + inquote := false + for i := 0; i+1 <= len(path); i++ { + if path[i] == '\'' { + inquote = !inquote + } else if path[i] == '/' && !inquote { + pieces = append(pieces, path[start:i]) + start = i + 1 + } + } + return append(pieces, path[start:]) +} + +// parseSegment parses a path segment between / characters. +func (c *compiler) parseSegment(path string) segment { + pieces := strings.Split(path, "[") + seg := segment{ + sel: c.parseSelector(pieces[0]), + filters: []filter{}, + } + for i := 1; i < len(pieces); i++ { + fpath := pieces[i] + if fpath[len(fpath)-1] != ']' { + c.err = ErrPath("path has invalid filter [brackets].") + break + } + seg.filters = append(seg.filters, c.parseFilter(fpath[:len(fpath)-1])) + } + return seg +} + +// parseSelector parses a selector at the start of a path segment. +func (c *compiler) parseSelector(path string) selector { + switch path { + case ".": + return new(selectSelf) + case "..": + return new(selectParent) + case "*": + return new(selectChildren) + case "": + return new(selectDescendants) + default: + return newSelectChildrenByTag(path) + } +} + +var fnTable = map[string]func(e *Element) string{ + "local-name": (*Element).name, + "name": (*Element).FullTag, + "namespace-prefix": (*Element).namespacePrefix, + "namespace-uri": (*Element).NamespaceURI, + "text": (*Element).Text, +} + +// parseFilter parses a path filter contained within [brackets]. +func (c *compiler) parseFilter(path string) filter { + if len(path) == 0 { + c.err = ErrPath("path contains an empty filter expression.") + return nil + } + + // Filter contains [@attr='val'], [fn()='val'], or [tag='val']? + eqindex := strings.Index(path, "='") + if eqindex >= 0 { + rindex := nextIndex(path, "'", eqindex+2) + if rindex != len(path)-1 { + c.err = ErrPath("path has mismatched filter quotes.") + return nil + } + + key := path[:eqindex] + value := path[eqindex+2 : rindex] + + switch { + case key[0] == '@': + return newFilterAttrVal(key[1:], value) + case strings.HasSuffix(key, "()"): + name := key[:len(key)-2] + if fn, ok := fnTable[name]; ok { + return newFilterFuncVal(fn, value) + } + c.err = ErrPath("path has unknown function " + name) + return nil + default: + return newFilterChildText(key, value) + } + } + + // Filter contains [@attr], [N], [tag] or [fn()] + switch { + case path[0] == '@': + return newFilterAttr(path[1:]) + case strings.HasSuffix(path, "()"): + name := path[:len(path)-2] + if fn, ok := fnTable[name]; ok { + return newFilterFunc(fn) + } + c.err = ErrPath("path has unknown function " + name) + return nil + case isInteger(path): + pos, _ := strconv.Atoi(path) + switch { + case pos > 0: + return newFilterPos(pos - 1) + default: + return newFilterPos(pos) + } + default: + return newFilterChild(path) + } +} + +// selectSelf selects the current element into the candidate list. +type selectSelf struct{} + +func (s *selectSelf) apply(e *Element, p *pather) { + p.candidates = append(p.candidates, e) +} + +// selectRoot selects the element's root node. +type selectRoot struct{} + +func (s *selectRoot) apply(e *Element, p *pather) { + root := e + for root.parent != nil { + root = root.parent + } + p.candidates = append(p.candidates, root) +} + +// selectParent selects the element's parent into the candidate list. +type selectParent struct{} + +func (s *selectParent) apply(e *Element, p *pather) { + if e.parent != nil { + p.candidates = append(p.candidates, e.parent) + } +} + +// selectChildren selects the element's child elements into the +// candidate list. +type selectChildren struct{} + +func (s *selectChildren) apply(e *Element, p *pather) { + for _, c := range e.Child { + if c, ok := c.(*Element); ok { + p.candidates = append(p.candidates, c) + } + } +} + +// selectDescendants selects all descendant child elements +// of the element into the candidate list. +type selectDescendants struct{} + +func (s *selectDescendants) apply(e *Element, p *pather) { + var queue fifo + for queue.add(e); queue.len() > 0; { + e := queue.remove().(*Element) + p.candidates = append(p.candidates, e) + for _, c := range e.Child { + if c, ok := c.(*Element); ok { + queue.add(c) + } + } + } +} + +// selectChildrenByTag selects into the candidate list all child +// elements of the element having the specified tag. +type selectChildrenByTag struct { + space, tag string +} + +func newSelectChildrenByTag(path string) *selectChildrenByTag { + s, l := spaceDecompose(path) + return &selectChildrenByTag{s, l} +} + +func (s *selectChildrenByTag) apply(e *Element, p *pather) { + for _, c := range e.Child { + if c, ok := c.(*Element); ok && spaceMatch(s.space, c.Space) && s.tag == c.Tag { + p.candidates = append(p.candidates, c) + } + } +} + +// filterPos filters the candidate list, keeping only the +// candidate at the specified index. +type filterPos struct { + index int +} + +func newFilterPos(pos int) *filterPos { + return &filterPos{pos} +} + +func (f *filterPos) apply(p *pather) { + if f.index >= 0 { + if f.index < len(p.candidates) { + p.scratch = append(p.scratch, p.candidates[f.index]) + } + } else { + if -f.index <= len(p.candidates) { + p.scratch = append(p.scratch, p.candidates[len(p.candidates)+f.index]) + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterAttr filters the candidate list for elements having +// the specified attribute. +type filterAttr struct { + space, key string +} + +func newFilterAttr(str string) *filterAttr { + s, l := spaceDecompose(str) + return &filterAttr{s, l} +} + +func (f *filterAttr) apply(p *pather) { + for _, c := range p.candidates { + for _, a := range c.Attr { + if spaceMatch(f.space, a.Space) && f.key == a.Key { + p.scratch = append(p.scratch, c) + break + } + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterAttrVal filters the candidate list for elements having +// the specified attribute with the specified value. +type filterAttrVal struct { + space, key, val string +} + +func newFilterAttrVal(str, value string) *filterAttrVal { + s, l := spaceDecompose(str) + return &filterAttrVal{s, l, value} +} + +func (f *filterAttrVal) apply(p *pather) { + for _, c := range p.candidates { + for _, a := range c.Attr { + if spaceMatch(f.space, a.Space) && f.key == a.Key && f.val == a.Value { + p.scratch = append(p.scratch, c) + break + } + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterFunc filters the candidate list for elements satisfying a custom +// boolean function. +type filterFunc struct { + fn func(e *Element) string +} + +func newFilterFunc(fn func(e *Element) string) *filterFunc { + return &filterFunc{fn} +} + +func (f *filterFunc) apply(p *pather) { + for _, c := range p.candidates { + if f.fn(c) != "" { + p.scratch = append(p.scratch, c) + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterFuncVal filters the candidate list for elements containing a value +// matching the result of a custom function. +type filterFuncVal struct { + fn func(e *Element) string + val string +} + +func newFilterFuncVal(fn func(e *Element) string, value string) *filterFuncVal { + return &filterFuncVal{fn, value} +} + +func (f *filterFuncVal) apply(p *pather) { + for _, c := range p.candidates { + if f.fn(c) == f.val { + p.scratch = append(p.scratch, c) + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterChild filters the candidate list for elements having +// a child element with the specified tag. +type filterChild struct { + space, tag string +} + +func newFilterChild(str string) *filterChild { + s, l := spaceDecompose(str) + return &filterChild{s, l} +} + +func (f *filterChild) apply(p *pather) { + for _, c := range p.candidates { + for _, cc := range c.Child { + if cc, ok := cc.(*Element); ok && + spaceMatch(f.space, cc.Space) && + f.tag == cc.Tag { + p.scratch = append(p.scratch, c) + } + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} + +// filterChildText filters the candidate list for elements having +// a child element with the specified tag and text. +type filterChildText struct { + space, tag, text string +} + +func newFilterChildText(str, text string) *filterChildText { + s, l := spaceDecompose(str) + return &filterChildText{s, l, text} +} + +func (f *filterChildText) apply(p *pather) { + for _, c := range p.candidates { + for _, cc := range c.Child { + if cc, ok := cc.(*Element); ok && + spaceMatch(f.space, cc.Space) && + f.tag == cc.Tag && + f.text == cc.Text() { + p.scratch = append(p.scratch, c) + } + } + } + p.candidates, p.scratch = p.scratch, p.candidates[0:0] +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 164d3db3c..34bc6c6e2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -58,6 +58,9 @@ github.com/aws/aws-sdk-go/service/s3/s3iface github.com/aws/aws-sdk-go/service/s3/s3manager github.com/aws/aws-sdk-go/service/sts github.com/aws/aws-sdk-go/service/sts/stsiface +# github.com/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c +## explicit +github.com/beevik/etree # github.com/coreos/go-json v0.0.0-20211020211907-c63f628265de github.com/coreos/go-json # github.com/coreos/go-semver v0.3.0 From e82aed09ef21581bbaefa60e736b0d5aba426130 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Fri, 18 Mar 2022 00:04:14 -0400 Subject: [PATCH 07/13] main: add ignition-rmcfg multicall binary For now, deleting Ignition configs from the provider will be handled by a separate program running in the real root. That allows a config with any spec version to disable the deletion by masking the unit. --- internal/main.go | 60 +++++++++++++++++++++++++++++++-- internal/platform/platform.go | 14 ++++++++ internal/providers/providers.go | 1 + 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/internal/main.go b/internal/main.go index 10d66a1f1..809a92b5f 100644 --- a/internal/main.go +++ b/internal/main.go @@ -41,10 +41,13 @@ import ( ) func main() { - if filepath.Base(os.Args[0]) == "ignition-apply" { + switch filepath.Base(os.Args[0]) { + case "ignition-apply": ignitionApplyMain() - } else { - // otherwise, assume regular Ignition + case "ignition-rmcfg": + ignitionRmCfgMain() + default: + // assume regular Ignition ignitionMain() } } @@ -187,3 +190,54 @@ func ignitionApplyMain() { os.Exit(1) } } + +func ignitionRmCfgMain() { + flags := struct { + logToStdout bool + platform string + version bool + }{} + pflag.StringVar(&flags.platform, "platform", "", fmt.Sprintf("current platform. %v", platform.Names())) + pflag.BoolVar(&flags.logToStdout, "log-to-stdout", false, "log to stdout instead of the system log") + pflag.BoolVar(&flags.version, "version", false, "print the version and exit") + pflag.Usage = func() { + fmt.Fprintf(pflag.CommandLine.Output(), "Usage: %s [options]\n", os.Args[0]) + fmt.Fprintf(pflag.CommandLine.Output(), "Options:\n") + pflag.PrintDefaults() + } + pflag.Parse() + + if flags.version { + fmt.Printf("%s\n", version.String) + return + } + + if pflag.NArg() != 0 { + pflag.Usage() + os.Exit(2) + } + + if flags.platform == "" { + fmt.Fprint(os.Stderr, "'--platform' must be provided\n") + os.Exit(2) + } + + logger := log.New(flags.logToStdout) + defer logger.Close() + + logger.Info(version.String) + + platformConfig := platform.MustGet(flags.platform) + fetcher, err := platformConfig.NewFetcherFunc()(&logger) + if err != nil { + logger.Crit("failed to generate fetcher: %s", err) + os.Exit(3) + } + + if err := platformConfig.DelConfig(&fetcher); err != nil { + logger.Crit("couldn't delete config: %s", err) + os.Exit(1) + } + + logger.Info("Successfully deleted config") +} diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 1aa0bc8bd..6ceeee953 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -15,6 +15,7 @@ package platform import ( + "errors" "fmt" "github.com/coreos/ignition/v2/internal/log" @@ -44,6 +45,10 @@ import ( "github.com/coreos/ignition/v2/internal/resource" ) +var ( + ErrCannotDelete = errors.New("cannot delete config on this platform") +) + // Config represents a set of options that map to a particular platform. type Config struct { name string @@ -51,6 +56,7 @@ type Config struct { init providers.FuncInit newFetcher providers.FuncNewFetcher status providers.FuncPostStatus + delConfig providers.FuncDelConfig } func (c Config) Name() string { @@ -93,6 +99,14 @@ func (c Config) Status(stageName string, f resource.Fetcher, statusErr error) er return nil } +func (c Config) DelConfig(f *resource.Fetcher) error { + if c.delConfig != nil { + return c.delConfig(f) + } else { + return ErrCannotDelete + } +} + var configs = registry.Create("platform configs") func init() { diff --git a/internal/providers/providers.go b/internal/providers/providers.go index 3e02fb2df..daa80bfb9 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -32,3 +32,4 @@ type FuncFetchConfig func(f *resource.Fetcher) (types.Config, report.Report, err type FuncInit func(f *resource.Fetcher) error type FuncNewFetcher func(logger *log.Logger) (resource.Fetcher, error) type FuncPostStatus func(stageName string, f resource.Fetcher, e error) error +type FuncDelConfig func(f *resource.Fetcher) error From 50138c42f10a7e38a1d189cec8241bb261aabe1b Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 20 Mar 2022 23:41:48 -0400 Subject: [PATCH 08/13] providers/vmware: support deleting Ignition configs Configs might be in guestinfo properties or OVF metadata. Remove any config in the OVF metadata, and replace any config in guestinfo (since we can't delete properties entirely). --- internal/platform/platform.go | 5 +- internal/providers/vmware/ovf.go | 67 +++++++++++++++++++ internal/providers/vmware/ovf_test.go | 60 +++++++++++++++++ internal/providers/vmware/vmware_amd64.go | 56 ++++++++++++++++ .../providers/vmware/vmware_unsupported.go | 4 ++ 5 files changed, 190 insertions(+), 2 deletions(-) diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 6ceeee953..664f810ae 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -190,8 +190,9 @@ func init() { fetch: virtualbox.FetchConfig, }) configs.Register(Config{ - name: "vmware", - fetch: vmware.FetchConfig, + name: "vmware", + fetch: vmware.FetchConfig, + delConfig: vmware.DelConfig, }) configs.Register(Config{ name: "vultr", diff --git a/internal/providers/vmware/ovf.go b/internal/providers/vmware/ovf.go index ad163560e..c95e4cac3 100644 --- a/internal/providers/vmware/ovf.go +++ b/internal/providers/vmware/ovf.go @@ -18,6 +18,13 @@ package vmware import ( "encoding/xml" + "fmt" + + "github.com/beevik/etree" +) + +const ( + XMLNS = "http://schemas.dmtf.org/ovf/environment/1" ) type environment struct { @@ -54,3 +61,63 @@ func ReadOvfEnvironment(doc []byte) (OvfEnvironment, error) { } return OvfEnvironment{Properties: dict, Platform: env.Platform}, nil } + +// Return the new OVF document, and true if anything was deleted. +func DeleteOvfProperties(from []byte, props []string) ([]byte, bool, error) { + // parse document + doc := etree.NewDocument() + if err := doc.ReadFromBytes(from); err != nil { + return nil, false, fmt.Errorf("parsing OVF environment: %w", err) + } + + // build set of properties to drop + drops := make(map[string]struct{}) + for _, prop := range props { + drops[prop] = struct{}{} + } + + // etree's XPath implementation isn't rigorous about keeping + // separate namespaces separate. Be extra-careful to check + // namespace URIs everywhere. + removed := false + for _, parent := range doc.FindElements("/Environment[namespace-uri()='" + XMLNS + "']/PropertySection[namespace-uri()='" + XMLNS + "']") { + // walk each property + var remove []*etree.Element + for _, el := range parent.ChildElements() { + if el.NamespaceURI() != XMLNS { + continue + } + // walk attrs by hand so we can check namespaces + for _, attr := range el.Attr { + if attr.NamespaceURI() != XMLNS { + continue + } + // queue property for removal if it's on the + // list + if attr.Key == "key" { + if _, drop := drops[attr.Value]; drop { + remove = append(remove, el) + removed = true + } + break + } + } + } + // remove queued properties + for _, el := range remove { + parent.RemoveChild(el) + } + } + + // Out of caution, if we didn't find anything to remove, return + // the input bytes rather than reserializing. + if !removed { + return from, removed, nil + } + + to, err := doc.WriteToBytes() + if err != nil { + return nil, false, fmt.Errorf("serializing OVF environment: %w", err) + } + return to, removed, nil +} diff --git a/internal/providers/vmware/ovf_test.go b/internal/providers/vmware/ovf_test.go index e403afaba..5bc859881 100644 --- a/internal/providers/vmware/ovf_test.go +++ b/internal/providers/vmware/ovf_test.go @@ -17,6 +17,7 @@ package vmware import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -66,6 +67,42 @@ var data_vapprun = []byte(` `) +var data_delete_prop_simple = []byte(` + + + garbage! + + + + + + +`) + +var data_delete_prop_complex = []byte(` + + + vapprun + 1.0 + VMware, Inc. + en_US + + + garbage! + + + + + + + + + + + + +`) + func TestOvfEnvProperties(t *testing.T) { var testOne = func(env_str []byte) { env, err := ReadOvfEnvironment(env_str) @@ -115,3 +152,26 @@ func TestInvalidData(t *testing.T) { _, err := ReadOvfEnvironment(append(data_vsphere, []byte("garbage")...)) assert.Nil(t, err) } + +func TestDeleteProps(t *testing.T) { + for _, sample := range [][]byte{data_delete_prop_simple, data_delete_prop_complex, data_vapprun} { + var expectedLines []string + expectedDelete := false + for _, line := range strings.Split(string(sample), "\n") { + if strings.Contains(line, "remove") { + // drop XML element, leave indentation + startSkip := strings.IndexAny(line, "<>") + endSkip := strings.LastIndexAny(line, "<>") + line = line[:startSkip] + line[endSkip+1:] + expectedDelete = true + } + expectedLines = append(expectedLines, line) + } + expected := strings.Join(expectedLines, "\n") + + result, deleted, err := DeleteOvfProperties(sample, []string{"guestinfo.ignition.config.data", "guestinfo.ignition.config.data.encoding"}) + assert.Nil(t, err) + assert.Equal(t, expected, string(result)) + assert.Equal(t, expectedDelete, deleted) + } +} diff --git a/internal/providers/vmware/vmware_amd64.go b/internal/providers/vmware/vmware_amd64.go index f1fac0fa1..186ec97d5 100644 --- a/internal/providers/vmware/vmware_amd64.go +++ b/internal/providers/vmware/vmware_amd64.go @@ -18,6 +18,8 @@ package vmware import ( + "fmt" + "github.com/coreos/ignition/v2/config/v3_4_experimental/types" "github.com/coreos/ignition/v2/internal/providers" "github.com/coreos/ignition/v2/internal/providers/util" @@ -33,6 +35,9 @@ const ( GUESTINFO_USERDATA = "ignition.config.data" GUESTINFO_USERDATA_ENCODING = "ignition.config.data.encoding" + GUESTINFO_DELETED_USERDATA = "e30K" + GUESTINFO_DELETED_USERDATA_ENCODING = "base64" + OVF_PREFIX = "guestinfo." OVF_USERDATA = OVF_PREFIX + GUESTINFO_USERDATA OVF_USERDATA_ENCODING = OVF_PREFIX + GUESTINFO_USERDATA_ENCODING @@ -97,3 +102,54 @@ func fetchRawConfig(f *resource.Fetcher) (config, error) { encoding: encoding, }, nil } + +func DelConfig(f *resource.Fetcher) error { + info := rpcvmx.NewConfig() + + // delete userdata if set and not already a deletion marker + orig, err := info.String(GUESTINFO_USERDATA, GUESTINFO_DELETED_USERDATA) + if err != nil { + return fmt.Errorf("getting config property: %w", err) + } + if orig != GUESTINFO_DELETED_USERDATA { + // we can't delete properties or set them to the empty + // string, so set encoding to "base64" and data to encoded "{}" + f.Logger.Info("deleting config from guestinfo properties") + if err := info.SetString(GUESTINFO_USERDATA, GUESTINFO_DELETED_USERDATA); err != nil { + return fmt.Errorf("replacing config: %w", err) + } + + // overwrite encoding if unset or not already base64 + origEncoding, err := info.String(GUESTINFO_USERDATA_ENCODING, "") + if err != nil { + return fmt.Errorf("getting config encoding property: %w", err) + } + if origEncoding != GUESTINFO_DELETED_USERDATA_ENCODING { + if err := info.SetString(GUESTINFO_USERDATA_ENCODING, GUESTINFO_DELETED_USERDATA_ENCODING); err != nil { + return fmt.Errorf("replacing config encoding: %w", err) + } + } + } + + ovfEnv, err := info.String(GUESTINFO_OVF, "") + if err != nil { + // unlike FetchConfig, don't ignore errors, since that could + // have security implications + return fmt.Errorf("reading OVF environment: %w", err) + } + if ovfEnv != "" { + prunedData, didPrune, err := DeleteOvfProperties([]byte(ovfEnv), []string{OVF_USERDATA, OVF_USERDATA_ENCODING}) + if err != nil { + return fmt.Errorf("deleting OVF properties: %w", err) + } + // don't rewrite the property if there's nothing to change + if didPrune { + f.Logger.Info("deleting config from OVF environment") + if err := info.SetString(GUESTINFO_OVF, string(prunedData)); err != nil { + return fmt.Errorf("replacing OVF environment: %w", err) + } + } + } + + return nil +} diff --git a/internal/providers/vmware/vmware_unsupported.go b/internal/providers/vmware/vmware_unsupported.go index 72d0c0823..e9e26cca8 100644 --- a/internal/providers/vmware/vmware_unsupported.go +++ b/internal/providers/vmware/vmware_unsupported.go @@ -32,3 +32,7 @@ import ( func FetchConfig(_ *resource.Fetcher) (types.Config, report.Report, error) { return types.Config{}, report.Report{}, errors.New("vmware provider is not supported on this architecture") } + +func DelConfig(_ *resource.Fetcher) error { + return errors.New("vmware provider is not supported on this architecture") +} From 67d26bb00d13bca93b273e085e472752ff48f1dd Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 20 Mar 2022 23:42:48 -0400 Subject: [PATCH 09/13] providers/virtualbox: add helper to set up hypervisor connection --- internal/providers/virtualbox/virtualbox.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/internal/providers/virtualbox/virtualbox.c b/internal/providers/virtualbox/virtualbox.c index 0fbf02899..ce6fd3683 100644 --- a/internal/providers/virtualbox/virtualbox.c +++ b/internal/providers/virtualbox/virtualbox.c @@ -162,7 +162,7 @@ static int disconnect(int fd, uint32_t client_id) { return msg.hdr.rc; } -int virtualbox_get_guest_property(char *name, void **value, size_t *size) { +static int start_connection(uint32_t *client_id) { // clear any previous garbage in errno for error returns errno = 0; @@ -179,12 +179,26 @@ int virtualbox_get_guest_property(char *name, void **value, size_t *size) { } // connect to property service - uint32_t client_id; - ret = connect(fd, &client_id); + ret = connect(fd, client_id); if (ret != VINF_SUCCESS) { return ret; } + // return fd + ret = fd; + fd = -1; + return ret; +} + +int virtualbox_get_guest_property(char *name, void **value, size_t *size) { + // connect + uint32_t client_id; + int ret = start_connection(&client_id); + if (ret < 0) { + return ret; + } + int _cleanup_close_ fd = ret; + // get property ret = get_prop(fd, client_id, name, value, size); if (ret != VINF_SUCCESS) { From 3f4d0b215854eb59fa13482dca65114de7b48744 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 2 May 2022 23:43:48 -0400 Subject: [PATCH 10/13] providers/virtualbox: add define for GUEST_PROP_FN_GET_PROP --- internal/providers/virtualbox/virtualbox.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/providers/virtualbox/virtualbox.c b/internal/providers/virtualbox/virtualbox.c index ce6fd3683..47c7ca083 100644 --- a/internal/providers/virtualbox/virtualbox.c +++ b/internal/providers/virtualbox/virtualbox.c @@ -24,6 +24,9 @@ #include #include "virtualbox.h" +// From virtualbox/include/VBox/HostServices/GuestPropertySvc.h +#define GUEST_PROP_FN_GET_PROP 1 + static void _cleanup_close(int *fd) { if (*fd != -1) { close(*fd); @@ -92,7 +95,7 @@ static int get_prop(int fd, uint32_t client_id, const char *name, void **value, // init_header re-adds the size of msg->hdr init_header(&msg->hdr, msg_size - sizeof(msg->hdr), msg_size - sizeof(msg->hdr)); msg->client_id = client_id; - msg->function = 1; // GUEST_PROP_FN_GET_PROP + msg->function = GUEST_PROP_FN_GET_PROP; msg->timeout_ms = -1; // inf msg->interruptible = 1; msg->parm_count = 4; From e5bc16ae6045214c5102278151a2a4c3c9fe9f06 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 2 May 2022 23:54:42 -0400 Subject: [PATCH 11/13] providers/virtualbox: add comment referencing VirtualBox source --- internal/providers/virtualbox/virtualbox.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/providers/virtualbox/virtualbox.c b/internal/providers/virtualbox/virtualbox.c index 47c7ca083..57968bc65 100644 --- a/internal/providers/virtualbox/virtualbox.c +++ b/internal/providers/virtualbox/virtualbox.c @@ -89,6 +89,9 @@ static int connect(int fd, uint32_t *client_id) { } static int get_prop(int fd, uint32_t client_id, const char *name, void **value, size_t *size) { + // xref VbglR3GuestPropRead() in + // virtualbox/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp + // init header size_t msg_size = sizeof(struct vbg_ioctl_hgcm_call) + 4 * sizeof(struct vmmdev_hgcm_function_parameter64); struct vbg_ioctl_hgcm_call _cleanup_free_ *msg = calloc(1, msg_size); From 6329b67b21f753c36fb5be7d0574ba7859163603 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 20 Mar 2022 23:44:41 -0400 Subject: [PATCH 12/13] providers/virtualbox: support deleting Ignition configs Delete config and config-encoding guest properties. --- internal/platform/platform.go | 5 +- internal/providers/virtualbox/virtualbox.c | 62 +++++++++++++++++++++ internal/providers/virtualbox/virtualbox.go | 31 +++++++++++ internal/providers/virtualbox/virtualbox.h | 1 + 4 files changed, 97 insertions(+), 2 deletions(-) diff --git a/internal/platform/platform.go b/internal/platform/platform.go index 664f810ae..233394392 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -186,8 +186,9 @@ func init() { fetch: qemu.FetchConfig, }) configs.Register(Config{ - name: "virtualbox", - fetch: virtualbox.FetchConfig, + name: "virtualbox", + fetch: virtualbox.FetchConfig, + delConfig: virtualbox.DelConfig, }) configs.Register(Config{ name: "vmware", diff --git a/internal/providers/virtualbox/virtualbox.c b/internal/providers/virtualbox/virtualbox.c index 57968bc65..a2783d8aa 100644 --- a/internal/providers/virtualbox/virtualbox.c +++ b/internal/providers/virtualbox/virtualbox.c @@ -26,6 +26,7 @@ // From virtualbox/include/VBox/HostServices/GuestPropertySvc.h #define GUEST_PROP_FN_GET_PROP 1 +#define GUEST_PROP_FN_DEL_PROP 4 static void _cleanup_close(int *fd) { if (*fd != -1) { @@ -153,6 +154,38 @@ static int get_prop(int fd, uint32_t client_id, const char *name, void **value, } } +static int del_prop(int fd, uint32_t client_id, const char *name) { + // xref VbglR3GuestPropDelete() in + // virtualbox/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp + + // init header + size_t msg_size = sizeof(struct vbg_ioctl_hgcm_call) + sizeof(struct vmmdev_hgcm_function_parameter64); + struct vbg_ioctl_hgcm_call _cleanup_free_ *msg = calloc(1, msg_size); + // init_header re-adds the size of msg->hdr + init_header(&msg->hdr, msg_size - sizeof(msg->hdr), msg_size - sizeof(msg->hdr)); + msg->client_id = client_id; + msg->function = GUEST_PROP_FN_DEL_PROP; + msg->timeout_ms = -1; // inf + msg->interruptible = 1; + msg->parm_count = 1; + + // init arguments + struct vmmdev_hgcm_function_parameter64 *params = (void *) (msg + 1); + // property name (in) + params[0].type = VMMDEV_HGCM_PARM_TYPE_LINADDR_IN; + params[0].u.pointer.size = strlen(name) + 1; + params[0].u.pointer.u.linear_addr = (uintptr_t) name; + + // delete value + if (ioctl(fd, VBG_IOCTL_HGCM_CALL_64(msg_size), msg)) { + return VERR_GENERAL_FAILURE; + } + if (msg->hdr.rc != VINF_SUCCESS) { + return msg->hdr.rc; + } + return VINF_SUCCESS; +} + static int disconnect(int fd, uint32_t client_id) { struct vbg_ioctl_hgcm_disconnect msg = { .u = { @@ -226,3 +259,32 @@ int virtualbox_get_guest_property(char *name, void **value, size_t *size) { errno = 0; return 0; } + +int virtualbox_delete_guest_property(char *name) { + // connect + uint32_t client_id; + int ret = start_connection(&client_id); + if (ret < 0) { + return ret; + } + int _cleanup_close_ fd = ret; + + // delete property + ret = del_prop(fd, client_id, name); + if (ret != VINF_SUCCESS) { + disconnect(fd, client_id); + return ret; + } + + // disconnect + ret = disconnect(fd, client_id); + if (ret != VINF_SUCCESS) { + // we could ignore the failure, but better to make sure bugs + // are noticed + return ret; + } + + // for clarity, ensure the Go error return is nil + errno = 0; + return 0; +} diff --git a/internal/providers/virtualbox/virtualbox.go b/internal/providers/virtualbox/virtualbox.go index 3e982db07..bc805a3d9 100644 --- a/internal/providers/virtualbox/virtualbox.go +++ b/internal/providers/virtualbox/virtualbox.go @@ -69,6 +69,19 @@ func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) { return util.ParseConfig(f.Logger, config) } +func DelConfig(f *resource.Fetcher) error { + f.Logger.Info("deleting Ignition config from VirtualBox guest property") + err := deleteProperty(configEncodingProperty) + if err != nil { + return err + } + err = deleteProperty(configProperty) + if err != nil { + return err + } + return nil +} + func fetchProperty(name string) ([]byte, error) { cName := C.CString(name) defer C.free(unsafe.Pointer(cName)) @@ -90,3 +103,21 @@ func fetchProperty(name string) ([]byte, error) { // properties are double-NUL-terminated return bytes.TrimRight(C.GoBytes(buf, C.int(size)), "\x00"), nil } + +func deleteProperty(name string) error { + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + ret, errno := C.virtualbox_delete_guest_property(cName) + if ret != C.VINF_SUCCESS { + if ret == C.VERR_GENERAL_FAILURE && errno != nil { + return fmt.Errorf("deleting VirtualBox guest property %q: %w", name, errno) + } + if ret == C.VERR_PERMISSION_DENIED { + return fmt.Errorf("deleting VirtualBox guest property %q: permission denied; is the property read-only?", name) + } + // see + return fmt.Errorf("deleting VirtualBox guest property %q: error %d", name, ret) + } + return nil +} diff --git a/internal/providers/virtualbox/virtualbox.h b/internal/providers/virtualbox/virtualbox.h index 283f623a1..c534a6589 100644 --- a/internal/providers/virtualbox/virtualbox.h +++ b/internal/providers/virtualbox/virtualbox.h @@ -16,5 +16,6 @@ #define IGNITION_VIRTUALBOX_H int virtualbox_get_guest_property(char *name, void **value, size_t *size); +int virtualbox_delete_guest_property(char *name); #endif From b0562e383703507f30f97eb1a277d10e5122d661 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 20 Apr 2022 15:20:25 -0400 Subject: [PATCH 13/13] Add ignition-delete-config.service and ignition-rmcfg symlink Add a systemd service that runs ignition-rmcfg to delete userdata early in first boot on VirtualBox and VMware. This will fail the boot if it runs unsuccessfully, and can be masked in the config if desired. Add /usr/libexec/ignition-rmcfg as a symlink to the Ignition binary in the Dracut module, to save space. Distros will probably want to enable this service by default. Fixes https://github.com/coreos/ignition/issues/1315. --- Makefile | 3 +++ systemd/ignition-delete-config.service | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 systemd/ignition-delete-config.service diff --git a/Makefile b/Makefile index af9a94ad3..d4a2e3188 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,11 @@ install: all install -m 0644 -D -t $(DESTDIR)/usr/lib/dracut/modules.d/$${bn} $$x/*; \ done chmod a+x $(DESTDIR)/usr/lib/dracut/modules.d/*/*.sh $(DESTDIR)/usr/lib/dracut/modules.d/*/*-generator + install -m 0644 -D -t $(DESTDIR)/usr/lib/systemd/system systemd/ignition-delete-config.service install -m 0755 -D -t $(DESTDIR)/usr/lib/dracut/modules.d/30ignition bin/$(GOARCH)/ignition install -m 0755 -D -t $(DESTDIR)/usr/bin bin/$(GOARCH)/ignition-validate + install -m 0755 -d $(DESTDIR)/usr/libexec + ln -sf ../lib/dracut/modules.d/30ignition/ignition $(DESTDIR)/usr/libexec/ignition-rmcfg .PHONY: vendor vendor: diff --git a/systemd/ignition-delete-config.service b/systemd/ignition-delete-config.service new file mode 100644 index 000000000..6e437a30a --- /dev/null +++ b/systemd/ignition-delete-config.service @@ -0,0 +1,25 @@ +[Unit] +Description=Ignition (delete config) +Documentation=https://coreos.github.io/ignition/ + +ConditionFirstBoot=true +ConditionPathExists=/run/ignition.env +ConditionKernelCommandLine=|ignition.platform.id=virtualbox +ConditionKernelCommandLine=|ignition.platform.id=vmware + +DefaultDependencies=no +# Run before any user services to prevent potential config leaks +Before=sysinit.target + +OnFailure=emergency.target +OnFailureJobMode=isolate + +[Service] +Type=oneshot +EnvironmentFile=/run/ignition.env +ExecStart=/usr/libexec/ignition-rmcfg --platform=${PLATFORM_ID} +RemainAfterExit=yes + +[Install] +# Not RequiredBy, since we want to allow the unit to be masked +WantedBy=sysinit.target