pax_global_header00006660000000000000000000000064140140451410014504gustar00rootroot0000000000000052 comment=98c0062a585d4ff0a002b53a6d10ab7e0aa9785c osconfig-20210219.00/000077500000000000000000000000001401404514100137735ustar00rootroot00000000000000osconfig-20210219.00/90-google-osconfig-agent.preset000066400000000000000000000000441401404514100216200ustar00rootroot00000000000000enable google-osconfig-agent.serviceosconfig-20210219.00/CONTRIBUTING.md000066400000000000000000000061001401404514100162210ustar00rootroot00000000000000# How to become a contributor and submit your own code ## Contributor License Agreements We'd love to accept your sample apps and patches! Before we can take them, we have to jump a couple of legal hurdles. Please fill out either the individual or corporate Contributor License Agreement (CLA). * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA] (https://developers.google.com/open-source/cla/individual). * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA] (https://developers.google.com/open-source/cla/corporate). Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. ## Contributing a patch 1. Submit an issue describing your proposed change to the repo in question. 1. The repo owner will respond to your issue promptly. 1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 1. Fork the desired repo, develop and test your code changes. 1. Ensure that your code adheres to the existing style in the sample to which you are contributing. Refer to the [Google Cloud Platform Samples Style Guide] (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the recommended coding standards for this organization. 1. Ensure that your code has an appropriate set of unit tests which all pass. 1. Submit a pull request. ## Contributing a new sample App 1. Submit an issue to the `GoogleCloudPlatform/Template` repo describing your proposed sample app. 1. The Template repo owner will respond to your enhancement issue promptly. Instructional value is the top priority when evaluating new app proposals for this collection of repos. 1. If your proposal is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 1. Create your own repo for your app following this naming convention: * {product}-{app-name}-{language} * products: appengine, compute, storage, bigquery, prediction, cloudsql * example: appengine-guestbook-python * For multi-product apps, concatenate the primary products, like this: compute-appengine-demo-suite-python. * For multi-language apps, concatenate the primary languages like this: appengine-sockets-python-java-go. 1. Clone the `README.md`, `CONTRIB.md` and `LICENSE` files from the GoogleCloudPlatform/Template repo. 1. Ensure that your code adheres to the existing style in the sample to which you are contributing. Refer to the [Google Cloud Platform Samples Style Guide] (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the recommended coding standards for this organization. 1. Ensure that your code has an appropriate set of unit tests which all pass. 1. Submit a request to fork your repo in GoogleCloudPlatform organization via your proposal issue. osconfig-20210219.00/LICENSE000066400000000000000000000261351401404514100150070ustar00rootroot00000000000000 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. osconfig-20210219.00/OWNERS000066400000000000000000000003471401404514100147370ustar00rootroot00000000000000# This file enables automatic assignment of PR reviewers. # See the OWNERS docs at https://go.k8s.io/owners approvers: - adjackura - ryanwe reviewers: - adjackura - ryanwe - iamsubratp - wj-chen - manjunathganga osconfig-20210219.00/README.md000066400000000000000000000012531401404514100152530ustar00rootroot00000000000000# Google OS Config Agent This repository contains the OS Config agent and associated end to end tests. The OS Config agent currently supports the following three main features: - [OS inventory management](https://cloud.google.com/compute/docs/instances/os-inventory-management) - [OS patch management](https://cloud.google.com/compute/docs/os-patch-management) - [OS configuration management](https://cloud.google.com/compute/docs/os-config-management) For instructions on how to install the OS Config agent on a [Compute Engine](https://cloud.google.com/compute) VM instance, see [Installing the OS Config agent](https://cloud.google.com/compute/docs/manage-os#agent-install). osconfig-20210219.00/THIRD_PARTY_LICENSES/000077500000000000000000000000001401404514100171115ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/cloud.google.com/000077500000000000000000000000001401404514100222475ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/cloud.google.com/go/000077500000000000000000000000001401404514100226545ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/cloud.google.com/go/LICENSE000066400000000000000000000261361401404514100236710ustar00rootroot00000000000000 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/cloud.google.com/go/logging/000077500000000000000000000000001401404514100243025ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/cloud.google.com/go/logging/LICENSE000066400000000000000000000261361401404514100253170ustar00rootroot00000000000000 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/cloud.google.com/go/storage/000077500000000000000000000000001401404514100243205ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/cloud.google.com/go/storage/LICENSE000066400000000000000000000261361401404514100253350ustar00rootroot00000000000000 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/000077500000000000000000000000001401404514100211505ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/000077500000000000000000000000001401404514100250605ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-logging-go/000077500000000000000000000000001401404514100302365ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-logging-go/logger/000077500000000000000000000000001401404514100315155ustar00rootroot00000000000000LICENSE000066400000000000000000000261351401404514100324520ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-logging-go/logger 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/osconfig/000077500000000000000000000000001401404514100266675ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/osconfig/LICENSE000066400000000000000000000261351401404514100277030ustar00rootroot00000000000000 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/golang/000077500000000000000000000000001401404514100224175ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/golang/groupcache/000077500000000000000000000000001401404514100245375ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/golang/groupcache/lru/000077500000000000000000000000001401404514100253415ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/golang/groupcache/lru/LICENSE000066400000000000000000000240411401404514100263470ustar00rootroot00000000000000Apache 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: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and 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 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/golang/protobuf/000077500000000000000000000000001401404514100242575ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/golang/protobuf/LICENSE000066400000000000000000000027101401404514100252640ustar00rootroot00000000000000Copyright 2010 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/google/000077500000000000000000000000001401404514100224245ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/google/go-cmp/000077500000000000000000000000001401404514100236065ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/google/go-cmp/cmp/000077500000000000000000000000001401404514100243655ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/google/go-cmp/cmp/LICENSE000066400000000000000000000027071401404514100254000ustar00rootroot00000000000000Copyright (c) 2017 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/googleapis/000077500000000000000000000000001401404514100233015ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/googleapis/gax-go/000077500000000000000000000000001401404514100244635ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/googleapis/gax-go/v2/000077500000000000000000000000001401404514100250125ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/googleapis/gax-go/v2/LICENSE000066400000000000000000000026771401404514100260330ustar00rootroot00000000000000Copyright 2016, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/tarm/000077500000000000000000000000001401404514100221135ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/tarm/serial/000077500000000000000000000000001401404514100233725ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/tarm/serial/LICENSE000066400000000000000000000027071401404514100244050ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/ulikunitz/000077500000000000000000000000001401404514100232065ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/ulikunitz/xz/000077500000000000000000000000001401404514100236475ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/github.com/ulikunitz/xz/LICENSE000066400000000000000000000026471401404514100246650ustar00rootroot00000000000000Copyright (c) 2014-2020 Ulrich Kunitz All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * My name, Ulrich Kunitz, may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/go.opencensus.io/000077500000000000000000000000001401404514100223055ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/go.opencensus.io/LICENSE000066400000000000000000000261351401404514100233210ustar00rootroot00000000000000 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.osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/000077500000000000000000000000001401404514100211465ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/000077500000000000000000000000001401404514100214155ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/crypto/000077500000000000000000000000001401404514100227355ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/crypto/LICENSE000066400000000000000000000027071401404514100237500ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/net/000077500000000000000000000000001401404514100222035ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/net/LICENSE000066400000000000000000000027071401404514100232160ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/oauth2/000077500000000000000000000000001401404514100226175ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/oauth2/LICENSE000066400000000000000000000027071401404514100236320ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/sync/000077500000000000000000000000001401404514100223715ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/sync/semaphore/000077500000000000000000000000001401404514100243545ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/sync/semaphore/LICENSE000066400000000000000000000027071401404514100253670ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/sys/000077500000000000000000000000001401404514100222335ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/sys/LICENSE000066400000000000000000000027071401404514100232460ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/text/000077500000000000000000000000001401404514100224015ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/golang.org/x/text/LICENSE000066400000000000000000000027071401404514100234140ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/000077500000000000000000000000001401404514100224215ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/api/000077500000000000000000000000001401404514100231725ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/api/LICENSE000066400000000000000000000027031401404514100242010ustar00rootroot00000000000000Copyright (c) 2011 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/api/internal/000077500000000000000000000000001401404514100250065ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/api/internal/third_party/000077500000000000000000000000001401404514100273375ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/api/internal/third_party/uritemplates/000077500000000000000000000000001401404514100320555ustar00rootroot00000000000000LICENSE000066400000000000000000000027061401404514100330100ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/api/internal/third_party/uritemplatesCopyright (c) 2013 Joshua Tacoma. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/genproto/000077500000000000000000000000001401404514100242565ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/genproto/LICENSE000066400000000000000000000261361401404514100252730ustar00rootroot00000000000000 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/grpc/000077500000000000000000000000001401404514100233545ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/grpc/LICENSE000066400000000000000000000261361401404514100243710ustar00rootroot00000000000000 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. osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/protobuf/000077500000000000000000000000001401404514100242615ustar00rootroot00000000000000osconfig-20210219.00/THIRD_PARTY_LICENSES/google.golang.org/protobuf/LICENSE000066400000000000000000000027071401404514100252740ustar00rootroot00000000000000Copyright (c) 2018 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER 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. osconfig-20210219.00/agentconfig/000077500000000000000000000000001401404514100162575ustar00rootroot00000000000000osconfig-20210219.00/agentconfig/agentconfig.go000066400000000000000000000444301401404514100210770ustar00rootroot00000000000000// Copyright 2018 Google 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 agentconfig stores and retrieves configuration settings for the OS Config agent. package agentconfig import ( "context" "crypto/sha256" "encoding/json" "flag" "fmt" "io/ioutil" "net" "net/http" "net/url" "os" "runtime" "strconv" "strings" "sync" "time" "cloud.google.com/go/compute/metadata" "github.com/GoogleCloudPlatform/osconfig/clog" "golang.org/x/oauth2/jws" ) const ( // metadataIP is the documented metadata server IP address. metadataIP = "169.254.169.254" // metadataHostEnv is the environment variable specifying the // GCE metadata hostname. metadataHostEnv = "GCE_METADATA_HOST" // InstanceMetadata is the compute metadata URL. InstanceMetadata = "http://metadata.google.internal/computeMetadata/v1/instance" // IdentityTokenPath is the instance identity token path. IdentityTokenPath = "instance/service-accounts/default/identity?audience=osconfig.googleapis.com&format=full" // ReportURL is the guest attributes endpoint. ReportURL = InstanceMetadata + "/guest-attributes" googetRepoDir = "C:/ProgramData/GooGet/repos" googetRepoFilePath = googetRepoDir + "/google_osconfig_managed.repo" zypperRepoDir = "/etc/zypp/repos.d" zypperRepoFilePath = zypperRepoDir + "/google_osconfig_managed.repo" yumRepoDir = "/etc/yum.repos.d" yumRepoFilePath = yumRepoDir + "/google_osconfig_managed.repo" aptRepoDir = "/etc/apt/sources.list.d" aptRepoFilePath = aptRepoDir + "/google_osconfig_managed.list" prodEndpoint = "{zone}-osconfig.googleapis.com:443" osInventoryEnabledDefault = false guestPoliciesEnabledDefault = false taskNotificationEnabledDefault = false debugEnabledDefault = false configDirWindows = `C:\Program Files\Google\OSConfig` configDirLinux = "/etc/osconfig" taskStateFileWindows = configDirWindows + `\osconfig_task.state` taskStateFileLinux = configDirLinux + "/osconfig_task.state" restartFileWindows = configDirWindows + `\osconfig_agent_restart_required` restartFileLinux = configDirLinux + "/osconfig_agent_restart_required" osConfigPollIntervalDefault = 10 osConfigMetadataPollTimeout = 60 ) var ( endpoint = flag.String("endpoint", prodEndpoint, "osconfig endpoint override") debug = flag.Bool("debug", false, "set debug log verbosity") stdout = flag.Bool("stdout", false, "log to stdout") agentConfig = &config{} agentConfigMx sync.RWMutex version string lEtag = &lastEtag{Etag: "0"} // Current supported capabilites for this agent. // These are matched server side to what tasks this agent can // perform. capabilities = []string{"PATCH_GA", "GUEST_POLICY_BETA"} osConfigWatchConfigTimeout = 10 * time.Minute ) type config struct { osInventoryEnabled, guestPoliciesEnabled, taskNotificationEnabled, debugEnabled bool svcEndpoint, googetRepoFilePath, zypperRepoFilePath, yumRepoFilePath, aptRepoFilePath string numericProjectID, osConfigPollInterval int projectID, instanceZone, instanceName, instanceID string } func (c *config) parseFeatures(features string, enabled bool) { for _, f := range strings.Split(features, ",") { f = strings.ToLower(strings.TrimSpace(f)) switch f { case "tasks", "ospatch": // ospatch is the legacy flag c.taskNotificationEnabled = enabled case "guestpolicies", "ospackage": // ospackage is the legacy flag c.guestPoliciesEnabled = enabled case "osinventory": c.osInventoryEnabled = enabled } } } func (c *config) asSha256() string { h := sha256.New() h.Write([]byte(fmt.Sprintf("%v", c))) return fmt.Sprintf("%x", h.Sum(nil)) } func getAgentConfig() config { agentConfigMx.RLock() defer agentConfigMx.RUnlock() return *agentConfig } type lastEtag struct { mu sync.RWMutex Etag string } func (e *lastEtag) set(etag string) { e.mu.Lock() defer e.mu.Unlock() e.Etag = etag } func (e *lastEtag) get() string { e.mu.RLock() defer e.mu.RUnlock() return e.Etag } func parseBool(s string) bool { enabled, err := strconv.ParseBool(s) if err != nil { // Bad entry returns as not enabled. return false } return enabled } type metadataJSON struct { Instance instanceJSON Project projectJSON } type instanceJSON struct { Attributes attributesJSON Zone string Name string ID *json.Number } type projectJSON struct { Attributes attributesJSON ProjectID string NumericProjectID int } type attributesJSON struct { InventoryEnabledOld string `json:"os-inventory-enabled"` InventoryEnabled string `json:"enable-os-inventory"` PreReleaseFeaturesOld string `json:"os-config-enabled-prerelease-features"` PreReleaseFeatures string `json:"osconfig-enabled-prerelease-features"` OSConfigEnabled string `json:"enable-osconfig"` DisabledFeatures string `json:"osconfig-disabled-features"` DebugEnabledOld string `json:"enable-os-config-debug"` LogLevel string `json:"osconfig-log-level"` OSConfigEndpointOld string `json:"os-config-endpoint"` OSConfigEndpoint string `json:"osconfig-endpoint"` PollIntervalOld *json.Number `json:"os-config-poll-interval"` PollInterval *json.Number `json:"osconfig-poll-interval"` } func createConfigFromMetadata(md metadataJSON) *config { old := getAgentConfig() c := &config{ osInventoryEnabled: osInventoryEnabledDefault, guestPoliciesEnabled: guestPoliciesEnabledDefault, taskNotificationEnabled: taskNotificationEnabledDefault, debugEnabled: debugEnabledDefault, svcEndpoint: prodEndpoint, osConfigPollInterval: osConfigPollIntervalDefault, googetRepoFilePath: googetRepoFilePath, zypperRepoFilePath: zypperRepoFilePath, yumRepoFilePath: yumRepoFilePath, aptRepoFilePath: aptRepoFilePath, projectID: old.projectID, numericProjectID: old.numericProjectID, instanceZone: old.instanceZone, instanceName: old.instanceName, instanceID: old.instanceID, } if md.Project.ProjectID != "" { c.projectID = md.Project.ProjectID } if md.Project.NumericProjectID != 0 { c.numericProjectID = md.Project.NumericProjectID } if md.Instance.Zone != "" { c.instanceZone = md.Instance.Zone } if md.Instance.Name != "" { c.instanceName = md.Instance.Name } if md.Instance.ID != nil { c.instanceID = md.Instance.ID.String() } // Check project first then instance as instance metadata overrides project. switch { case md.Project.Attributes.InventoryEnabled != "": c.osInventoryEnabled = parseBool(md.Project.Attributes.InventoryEnabled) case md.Project.Attributes.InventoryEnabledOld != "": c.osInventoryEnabled = parseBool(md.Project.Attributes.InventoryEnabledOld) } c.parseFeatures(md.Project.Attributes.PreReleaseFeaturesOld, true) c.parseFeatures(md.Project.Attributes.PreReleaseFeatures, true) if md.Project.Attributes.OSConfigEnabled != "" { e := parseBool(md.Project.Attributes.OSConfigEnabled) c.taskNotificationEnabled = e c.guestPoliciesEnabled = e c.osInventoryEnabled = e } c.parseFeatures(md.Project.Attributes.DisabledFeatures, false) switch { case md.Instance.Attributes.InventoryEnabled != "": c.osInventoryEnabled = parseBool(md.Instance.Attributes.InventoryEnabled) case md.Instance.Attributes.InventoryEnabledOld != "": c.osInventoryEnabled = parseBool(md.Instance.Attributes.InventoryEnabledOld) } c.parseFeatures(md.Instance.Attributes.PreReleaseFeaturesOld, true) c.parseFeatures(md.Instance.Attributes.PreReleaseFeatures, true) if md.Instance.Attributes.OSConfigEnabled != "" { e := parseBool(md.Instance.Attributes.OSConfigEnabled) c.taskNotificationEnabled = e c.guestPoliciesEnabled = e c.osInventoryEnabled = e } c.parseFeatures(md.Instance.Attributes.DisabledFeatures, false) switch { case md.Project.Attributes.PollInterval != nil: if val, err := md.Project.Attributes.PollInterval.Int64(); err == nil { c.osConfigPollInterval = int(val) } case md.Project.Attributes.PollIntervalOld != nil: if val, err := md.Project.Attributes.PollIntervalOld.Int64(); err == nil { c.osConfigPollInterval = int(val) } } switch { case md.Instance.Attributes.PollInterval != nil: if val, err := md.Instance.Attributes.PollInterval.Int64(); err == nil { c.osConfigPollInterval = int(val) } case md.Instance.Attributes.PollIntervalOld != nil: if val, err := md.Instance.Attributes.PollInterval.Int64(); err == nil { c.osConfigPollInterval = int(val) } } switch { case md.Project.Attributes.DebugEnabledOld != "": c.debugEnabled = parseBool(md.Project.Attributes.DebugEnabledOld) case md.Instance.Attributes.DebugEnabledOld != "": c.debugEnabled = parseBool(md.Instance.Attributes.DebugEnabledOld) } switch strings.ToLower(md.Project.Attributes.LogLevel) { case "debug": c.debugEnabled = true case "info": c.debugEnabled = false } switch strings.ToLower(md.Instance.Attributes.LogLevel) { case "debug": c.debugEnabled = true case "info": c.debugEnabled = false } // Flags take precedence over metadata. if *debug { c.debugEnabled = true } setSVCEndpoint(md, c) return c } func setSVCEndpoint(md metadataJSON, c *config) { switch { case *endpoint != prodEndpoint: c.svcEndpoint = *endpoint case md.Instance.Attributes.OSConfigEndpoint != "": c.svcEndpoint = md.Instance.Attributes.OSConfigEndpoint case md.Instance.Attributes.OSConfigEndpointOld != "": c.svcEndpoint = md.Instance.Attributes.OSConfigEndpointOld case md.Project.Attributes.OSConfigEndpoint != "": c.svcEndpoint = md.Project.Attributes.OSConfigEndpoint case md.Project.Attributes.OSConfigEndpointOld != "": c.svcEndpoint = md.Project.Attributes.OSConfigEndpointOld } // Example instanceZone: projects/123456/zones/us-west1-b parts := strings.Split(c.instanceZone, "/") zone := parts[len(parts)-1] c.svcEndpoint = strings.ReplaceAll(c.svcEndpoint, "{zone}", zone) } func formatMetadataError(err error) error { if urlErr, ok := err.(*url.Error); ok { if _, ok := urlErr.Err.(*net.DNSError); ok { return fmt.Errorf("DNS error when requesting metadata, check DNS settings and ensure metadata.google.internal is setup in your hosts file: %w", err) } if _, ok := urlErr.Err.(*net.OpError); ok { return fmt.Errorf("network error when requesting metadata, make sure your instance has an active network and can reach the metadata server: %w", err) } } return err } func getMetadata(suffix string) ([]byte, string, error) { host := os.Getenv(metadataHostEnv) if host == "" { // Using 169.254.169.254 instead of "metadata" here because Go // binaries built with the "netgo" tag and without cgo won't // know the search suffix for "metadata" is // ".google.internal", and this IP address is documented as // being stable anyway. host = metadataIP } computeMetadataURL := "http://" + host + "/computeMetadata/v1/" + suffix req, err := http.NewRequest("GET", computeMetadataURL, nil) if err != nil { return nil, "", err } req.Header.Add("Metadata-Flavor", "Google") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, "", err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, "", err } if resp.StatusCode != http.StatusOK { return nil, "", err } all, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, "", err } return all, resp.Header.Get("Etag"), nil } // WatchConfig looks for changes in metadata keys. Upon receiving successful response, // it create a new agent config. func WatchConfig(ctx context.Context) error { var md []byte var webError error // Max watch time, after this WatchConfig will return. timeout := time.After(osConfigWatchConfigTimeout) // Min watch loop time. loopTicker := time.NewTicker(5 * time.Second) defer loopTicker.Stop() eTag := lEtag.get() webErrorCount := 0 unmarshalErrorCount := 0 for { md, eTag, webError = getMetadata(fmt.Sprintf("?recursive=true&alt=json&wait_for_change=true&last_etag=%s&timeout_sec=%d", lEtag.get(), osConfigMetadataPollTimeout)) if webError == nil && eTag != lEtag.get() { var metadataConfig metadataJSON if err := json.Unmarshal(md, &metadataConfig); err != nil { // Try up to three times (with 5s sleep) to get and unmarshal metadata. // Most unmarshal errors are transient read issues with the metadata server // so we should retry without logging the error. if unmarshalErrorCount >= 3 { return err } unmarshalErrorCount++ select { case <-timeout: return err case <-ctx.Done(): return nil case <-loopTicker.C: continue } } unmarshalErrorCount = 0 lEtag.set(eTag) newAgentConfig := createConfigFromMetadata(metadataConfig) agentConfigMx.Lock() if agentConfig.asSha256() != newAgentConfig.asSha256() { agentConfig = newAgentConfig agentConfigMx.Unlock() break } agentConfigMx.Unlock() } // Try up to 12 times (60s) to wait for slow network initialization, after // that resort to using defaults and returning the error. if webError != nil { if webErrorCount == 12 { return formatMetadataError(webError) } webErrorCount++ } select { case <-timeout: return webError case <-ctx.Done(): return nil case <-loopTicker.C: continue } } return webError } // LogFeatures logs the osconfig feature status. func LogFeatures(ctx context.Context) { clog.Infof(ctx, "OSConfig enabled features status:{GuestPolicies: %t, OSInventory: %t, PatchManagement: %t}.", GuestPoliciesEnabled(), OSInventoryEnabled(), TaskNotificationEnabled()) } // SvcPollInterval returns the frequency to poll the service. func SvcPollInterval() time.Duration { return time.Duration(getAgentConfig().osConfigPollInterval) * time.Minute } // SerialLogPort is the serial port to log to. func SerialLogPort() string { if runtime.GOOS == "windows" { return "COM1" } // Don't write directly to the serial port on Linux as syslog already writes there. return "" } // Debug sets the debug log verbosity. func Debug() bool { return *debug || getAgentConfig().debugEnabled } // Stdout flag. func Stdout() bool { return *stdout } // SvcEndpoint is the OS Config service endpoint. func SvcEndpoint() string { return getAgentConfig().svcEndpoint } // ZypperRepoDir is the location of the zypper repo files. func ZypperRepoDir() string { return zypperRepoDir } // ZypperRepoFilePath is the location where the zypper repo file will be created. func ZypperRepoFilePath() string { return getAgentConfig().zypperRepoFilePath } // YumRepoDir is the location of the yum repo files. func YumRepoDir() string { return yumRepoDir } // YumRepoFilePath is the location where the zypper repo file will be created. func YumRepoFilePath() string { return getAgentConfig().yumRepoFilePath } // AptRepoDir is the location of the apt repo files. func AptRepoDir() string { return aptRepoDir } // AptRepoFilePath is the location where the zypper repo file will be created. func AptRepoFilePath() string { return getAgentConfig().aptRepoFilePath } // GooGetRepoDir is the location of the googet repo files. func GooGetRepoDir() string { return googetRepoDir } // GooGetRepoFilePath is the location where the googet repo file will be created. func GooGetRepoFilePath() string { return getAgentConfig().googetRepoFilePath } // OSInventoryEnabled indicates whether OSInventory should be enabled. func OSInventoryEnabled() bool { return getAgentConfig().osInventoryEnabled } // GuestPoliciesEnabled indicates whether GuestPolicies should be enabled. func GuestPoliciesEnabled() bool { return getAgentConfig().guestPoliciesEnabled } // TaskNotificationEnabled indicates whether TaskNotification should be enabled. func TaskNotificationEnabled() bool { return getAgentConfig().taskNotificationEnabled } // Instance is the URI of the instance the agent is running on. func Instance() string { // Zone contains 'projects/project-id/zones' as a prefix. return fmt.Sprintf("%s/instances/%s", Zone(), Name()) } // NumericProjectID is the numeric project ID of the instance. func NumericProjectID() int { return getAgentConfig().numericProjectID } // ProjectID is the project ID of the instance. func ProjectID() string { return getAgentConfig().projectID } // Zone is the zone the instance is running in. func Zone() string { return getAgentConfig().instanceZone } // Name is the instance name. func Name() string { return getAgentConfig().instanceName } // ID is the instance id. func ID() string { return getAgentConfig().instanceID } type idToken struct { raw string exp *time.Time sync.Mutex } func (t *idToken) get() error { data, err := metadata.Get(IdentityTokenPath) if err != nil { return fmt.Errorf("error getting token from metadata: %w", err) } cs, err := jws.Decode(data) if err != nil { return err } t.raw = data exp := time.Unix(cs.Exp, 0) t.exp = &exp return nil } var identity idToken // IDToken is the instance id token. func IDToken() (string, error) { identity.Lock() defer identity.Unlock() // Rerequest token if expiry is within 10 minutes. if identity.exp == nil || time.Now().After(identity.exp.Add(-10*time.Minute)) { if err := identity.get(); err != nil { return "", err } } return identity.raw, nil } // Version is the agent version. func Version() string { return version } // SetVersion sets the agent version. func SetVersion(v string) { version = v } // Capabilities returns the agents capabilities. func Capabilities() []string { return capabilities } // TaskStateFile is the location of the task state file. func TaskStateFile() string { if runtime.GOOS == "windows" { return taskStateFileWindows } return taskStateFileLinux } // RestartFile is the location of the restart required file. func RestartFile() string { if runtime.GOOS == "windows" { return restartFileWindows } return restartFileLinux } osconfig-20210219.00/agentconfig/agentconfig_test.go000066400000000000000000000204561401404514100221400ustar00rootroot00000000000000// Copyright 2019 Google 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 agentconfig import ( "context" "fmt" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "runtime" "strings" "testing" "time" ) func TestWatchConfig(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `{"project":{"numericProjectID":12345,"projectId":"projectId","attributes":{"osconfig-endpoint":"bad!!1","enable-os-inventory":"false"}},"instance":{"id":12345,"name":"name","zone":"zone","attributes":{"osconfig-endpoint":"SvcEndpoint","enable-os-inventory":"1","enable-os-config-debug":"true","osconfig-enabled-prerelease-features":"ospackage,ospatch", "osconfig-poll-interval":"3"}}}`) })) defer ts.Close() if err := os.Setenv("GCE_METADATA_HOST", strings.Trim(ts.URL, "http://")); err != nil { t.Fatalf("Error running os.Setenv: %v", err) } if err := WatchConfig(context.Background()); err != nil { t.Fatalf("Error running WatchConfig: %v", err) } testsString := []struct { desc string op func() string want string }{ {"SvcEndpoint", SvcEndpoint, "SvcEndpoint"}, {"Instance", Instance, "zone/instances/name"}, {"ID", ID, "12345"}, {"ProjectID", ProjectID, "projectId"}, {"Zone", Zone, "zone"}, {"Name", Name, "name"}, } for _, tt := range testsString { if tt.op() != tt.want { t.Errorf("%q: got(%q) != want(%q)", tt.desc, tt.op(), tt.want) } } testsBool := []struct { desc string op func() bool want bool }{ {"osinventory should be enabled (proj disabled, inst enabled)", OSInventoryEnabled, true}, {"taskNotification should be enabled (inst enabled)", TaskNotificationEnabled, true}, {"guestpolicies should be enabled (proj enabled)", GuestPoliciesEnabled, true}, {"debugenabled should be true (proj disabled, inst enabled)", Debug, true}, } for _, tt := range testsBool { if tt.op() != tt.want { t.Errorf("%q: got(%t) != want(%t)", tt.desc, tt.op(), tt.want) } } if SvcPollInterval().Minutes() != float64(3) { t.Errorf("Default poll interval: got(%f) != want(%d)", SvcPollInterval().Minutes(), 3) } if NumericProjectID() != 12345 { t.Errorf("NumericProjectID: got(%v) != want(%d)", NumericProjectID(), 12345) } if Instance() != "zone/instances/name" { t.Errorf("zone: got(%s) != want(%s)", Instance(), "zone/instances/name") } } func TestSetConfigEnabled(t *testing.T) { var request int ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch request { case 0: w.Header().Set("Etag", "etag-0") fmt.Fprintln(w, `{"project":{"attributes":{"enable-osconfig":"false"}},"instance":{"attributes":{"enable-osconfig":"false"}}}`) case 1: w.Header().Set("Etag", "etag-1") fmt.Fprintln(w, `{"project":{"attributes":{"enable-osconfig":"false"}},"instance":{"attributes":{"enable-osconfig":"true"}}}`) case 2: w.Header().Set("Etag", "etag-2") fmt.Fprintln(w, `{"project":{"attributes":{"enable-osconfig":"false"}},"instance":{"attributes":{"enable-osconfig":"false"}}}`) case 3: w.Header().Set("Etag", "etag-3") fmt.Fprintln(w, `{"project":{"attributes":{"enable-osconfig":"true","osconfig-disabled-features":"osinventory"}}}`) } })) defer ts.Close() if err := os.Setenv("GCE_METADATA_HOST", strings.Trim(ts.URL, "http://")); err != nil { t.Fatalf("Error running os.Setenv: %v", err) } for i, want := range []bool{false, true, false} { request = i if err := WatchConfig(context.Background()); err != nil { t.Fatalf("Error running SetConfig: %v", err) } testsBool := []struct { desc string op func() bool }{ {"OSInventoryEnabled", OSInventoryEnabled}, {"TaskNotificationEnabled", TaskNotificationEnabled}, {"GuestPoliciesEnabled", GuestPoliciesEnabled}, } for _, tt := range testsBool { if tt.op() != want { t.Errorf("Request %d: %s: got(%t) != want(%t)", request, tt.desc, tt.op(), want) } } } request = 3 if err := WatchConfig(context.Background()); err != nil { t.Fatalf("Error running SetConfig: %v", err) } testsBool := []struct { desc string op func() bool want bool }{ {"OSInventoryEnabled", OSInventoryEnabled, false}, {"TaskNotificationEnabled", TaskNotificationEnabled, true}, {"GuestPoliciesEnabled", GuestPoliciesEnabled, true}, } for _, tt := range testsBool { if tt.op() != tt.want { t.Errorf("%s: got(%t) != want(%t)", tt.desc, tt.op(), tt.want) } } } func TestSetConfigDefaultValues(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Etag", "sample-etag") // we always get zone value in instance metadata. fmt.Fprintln(w, `{"instance": {"zone": "fake-zone"}}`) })) defer ts.Close() if err := os.Setenv("GCE_METADATA_HOST", strings.Trim(ts.URL, "http://")); err != nil { t.Fatalf("Error running os.Setenv: %v", err) } if err := WatchConfig(context.Background()); err != nil { t.Fatalf("Error running SetConfig: %v", err) } testsString := []struct { op func() string want string }{ {AptRepoFilePath, aptRepoFilePath}, {YumRepoFilePath, yumRepoFilePath}, {ZypperRepoFilePath, zypperRepoFilePath}, {GooGetRepoFilePath, googetRepoFilePath}, } for _, tt := range testsString { if tt.op() != tt.want { f := filepath.Base(runtime.FuncForPC(reflect.ValueOf(tt.op).Pointer()).Name()) t.Errorf("%q: got(%q) != want(%q)", f, tt.op(), tt.want) } } testsBool := []struct { op func() bool want bool }{ {OSInventoryEnabled, osInventoryEnabledDefault}, {TaskNotificationEnabled, taskNotificationEnabledDefault}, {GuestPoliciesEnabled, guestPoliciesEnabledDefault}, {Debug, debugEnabledDefault}, } for _, tt := range testsBool { if tt.op() != tt.want { f := filepath.Base(runtime.FuncForPC(reflect.ValueOf(tt.op).Pointer()).Name()) t.Errorf("%q: got(%t) != want(%t)", f, tt.op(), tt.want) } } if SvcPollInterval().Minutes() != float64(osConfigPollIntervalDefault) { t.Errorf("Default poll interval: got(%f) != want(%d)", SvcPollInterval().Minutes(), osConfigPollIntervalDefault) } expectedEndpoint := "fake-zone-osconfig.googleapis.com:443" if SvcEndpoint() != expectedEndpoint { t.Errorf("Default endpoint: got(%s) != want(%s)", SvcEndpoint(), expectedEndpoint) } } func TestVersion(t *testing.T) { if Version() != "" { t.Errorf("Unexpected version %q, want \"\"", Version()) } var v = "1" SetVersion(v) if Version() != v { t.Errorf("Unexpected version %q, want %q", Version(), v) } } func TestSvcEndpoint(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Etag", "sametag") // we always get zone value in instance metadata. fmt.Fprintln(w, `{"instance": {"id": 12345,"name": "name","zone": "fakezone","attributes": {"osconfig-endpoint": "{zone}-dev.osconfig.googleapis.com"}}}`) })) defer ts.Close() if err := os.Setenv("GCE_METADATA_HOST", strings.Trim(ts.URL, "http://")); err != nil { t.Fatalf("Error running os.Setenv: %v", err) } if err := WatchConfig(context.Background()); err != nil { t.Fatalf("Error running SetConfig: %v", err) } expectedSvcEndpoint := "fakezone-dev.osconfig.googleapis.com" if SvcEndpoint() != expectedSvcEndpoint { t.Errorf("Default endpoint: got(%s) != want(%s)", SvcEndpoint(), expectedSvcEndpoint) } } func TestSetConfigError(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { })) defer ts.Close() if err := os.Setenv("GCE_METADATA_HOST", strings.Trim(ts.URL, "http://")); err != nil { t.Fatalf("Error running os.Setenv: %v", err) } osConfigWatchConfigTimeout = 1 * time.Millisecond if err := WatchConfig(context.Background()); err == nil || !strings.Contains(err.Error(), "unexpected end of JSON input") { t.Errorf("Unexpected output %+v", err) } } osconfig-20210219.00/agentendpoint/000077500000000000000000000000001401404514100166325ustar00rootroot00000000000000osconfig-20210219.00/agentendpoint/agentendpoint.go000066400000000000000000000302131401404514100220170ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint connects to the osconfig agentendpoint api. package agentendpoint import ( "bytes" "context" "crypto/sha256" "encoding/hex" "errors" "fmt" "io" "sync" "time" "cloud.google.com/go/compute/metadata" agentendpoint "cloud.google.com/go/osconfig/agentendpoint/apiv1" "github.com/GoogleCloudPlatform/osconfig/agentconfig" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/retryutil" "github.com/GoogleCloudPlatform/osconfig/tasker" "github.com/GoogleCloudPlatform/osconfig/util" "google.golang.org/api/option" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) const apiRetrySec = 600 var ( errServerCancel = errors.New("task canceled by server") errServiceNotEnabled = errors.New("service is not enabled for this project") errResourceExhausted = errors.New("ResourceExhausted") taskStateFile = agentconfig.TaskStateFile() sameStateTimeWindow = -5 * time.Second ) // Client is a an agentendpoint client. type Client struct { raw *agentendpoint.Client cancel context.CancelFunc noti chan struct{} closed bool mx sync.Mutex } // NewClient a new agentendpoint Client. func NewClient(ctx context.Context) (*Client, error) { opts := []option.ClientOption{ option.WithoutAuthentication(), // Do not use oauth. option.WithGRPCDialOption(grpc.WithTransportCredentials(credentials.NewTLS(nil))), // Because we disabled Auth we need to specifically enable TLS. option.WithEndpoint(agentconfig.SvcEndpoint()), } clog.Debugf(ctx, "Creating new agentendpoint client.") c, err := agentendpoint.NewClient(ctx, opts...) if err != nil { return nil, err } return &Client{raw: c, noti: make(chan struct{}, 1)}, nil } // Close cancels WaitForTaskNotification and closes the underlying ClientConn. func (c *Client) Close() error { // Lock so nothing can use the client while we are closing. c.mx.Lock() if c.cancel != nil { c.cancel() } c.closed = true return c.raw.Close() } // Closed reports whether the Client has been closed. func (c *Client) Closed() bool { return c.closed } // RegisterAgent calls RegisterAgent discarding the response. func (c *Client) RegisterAgent(ctx context.Context) error { token, err := agentconfig.IDToken() if err != nil { return err } req := &agentendpointpb.RegisterAgentRequest{AgentVersion: agentconfig.Version(), SupportedCapabilities: agentconfig.Capabilities()} clog.Debugf(ctx, "Calling RegisterAgent with request:\n%s", util.PrettyFmt(req)) req.InstanceIdToken = token _, err = c.raw.RegisterAgent(ctx, req) return err } // reportInventory calls ReportInventory with the provided inventory. func (c *Client) reportInventory(ctx context.Context, inventory *agentendpointpb.Inventory, reportFull bool) (*agentendpointpb.ReportInventoryResponse, error) { token, err := agentconfig.IDToken() if err != nil { return nil, err } hash := sha256.New() b, err := proto.Marshal(inventory) if err != nil { return nil, err } io.Copy(hash, bytes.NewReader(b)) checksum := hex.EncodeToString(hash.Sum(nil)) req := &agentendpointpb.ReportInventoryRequest{InventoryChecksum: checksum} if reportFull { req = &agentendpointpb.ReportInventoryRequest{InventoryChecksum: checksum, Inventory: inventory} } clog.Debugf(ctx, "Calling ReportInventory with request:\n%s", util.PrettyFmt(req)) req.InstanceIdToken = token // Additional logging for verifications in e2e tests. payloadSummary := fmt.Sprintf("hostname %s, short name %s, %d installed packages, %d available packages", inventory.OsInfo.Hostname, inventory.OsInfo.ShortName, len(inventory.InstalledPackages), len(inventory.AvailablePackages)) clog.Debugf(ctx, "Calling ReportInventory with request containing %s", payloadSummary) return c.raw.ReportInventory(ctx, req) } func (c *Client) startNextTask(ctx context.Context) (res *agentendpointpb.StartNextTaskResponse, err error) { token, err := agentconfig.IDToken() if err != nil { return nil, err } req := &agentendpointpb.StartNextTaskRequest{} clog.Debugf(ctx, "Calling StartNextTask with request:\n%s", util.PrettyFmt(req)) req.InstanceIdToken = token if err := retryutil.RetryAPICall(ctx, apiRetrySec*time.Second, "StartNextTask", func() error { res, err = c.raw.StartNextTask(ctx, req) if err != nil { return err } clog.Debugf(ctx, "StartNextTask response:\n%s", util.PrettyFmt(res)) return nil }); err != nil { return nil, fmt.Errorf("error calling StartNextTask: %w", err) } return res, nil } func (c *Client) reportTaskProgress(ctx context.Context, req *agentendpointpb.ReportTaskProgressRequest) (res *agentendpointpb.ReportTaskProgressResponse, err error) { token, err := agentconfig.IDToken() if err != nil { return nil, err } clog.Debugf(ctx, "Calling ReportTaskProgress with request:\n%s", util.PrettyFmt(req)) req.InstanceIdToken = token if err := retryutil.RetryAPICall(ctx, apiRetrySec*time.Second, "ReportTaskProgress", func() error { res, err = c.raw.ReportTaskProgress(ctx, req) if err != nil { return err } clog.Debugf(ctx, "ReportTaskProgress response:\n%s", util.PrettyFmt(res)) return nil }); err != nil { return nil, fmt.Errorf("error calling ReportTaskProgress: %w", err) } return res, nil } func (c *Client) reportTaskComplete(ctx context.Context, req *agentendpointpb.ReportTaskCompleteRequest) error { token, err := agentconfig.IDToken() if err != nil { return err } clog.Debugf(ctx, "Calling ReportTaskComplete with request:\n%s", util.PrettyFmt(req)) req.InstanceIdToken = token if err := retryutil.RetryAPICall(ctx, apiRetrySec*time.Second, "ReportTaskComplete", func() error { res, err := c.raw.ReportTaskComplete(ctx, req) if err != nil { return err } clog.Debugf(ctx, "ReportTaskComplete response:\n%s", util.PrettyFmt(res)) return nil }); err != nil { return fmt.Errorf("error calling ReportTaskComplete: %w", err) } return nil } func (c *Client) runTask(ctx context.Context) { clog.Debugf(ctx, "Beginning run task loop.") for { res, err := c.startNextTask(ctx) if err != nil { clog.Errorf(ctx, "Error running StartNextTask, cannot continue: %v", err) return } task := res.GetTask() if task == nil { clog.Debugf(ctx, "No task to run, ending run task loop.") return } clog.Debugf(ctx, "Received task: %s.", task.GetTaskType()) switch task.GetTaskType() { case agentendpointpb.TaskType_APPLY_PATCHES: if err := c.RunApplyPatches(ctx, task); err != nil { clog.Errorf(ctx, "Error running TaskType_APPLY_PATCHES: %v", err) } case agentendpointpb.TaskType_EXEC_STEP_TASK: if err := c.RunExecStep(ctx, task); err != nil { clog.Errorf(ctx, "Error running TaskType_EXEC_STEP_TASK: %v", err) } case agentendpointpb.TaskType_APPLY_CONFIG_TASK: if err := c.RunApplyConfig(ctx, task); err != nil { clog.Errorf(ctx, "Error running TaskType_APPLY_CONFIG_TASK: %v", err) } default: clog.Errorf(ctx, "Unknown task type: %v", task.GetTaskType()) } } } func (c *Client) handleStream(ctx context.Context, stream agentendpointpb.AgentEndpointService_ReceiveTaskNotificationClient) error { for { clog.Debugf(ctx, "Waiting on ReceiveTaskNotification stream Recv().") if _, err := stream.Recv(); err != nil { // Return on any stream error, even a close, the caller will simply // reconnect the stream as needed. return err } clog.Debugf(ctx, "Received task notification.") // Only queue up one notifcation at a time. We should only ever // have one active task being worked on and one in the queue. select { case <-ctx.Done(): // We have been canceled. return nil case c.noti <- struct{}{}: tasker.Enqueue(ctx, "TaskNotification", func() { // We lock so that this task will complete before the client can get canceled. c.mx.Lock() defer c.mx.Unlock() select { case <-ctx.Done(): // We have been canceled. default: // Take this task off the notification queue so another can be // queued up. <-c.noti c.runTask(ctx) } }) default: // Ignore the notificaction as we already have one queued. } } } func (c *Client) receiveTaskNotification(ctx context.Context) (agentendpointpb.AgentEndpointService_ReceiveTaskNotificationClient, error) { req := &agentendpointpb.ReceiveTaskNotificationRequest{ AgentVersion: agentconfig.Version(), } token, err := agentconfig.IDToken() if err != nil { return nil, fmt.Errorf("error fetching Instance IDToken: %w", err) } clog.Debugf(ctx, "Calling ReceiveTaskNotification with request:\n%s", util.PrettyFmt(req)) req.InstanceIdToken = token return c.raw.ReceiveTaskNotification(ctx, req) } func (c *Client) loadTaskFromState(ctx context.Context) error { st, err := loadState(taskStateFile) if err != nil { return fmt.Errorf("loadState error: %w", err) } if st != nil && st.PatchTask != nil { st.PatchTask.client = c st.PatchTask.state = st tasker.Enqueue(ctx, "PatchRun", func() { st.PatchTask.run(ctx) }) } return nil } func (c *Client) waitForTask(ctx context.Context) error { stream, err := c.receiveTaskNotification(ctx) if err != nil { return err } err = c.handleStream(ctx, stream) if err == io.EOF { // Server closed the stream indication we should reconnect. return nil } if s, ok := status.FromError(err); ok { switch s.Code() { case codes.PermissionDenied: // Service is not enabled for this project. return errServiceNotEnabled case codes.ResourceExhausted: return errResourceExhausted } } return err } // WaitForTaskNotification waits for and acts on any task notification until the Client is closed. // Multiple calls to WaitForTaskNotification will not create new watchers. func (c *Client) WaitForTaskNotification(ctx context.Context) { c.mx.Lock() defer c.mx.Unlock() if c.cancel != nil { // WaitForTaskNotification is already running on this client. return } clog.Debugf(ctx, "Running WaitForTaskNotification") ctx, c.cancel = context.WithCancel(ctx) clog.Debugf(ctx, "Checking local state file for saved task.") if err := c.loadTaskFromState(ctx); err != nil { clog.Errorf(ctx, err.Error()) } clog.Debugf(ctx, "Setting up ReceiveTaskNotification stream watcher.") go func() { var resourceExhausted int var errs int var sleep time.Duration for { select { case <-ctx.Done(): // We have been canceled. clog.Debugf(ctx, "Disabling WaitForTaskNotification") return default: } if err := c.waitForTask(ctx); err != nil { if errors.Is(err, errServiceNotEnabled) { // Service is disabled, close this client and return. clog.Warningf(ctx, "OSConfig Service is disabled.") c.Close() return } var ndr *metadata.NotDefinedError if errors.As(err, &ndr) { // No service account setup for this instance, close this client and return. clog.Warningf(ctx, "No service account set for instance.") c.Close() return } if errors.Is(err, errResourceExhausted) { resourceExhausted++ sleep = retryutil.RetrySleep(resourceExhausted, 5) } else { // Retry any other errors with a modest backoff. Only retry up to 10 // times, at that point return, the client will be recreated during the next // cycle. errs++ clog.Warningf(ctx, "Error waiting for task (attempt %d of 10): %v", errs, err) resourceExhausted = 0 if errs > 10 { c.Close() return } sleep = retryutil.RetrySleep(errs, 0) } time.Sleep(sleep) continue } errs = 0 } }() } osconfig-20210219.00/agentendpoint/agentendpoint_beta.go000066400000000000000000000067341401404514100230250ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint import ( "context" "fmt" "sync" "time" agentendpoint "cloud.google.com/go/osconfig/agentendpoint/apiv1beta" "github.com/GoogleCloudPlatform/osconfig/agentconfig" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/osinfo" "github.com/GoogleCloudPlatform/osconfig/retryutil" "github.com/GoogleCloudPlatform/osconfig/util" "google.golang.org/api/option" "google.golang.org/grpc" "google.golang.org/grpc/credentials" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) // BetaClient is a an agentendpoint client. type BetaClient struct { raw *agentendpoint.Client cancel context.CancelFunc noti chan struct{} closed bool mx sync.Mutex } // NewBetaClient a new agentendpoint Client. func NewBetaClient(ctx context.Context) (*BetaClient, error) { opts := []option.ClientOption{ option.WithoutAuthentication(), // Do not use oauth. option.WithGRPCDialOption(grpc.WithTransportCredentials(credentials.NewTLS(nil))), // Because we disabled Auth we need to specifically enable TLS. option.WithEndpoint(agentconfig.SvcEndpoint()), } clog.Debugf(ctx, "Creating new agentendpoint beta client.") c, err := agentendpoint.NewClient(ctx, opts...) if err != nil { return nil, err } return &BetaClient{raw: c, noti: make(chan struct{}, 1)}, nil } // Close cancels WaitForTaskNotification and closes the underlying ClientConn. func (c *BetaClient) Close() error { // Lock so nothing can use the client while we are closing. c.mx.Lock() if c.cancel != nil { c.cancel() } c.closed = true return c.raw.Close() } // Closed reports whether the Client has been closed. func (c *BetaClient) Closed() bool { return c.closed } // LookupEffectiveGuestPolicies calls the agentendpoint service LookupEffectiveGuestPolicies. func (c *BetaClient) LookupEffectiveGuestPolicies(ctx context.Context) (res *agentendpointpb.EffectiveGuestPolicy, err error) { info, err := osinfo.Get() if err != nil { return nil, err } req := &agentendpointpb.LookupEffectiveGuestPolicyRequest{ OsShortName: info.ShortName, OsVersion: info.Version, OsArchitecture: info.Architecture, } token, err := agentconfig.IDToken() if err != nil { return nil, err } clog.Debugf(ctx, "Calling LookupEffectiveGuestPolicies with request:\n%s", util.PrettyFmt(req)) req.InstanceIdToken = token // Only retry up to 30s for LookupEffectiveGuestPolicies in order to not hang up local configs. if err := retryutil.RetryAPICall(ctx, 30*time.Second, "LookupEffectiveGuestPolicies", func() error { res, err = c.raw.LookupEffectiveGuestPolicy(ctx, req) if err != nil { return err } clog.Debugf(ctx, "LookupEffectiveGuestPolicies response:\n%s", util.PrettyFmt(res)) return nil }); err != nil { return nil, fmt.Errorf("error calling LookupEffectiveGuestPolicies: %w", err) } return res, nil } osconfig-20210219.00/agentendpoint/agentendpoint_test.go000066400000000000000000000263051401404514100230650ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint import ( "context" "crypto/rand" "crypto/rsa" "errors" "fmt" "io" "io/ioutil" "log" "net" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" agentendpoint "cloud.google.com/go/osconfig/agentendpoint/apiv1" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "golang.org/x/oauth2/jws" "google.golang.org/api/option" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) var testIDToken string func TestMain(m *testing.M) { cs := &jws.ClaimSet{ Exp: time.Now().Add(1 * time.Hour).Unix(), } key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { fmt.Printf("Error creating rsa key: %v", err) os.Exit(1) } testIDToken, err = jws.Encode(nil, cs, key) if err != nil { fmt.Printf("Error creating jwt token: %v", err) os.Exit(1) } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, testIDToken) })) if err := os.Setenv("GCE_METADATA_HOST", strings.Trim(ts.URL, "http://")); err != nil { fmt.Printf("Error running os.Setenv: %v", err) os.Exit(1) } opts := logger.LogOpts{LoggerName: "OSConfigAgent", Debug: true, Writers: []io.Writer{os.Stdout}} logger.Init(context.Background(), opts) out := m.Run() ts.Close() os.Exit(out) } const bufSize = 1024 * 1024 type testClient struct { client *Client s *grpc.Server } func (c *testClient) close() { c.client.Close() c.s.Stop() } func newTestClient(ctx context.Context, srv agentendpointpb.AgentEndpointServiceServer) (*testClient, error) { lis := bufconn.Listen(bufSize) s := grpc.NewServer() agentendpointpb.RegisterAgentEndpointServiceServer(s, srv) go func() { if err := s.Serve(lis); err != nil { log.Fatalf("Server exited with error: %v", err) } }() var bufDialer = func(string, time.Duration) (net.Conn, error) { return lis.Dial() } conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithDialer(bufDialer), grpc.WithInsecure()) if err != nil { return nil, err } client, err := agentendpoint.NewClient(ctx, option.WithGRPCConn(conn)) if err != nil { return nil, err } return &testClient{ client: &Client{raw: client, noti: make(chan struct{}, 1)}, s: s, }, nil } type agentEndpointServiceTestServer struct { streamClose chan struct{} streamSend chan struct{} permissionError chan struct{} taskStart bool execTaskProgress bool patchTaskProgress bool applyConfigTaskProgress bool execTaskComplete bool patchTaskComplete bool applyConfigTaskComplete bool runTaskIDs []string } func newAgentEndpointServiceTestServer() *agentEndpointServiceTestServer { return &agentEndpointServiceTestServer{ streamClose: make(chan struct{}, 1), streamSend: make(chan struct{}, 1), permissionError: make(chan struct{}, 1), } } func (s *agentEndpointServiceTestServer) ReceiveTaskNotification(req *agentendpointpb.ReceiveTaskNotificationRequest, srv agentendpointpb.AgentEndpointService_ReceiveTaskNotificationServer) error { for { select { case <-s.streamClose: return nil case <-s.streamSend: srv.Send(&agentendpointpb.ReceiveTaskNotificationResponse{}) case <-s.permissionError: return status.Errorf(codes.PermissionDenied, "") } } } func (s *agentEndpointServiceTestServer) StartNextTask(ctx context.Context, req *agentendpointpb.StartNextTaskRequest) (*agentendpointpb.StartNextTaskResponse, error) { // We first return an TaskType_EXEC_STEP_TASK, then TaskType_APPLY_PATCHES, then TaskType_APPLY_CONFIG_TASK. // After all tasks complete, we return nothing signalling the end to tasks. s.taskStart = true switch { case s.applyConfigTaskComplete && s.execTaskComplete && s.patchTaskComplete: return &agentendpointpb.StartNextTaskResponse{}, nil case !s.execTaskComplete: return &agentendpointpb.StartNextTaskResponse{Task: &agentendpointpb.Task{TaskType: agentendpointpb.TaskType_EXEC_STEP_TASK, TaskId: "TaskType_EXEC_STEP_TASK"}}, nil case !s.patchTaskComplete: return &agentendpointpb.StartNextTaskResponse{Task: &agentendpointpb.Task{TaskType: agentendpointpb.TaskType_APPLY_PATCHES, TaskId: "TaskType_APPLY_PATCHES"}}, nil case !s.applyConfigTaskComplete: return &agentendpointpb.StartNextTaskResponse{Task: &agentendpointpb.Task{TaskType: agentendpointpb.TaskType_APPLY_CONFIG_TASK, TaskId: "TaskType_APPLY_CONFIG_TASK"}}, nil default: return &agentendpointpb.StartNextTaskResponse{}, status.Errorf(codes.Unimplemented, "unexpected start next task") } } func (s *agentEndpointServiceTestServer) ReportTaskProgress(ctx context.Context, req *agentendpointpb.ReportTaskProgressRequest) (*agentendpointpb.ReportTaskProgressResponse, error) { // Simply record and send STOP. switch req.GetTaskType() { case agentendpointpb.TaskType_EXEC_STEP_TASK: s.execTaskProgress = true case agentendpointpb.TaskType_APPLY_PATCHES: s.patchTaskProgress = true case agentendpointpb.TaskType_APPLY_CONFIG_TASK: s.applyConfigTaskProgress = true default: return &agentendpointpb.ReportTaskProgressResponse{}, status.Errorf(codes.Unimplemented, "task type %q not implemented", req.GetTaskType()) } return &agentendpointpb.ReportTaskProgressResponse{TaskDirective: agentendpointpb.TaskDirective_STOP}, nil } func (s *agentEndpointServiceTestServer) ReportTaskComplete(ctx context.Context, req *agentendpointpb.ReportTaskCompleteRequest) (*agentendpointpb.ReportTaskCompleteResponse, error) { // Record what task types we have seen, when the complete is called for TaskType_APPLY_CONFIG_TASK, close the stream. s.runTaskIDs = append(s.runTaskIDs, req.GetTaskId()) switch req.GetTaskType() { case agentendpointpb.TaskType_EXEC_STEP_TASK: s.execTaskComplete = true case agentendpointpb.TaskType_APPLY_PATCHES: s.patchTaskComplete = true case agentendpointpb.TaskType_APPLY_CONFIG_TASK: s.applyConfigTaskComplete = true default: return &agentendpointpb.ReportTaskCompleteResponse{}, status.Errorf(codes.Unimplemented, "task type %q not implemented", req.GetTaskType()) } if s.execTaskComplete && s.patchTaskComplete && s.applyConfigTaskComplete { s.streamClose <- struct{}{} } return &agentendpointpb.ReportTaskCompleteResponse{}, nil } func (*agentEndpointServiceTestServer) RegisterAgent(ctx context.Context, req *agentendpointpb.RegisterAgentRequest) (*agentendpointpb.RegisterAgentResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegisterAgent not implemented") } func (*agentEndpointServiceTestServer) ReportInventory(ctx context.Context, req *agentendpointpb.ReportInventoryRequest) (*agentendpointpb.ReportInventoryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ReportInventory not implemented") } func TestWaitForTask(t *testing.T) { ctx := context.Background() srv := newAgentEndpointServiceTestServer() tc, err := newTestClient(ctx, srv) if err != nil { t.Fatal(err) } defer tc.close() td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { t.Fatalf("error creating temp dir: %v", err) } defer os.RemoveAll(td) taskStateFile = filepath.Join(td, "testState") // Stream recieve. srv.streamSend <- struct{}{} if err := tc.client.waitForTask(ctx); err != nil { t.Errorf("did not expect error from a closed stream: %v", err) } if !srv.execTaskProgress { t.Error("expected ReportTaskProgress for TaskType_EXEC_STEP_TASK to have been called") } if !srv.execTaskComplete { t.Error("expected ReportTaskComplete for TaskType_EXEC_STEP_TASK to have been called") } if !srv.patchTaskProgress { t.Error("expected ReportTaskProgress for TaskType_APPLY_PATCHES to have been called") } if !srv.patchTaskComplete { t.Error("expected ReportTaskComplete for TaskType_APPLY_PATCHES to have been called") } if !srv.applyConfigTaskProgress { t.Error("expected ReportTaskProgress for TaskType_APPLY_CONFIG_TASK to have been called") } if !srv.applyConfigTaskComplete { t.Error("expected ReportTaskComplete for TaskType_APPLY_CONFIG_TASK to have been called") } } func TestWaitForTaskErrors(t *testing.T) { ctx := context.Background() srv := newAgentEndpointServiceTestServer() tc, err := newTestClient(ctx, srv) if err != nil { t.Fatal(err) } // errServiceNotEnabled from PermissionDenied error. srv.permissionError <- struct{}{} if err := tc.client.waitForTask(ctx); !errors.Is(err, errServiceNotEnabled) { t.Errorf("did not get expected errServiceNotEnabled, got: %v", err) } // No error from a closed stream. srv.streamClose <- struct{}{} if err := tc.client.waitForTask(ctx); err != nil { t.Errorf("did not expect error from a closed stream: %v", err) } } func TestLoadPatchTaskFromState(t *testing.T) { ctx := context.Background() srv := newAgentEndpointServiceTestServer() tc, err := newTestClient(ctx, srv) if err != nil { t.Fatal(err) } defer tc.close() td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { t.Fatalf("error creating temp dir: %v", err) } defer os.RemoveAll(td) taskStateFile = filepath.Join(td, "testState") srv.streamSend <- struct{}{} // No state. if err := tc.client.loadTaskFromState(ctx); err != nil { t.Error(err) } if srv.taskStart { t.Error("expected ReportTaskStart to not have been called") } // Bad state. if err := ioutil.WriteFile(taskStateFile, []byte("bad"), 0600); err != nil { t.Fatal(err) } if err := tc.client.loadTaskFromState(ctx); err == nil { t.Error("expected error from loadTaskFromState") } // Existing task. taskID := "foo" if err := ioutil.WriteFile(taskStateFile, []byte(fmt.Sprintf(`{"PatchTask":{"TaskID":"%s", "PatchStep": "%s"}}`, taskID, patching)), 0600); err != nil { t.Fatal(err) } if err := tc.client.loadTaskFromState(ctx); err != nil { t.Fatal(err) } srv.execTaskComplete = true srv.applyConfigTaskComplete = true // Launch another patch task, this should run AFTER the task loaded from state file if err := tc.client.waitForTask(ctx); err != nil { t.Errorf("did not expect error from a closed stream: %v", err) } if srv.taskStart { t.Error("did not expect ReportTaskStart to have been called") } if !srv.patchTaskProgress { t.Error("expected ReportTaskProgress for TaskType_APPLY_PATCHES to have been called") } if !srv.patchTaskComplete { t.Error("expected ReportTaskComplete for TaskType_APPLY_PATCHES to have been called") } if len(srv.runTaskIDs) != 1 { t.Fatalf("expected srv.runTaskIDs to have a length of 1, not %d", len(srv.runTaskIDs)) } if srv.runTaskIDs[0] != taskID { t.Errorf("first entry in runTaskIDs does not match taskID, %q, %q", srv.runTaskIDs, taskID) } } osconfig-20210219.00/agentendpoint/config_task.go000066400000000000000000000372761401404514100214670ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint import ( "context" "fmt" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/config" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) const ( numExecutionSteps = 4 validationStepIndex = 0 checkDesiredStateStepIndex = 1 enforcementStepIndex = 2 postCheckDesiredStateStepIndex = 3 ) var newResource = func(r *agentendpointpb.OSPolicy_Resource) resourceIface { return resourceIface(&config.OSPolicyResource{OSPolicy_Resource: r}) } type configTask struct { client *Client lastProgressState map[agentendpointpb.ApplyConfigTaskProgress_State]time.Time TaskID string Task *applyConfigTask StartedAt time.Time `json:",omitempty"` // ApplyConfigTaskOutput result results []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult postCheckRequired bool policies map[string]*policy } type applyConfigTask struct { *agentendpointpb.ApplyConfigTask } type policy struct { resources map[string]resourceIface hasError bool } type resourceIface interface { Validate(context.Context) error CheckState(context.Context) error EnforceState(context.Context) error Cleanup(context.Context) error InDesiredState() bool ManagedResources() *config.ManagedResources } func (c *configTask) reportCompletedState(ctx context.Context, errMsg string, state agentendpointpb.ApplyConfigTaskOutput_State) error { req := &agentendpointpb.ReportTaskCompleteRequest{ TaskId: c.TaskID, TaskType: agentendpointpb.TaskType_APPLY_CONFIG_TASK, ErrorMessage: errMsg, Output: &agentendpointpb.ReportTaskCompleteRequest_ApplyConfigTaskOutput{ ApplyConfigTaskOutput: &agentendpointpb.ApplyConfigTaskOutput{State: state, OsPolicyResults: c.results}, }, } if err := c.client.reportTaskComplete(ctx, req); err != nil { return fmt.Errorf("error reporting completed state: %v", err) } return nil } func (c *configTask) handleErrorState(ctx context.Context, msg string, err error) error { if err == errServerCancel { clog.Infof(ctx, "Cancelling config run: %v", errServerCancel) return c.reportCompletedState(ctx, errServerCancel.Error(), agentendpointpb.ApplyConfigTaskOutput_CANCELLED) } msg = fmt.Sprintf("%s: %v", msg, err) clog.Errorf(ctx, msg) return c.reportCompletedState(ctx, msg, agentendpointpb.ApplyConfigTaskOutput_FAILED) } func (c *configTask) reportContinuingState(ctx context.Context, configState agentendpointpb.ApplyConfigTaskProgress_State) error { st, ok := c.lastProgressState[configState] if ok && st.After(time.Now().Add(sameStateTimeWindow)) { // Don't resend the same state more than once every 5s. return nil } req := &agentendpointpb.ReportTaskProgressRequest{ TaskId: c.TaskID, TaskType: agentendpointpb.TaskType_APPLY_CONFIG_TASK, Progress: &agentendpointpb.ReportTaskProgressRequest_ApplyConfigTaskProgress{ ApplyConfigTaskProgress: &agentendpointpb.ApplyConfigTaskProgress{State: configState}, }, } res, err := c.client.reportTaskProgress(ctx, req) if err != nil { return fmt.Errorf("error reporting task progress %s: %v", configState, err) } if res.GetTaskDirective() == agentendpointpb.TaskDirective_STOP { return errServerCancel } if c.lastProgressState == nil { c.lastProgressState = make(map[agentendpointpb.ApplyConfigTaskProgress_State]time.Time) } c.lastProgressState[configState] = time.Now() return nil } // detectPolicyConflicts checks for managed resource conflicts between a proposed // OSPolicyResource and all other OSPolcyResources up to this point, adding to the // current set of ManagedResources. func detectPolicyConflicts(proposed, current *config.ManagedResources) error { // TODO: implement return nil } func validateConfigResource(ctx context.Context, plcy *policy, policyMR *config.ManagedResources, rCompliance *agentendpointpb.OSPolicyResourceCompliance, configResource *agentendpointpb.OSPolicy_Resource) { ctx = clog.WithLabels(ctx, map[string]string{"resource_id": configResource.GetId()}) plcy.resources[configResource.GetId()] = newResource(configResource) resource := plcy.resources[configResource.GetId()] outcome := agentendpointpb.OSPolicyResourceConfigStep_SUCCEEDED state := agentendpointpb.OSPolicyComplianceState_UNKNOWN if err := resource.Validate(ctx); err != nil { outcome = agentendpointpb.OSPolicyResourceConfigStep_FAILED plcy.hasError = true clog.Errorf(ctx, "Error validating resource: %v", err) } // Detect any resource conflicts within this policy. if err := detectPolicyConflicts(resource.ManagedResources(), policyMR); err != nil { outcome = agentendpointpb.OSPolicyResourceConfigStep_FAILED plcy.hasError = true clog.Errorf(ctx, "Resource conflict in policy: %v", err) } rCompliance.GetConfigSteps()[validationStepIndex] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_VALIDATION, Outcome: outcome, } rCompliance.State = state } func (c *configTask) validation(ctx context.Context) { // This is all the managed resources by policy. globalManagedResources := map[string]*config.ManagedResources{} // Validate each resouce and populate results and internal assignment state. c.policies = map[string]*policy{} for i, osPolicy := range c.Task.GetOsPolicies() { ctx = clog.WithLabels(ctx, map[string]string{"config_assignment": osPolicy.GetOsPolicyAssignment(), "policy_id": osPolicy.GetId()}) pResult := c.results[i] plcy := &policy{resources: map[string]resourceIface{}} c.policies[osPolicy.GetId()] = plcy var policyMR *config.ManagedResources for i, configResource := range osPolicy.GetResources() { rCompliance := pResult.GetOsPolicyResourceCompliances()[i] validateConfigResource(ctx, plcy, policyMR, rCompliance, configResource) } // We only care about conflict detection across policies that are in enforcement mode. if osPolicy.GetMode() == agentendpointpb.OSPolicy_ENFORCEMENT { globalManagedResources[osPolicy.GetId()] = policyMR } } // TODO: check for global resource conflicts. } func checkConfigResourceState(ctx context.Context, plcy *policy, rCompliance *agentendpointpb.OSPolicyResourceCompliance, configResource *agentendpointpb.OSPolicy_Resource) { ctx = clog.WithLabels(ctx, map[string]string{"resource_id": configResource.GetId()}) res := plcy.resources[configResource.GetId()] outcome := agentendpointpb.OSPolicyResourceConfigStep_SUCCEEDED state := agentendpointpb.OSPolicyComplianceState_UNKNOWN err := res.CheckState(ctx) if err != nil { outcome = agentendpointpb.OSPolicyResourceConfigStep_FAILED plcy.hasError = true clog.Errorf(ctx, "Error running desired state check: %v", err) } else if res.InDesiredState() { state = agentendpointpb.OSPolicyComplianceState_COMPLIANT } else { state = agentendpointpb.OSPolicyComplianceState_NON_COMPLIANT } rCompliance.GetConfigSteps()[checkDesiredStateStepIndex] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_DESIRED_STATE_CHECK, Outcome: outcome, } rCompliance.State = state } func (c *configTask) checkState(ctx context.Context) { // First populate check state results (for policies that do not have validation errors). for i, osPolicy := range c.Task.GetOsPolicies() { plcy := c.policies[osPolicy.GetId()] // Skip state check if this policy already has an error from a previous step. if plcy.hasError { continue } pResult := c.results[i] for i := range osPolicy.GetResources() { rCompliance := pResult.GetOsPolicyResourceCompliances()[i] rCompliance.GetConfigSteps()[checkDesiredStateStepIndex] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_DESIRED_STATE_CHECK, } } } // Actually run check state. for i, osPolicy := range c.Task.GetOsPolicies() { ctx = clog.WithLabels(ctx, map[string]string{"config_assignment": osPolicy.GetOsPolicyAssignment(), "policy_id": osPolicy.GetId()}) plcy := c.policies[osPolicy.GetId()] // Skip state check if this policy already has an error from a previous step. if plcy.hasError { clog.Debugf(ctx, "Policy has error, skipping state check.") continue } pResult := c.results[i] for i, configResource := range osPolicy.GetResources() { rCompliance := pResult.GetOsPolicyResourceCompliances()[i] checkConfigResourceState(ctx, plcy, rCompliance, configResource) // Stop state check of this policy if we encounter an error. if plcy.hasError { clog.Debugf(ctx, "Policy has error, stopping state check.") break } } } } func enforceConfigResourceState(ctx context.Context, plcy *policy, rCompliance *agentendpointpb.OSPolicyResourceCompliance, configResource *agentendpointpb.OSPolicy_Resource) bool { ctx = clog.WithLabels(ctx, map[string]string{"resource_id": configResource.GetId()}) res := plcy.resources[configResource.GetId()] // Only enforce resources that need it. if res.InDesiredState() { clog.Debugf(ctx, "No enforcement required.") return false } outcome := agentendpointpb.OSPolicyResourceConfigStep_SUCCEEDED err := res.EnforceState(ctx) if err != nil { outcome = agentendpointpb.OSPolicyResourceConfigStep_FAILED plcy.hasError = true clog.Errorf(ctx, "Error running enforcement: %v", err) } rCompliance.GetConfigSteps()[enforcementStepIndex] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_DESIRED_STATE_ENFORCEMENT, Outcome: outcome, } // Resource is always in an unknown state after enforcement is run. // A COMPLIANT state will only happen after a post check. rCompliance.State = agentendpointpb.OSPolicyComplianceState_UNKNOWN return true } func (c *configTask) enforceState(ctx context.Context) { // Run enforcement (for resources that require it). for i, osPolicy := range c.Task.GetOsPolicies() { ctx = clog.WithLabels(ctx, map[string]string{"config_assignment": osPolicy.GetOsPolicyAssignment(), "policy_id": osPolicy.GetId()}) plcy := c.policies[osPolicy.GetId()] // Skip state check if this policy already has an error from a previous step. if plcy.hasError { clog.Debugf(ctx, "Policy has error, skipping enforcement.") continue } pResult := c.results[i] for i, configResource := range osPolicy.GetResources() { rCompliance := pResult.GetOsPolicyResourceCompliances()[i] if enforceConfigResourceState(ctx, plcy, rCompliance, configResource) { // On aany change we trigger post check. c.postCheckRequired = true } // Stop enforcement of this policy if we encounter an error. if plcy.hasError { clog.Debugf(ctx, "Policy has error, stopping enforcement.") break } } } } func postCheckConfigResourceState(ctx context.Context, plcy *policy, rCompliance *agentendpointpb.OSPolicyResourceCompliance, configResource *agentendpointpb.OSPolicy_Resource) { ctx = clog.WithLabels(ctx, map[string]string{"resource_id": configResource.GetId()}) res := plcy.resources[configResource.GetId()] outcome := agentendpointpb.OSPolicyResourceConfigStep_SUCCEEDED state := agentendpointpb.OSPolicyComplianceState_UNKNOWN err := res.CheckState(ctx) if err != nil { outcome = agentendpointpb.OSPolicyResourceConfigStep_FAILED plcy.hasError = true clog.Errorf(ctx, "Error running post config desired state check: %v", err) } else if res.InDesiredState() { state = agentendpointpb.OSPolicyComplianceState_COMPLIANT } else { state = agentendpointpb.OSPolicyComplianceState_NON_COMPLIANT } rCompliance.GetConfigSteps()[postCheckDesiredStateStepIndex] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_DESIRED_STATE_CHECK_POST_ENFORCEMENT, Outcome: outcome, } rCompliance.State = state } func (c *configTask) postCheckState(ctx context.Context) { // Actually run post check state (for policies that do not have a previous error). // No prepopulate run for post check as we will always check every resource. for i, osPolicy := range c.Task.GetOsPolicies() { ctx = clog.WithLabels(ctx, map[string]string{"config_assignment": osPolicy.GetOsPolicyAssignment(), "policy_id": osPolicy.GetId()}) plcy := c.policies[osPolicy.GetId()] // Skip state check if this policy already has an error from a previous step. if plcy.hasError { clog.Debugf(ctx, "Policy has error, skipping post check.") continue } pResult := c.results[i] for i, configResource := range osPolicy.GetResources() { rCompliance := pResult.GetOsPolicyResourceCompliances()[i] postCheckConfigResourceState(ctx, plcy, rCompliance, configResource) } } } func (c *configTask) generateBaseResults() { c.results = make([]*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult, len(c.Task.GetOsPolicies())) for i, p := range c.Task.GetOsPolicies() { pResult := &agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{ OsPolicyId: p.GetId(), OsPolicyAssignment: p.GetOsPolicyAssignment(), OsPolicyResourceCompliances: make([]*agentendpointpb.OSPolicyResourceCompliance, len(p.GetResources())), } c.results[i] = pResult for i, r := range p.GetResources() { pResult.GetOsPolicyResourceCompliances()[i] = &agentendpointpb.OSPolicyResourceCompliance{ OsPolicyResourceId: r.GetId(), ConfigSteps: make([]*agentendpointpb.OSPolicyResourceConfigStep, numExecutionSteps), } } } } func (c *configTask) cleanup(ctx context.Context) { for _, osPolicy := range c.Task.GetOsPolicies() { ctx = clog.WithLabels(ctx, map[string]string{"config_assignment": osPolicy.GetOsPolicyAssignment(), "policy_id": osPolicy.GetId()}) plcy := c.policies[osPolicy.GetId()] for _, configResource := range osPolicy.GetResources() { ctx = clog.WithLabels(ctx, map[string]string{"resource_id": configResource.GetId()}) res := plcy.resources[configResource.GetId()] if err := res.Cleanup(ctx); err != nil { clog.Warningf(ctx, "Error running resource cleanup:%v", err) } } } } func (c *configTask) run(ctx context.Context) error { clog.Infof(ctx, "Beginning apply config task.") c.StartedAt = time.Now() rcsErrMsg := "Error reporting continuing state" if err := c.reportContinuingState(ctx, agentendpointpb.ApplyConfigTaskProgress_STARTED); err != nil { return c.handleErrorState(ctx, rcsErrMsg, err) } if len(c.Task.GetOsPolicies()) == 0 { clog.Infof(ctx, "No OSPolicies to apply.") return c.reportCompletedState(ctx, "", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED) } // We need to generate base results first thing, each execution step // just adds on. c.generateBaseResults() c.validation(ctx) defer c.cleanup(ctx) c.checkState(ctx) if err := c.reportContinuingState(ctx, agentendpointpb.ApplyConfigTaskProgress_APPLYING_CONFIG); err != nil { return c.handleErrorState(ctx, rcsErrMsg, err) } c.enforceState(ctx) if c.postCheckRequired { c.postCheckState(ctx) } return c.reportCompletedState(ctx, "", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED) } // RunApplyConfig runs an apply config task. func (c *Client) RunApplyConfig(ctx context.Context, task *agentendpointpb.Task) error { ctx = clog.WithLabels(ctx, task.GetServiceLabels()) e := &configTask{ TaskID: task.GetTaskId(), client: c, Task: &applyConfigTask{task.GetApplyConfigTask()}, } return e.run(ctx) } osconfig-20210219.00/agentendpoint/config_task_test.go000066400000000000000000000271701401404514100225160ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint import ( "context" "errors" "testing" "github.com/GoogleCloudPlatform/osconfig/config" "github.com/google/go-cmp/cmp" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/testing/protocmp" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) var errTest = errors.New("this is a test error") type testResource struct { inDesiredState bool steps int } func (r *testResource) InDesiredState() bool { return r.inDesiredState } func (r *testResource) Cleanup(ctx context.Context) error { return nil } func (r *testResource) Validate(ctx context.Context) error { if r.steps == 0 { return errTest } return nil } func (r *testResource) CheckState(ctx context.Context) error { if r.steps == 1 { return errTest } if r.steps == 3 && r.inDesiredState { return errTest } return nil } func (r *testResource) EnforceState(ctx context.Context) error { if r.steps == 2 { return errTest } r.inDesiredState = true return nil } func (r *testResource) ManagedResources() *config.ManagedResources { return nil } type agentEndpointServiceConfigTestServer struct { lastReportTaskCompleteRequest *agentendpointpb.ReportTaskCompleteRequest progressError chan struct{} progressCancel chan struct{} } func (*agentEndpointServiceConfigTestServer) ReceiveTaskNotification(req *agentendpointpb.ReceiveTaskNotificationRequest, srv agentendpointpb.AgentEndpointService_ReceiveTaskNotificationServer) error { return status.Errorf(codes.Unimplemented, "method ReceiveTaskNotification not implemented") } func (*agentEndpointServiceConfigTestServer) StartNextTask(ctx context.Context, req *agentendpointpb.StartNextTaskRequest) (*agentendpointpb.StartNextTaskResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method StartNextTask not implemented") } func (s *agentEndpointServiceConfigTestServer) ReportTaskProgress(ctx context.Context, req *agentendpointpb.ReportTaskProgressRequest) (*agentendpointpb.ReportTaskProgressResponse, error) { select { case s.progressError <- struct{}{}: default: return nil, status.Errorf(codes.Unimplemented, "") } select { case s.progressCancel <- struct{}{}: default: return &agentendpointpb.ReportTaskProgressResponse{TaskDirective: agentendpointpb.TaskDirective_STOP}, nil } return &agentendpointpb.ReportTaskProgressResponse{TaskDirective: agentendpointpb.TaskDirective_CONTINUE}, nil } func (s *agentEndpointServiceConfigTestServer) ReportTaskComplete(ctx context.Context, req *agentendpointpb.ReportTaskCompleteRequest) (*agentendpointpb.ReportTaskCompleteResponse, error) { s.lastReportTaskCompleteRequest = req return &agentendpointpb.ReportTaskCompleteResponse{}, nil } func (*agentEndpointServiceConfigTestServer) RegisterAgent(ctx context.Context, req *agentendpointpb.RegisterAgentRequest) (*agentendpointpb.RegisterAgentResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegisterAgent not implemented") } func (*agentEndpointServiceConfigTestServer) ReportInventory(ctx context.Context, req *agentendpointpb.ReportInventoryRequest) (*agentendpointpb.ReportInventoryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ReportInventory not implemented") } func configOutputGen(msg string, st agentendpointpb.ApplyConfigTaskOutput_State, results []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult) *agentendpointpb.ReportTaskCompleteRequest { return &agentendpointpb.ReportTaskCompleteRequest{ TaskType: agentendpointpb.TaskType_APPLY_CONFIG_TASK, ErrorMessage: msg, Output: &agentendpointpb.ReportTaskCompleteRequest_ApplyConfigTaskOutput{ ApplyConfigTaskOutput: &agentendpointpb.ApplyConfigTaskOutput{State: st, OsPolicyResults: results}, }, InstanceIdToken: testIDToken, } } func genTestResource(id string) *agentendpointpb.OSPolicy_Resource { return &agentendpointpb.OSPolicy_Resource{ Id: id, } } func genTestResourceCompliance(id string, steps int, inDesiredState bool) *agentendpointpb.OSPolicyResourceCompliance { // TODO: test various types of executions. ret := &agentendpointpb.OSPolicyResourceCompliance{ OsPolicyResourceId: id, ConfigSteps: make([]*agentendpointpb.OSPolicyResourceConfigStep, 4), } // Validation if steps > 0 { outcome := agentendpointpb.OSPolicyResourceConfigStep_FAILED state := agentendpointpb.OSPolicyComplianceState_UNKNOWN if steps > 1 { outcome = agentendpointpb.OSPolicyResourceConfigStep_SUCCEEDED } ret.ConfigSteps[0] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_VALIDATION, Outcome: outcome, } ret.State = state } // DesiredStateCheck if steps > 1 { outcome := agentendpointpb.OSPolicyResourceConfigStep_SUCCEEDED state := agentendpointpb.OSPolicyComplianceState_NON_COMPLIANT if steps == 2 && !inDesiredState { outcome = agentendpointpb.OSPolicyResourceConfigStep_FAILED state = agentendpointpb.OSPolicyComplianceState_UNKNOWN } else if inDesiredState { state = agentendpointpb.OSPolicyComplianceState_COMPLIANT } ret.ConfigSteps[1] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_DESIRED_STATE_CHECK, Outcome: outcome, } ret.State = state } // EnforceDesiredState if steps > 2 { outcome := agentendpointpb.OSPolicyResourceConfigStep_FAILED state := agentendpointpb.OSPolicyComplianceState_UNKNOWN if steps > 3 { outcome = agentendpointpb.OSPolicyResourceConfigStep_SUCCEEDED } ret.ConfigSteps[2] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_DESIRED_STATE_ENFORCEMENT, Outcome: outcome, } ret.State = state } // DesiredStateCheckPostEnforcement{ if steps > 3 { outcome := agentendpointpb.OSPolicyResourceConfigStep_SUCCEEDED state := agentendpointpb.OSPolicyComplianceState_NON_COMPLIANT if steps == 4 { outcome = agentendpointpb.OSPolicyResourceConfigStep_FAILED state = agentendpointpb.OSPolicyComplianceState_UNKNOWN } else { state = agentendpointpb.OSPolicyComplianceState_COMPLIANT } ret.ConfigSteps[3] = &agentendpointpb.OSPolicyResourceConfigStep{ Type: agentendpointpb.OSPolicyResourceConfigStep_DESIRED_STATE_CHECK_POST_ENFORCEMENT, Outcome: outcome, } ret.State = state } return ret } func genTestPolicy(id string) *agentendpointpb.ApplyConfigTask_OSPolicy { return &agentendpointpb.ApplyConfigTask_OSPolicy{ Id: id, Mode: agentendpointpb.OSPolicy_ENFORCEMENT, Resources: []*agentendpointpb.OSPolicy_Resource{ genTestResource("r1"), }, } } func genTestPolicyResult(id string, steps int, inDesiredState bool) *agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult { return &agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{ OsPolicyId: id, OsPolicyResourceCompliances: []*agentendpointpb.OSPolicyResourceCompliance{ genTestResourceCompliance("r1", steps, inDesiredState), }, } } func TestRunApplyConfig(t *testing.T) { ctx := context.Background() sameStateTimeWindow = 0 res := &testResource{} newResource = func(r *agentendpointpb.OSPolicy_Resource) resourceIface { return resourceIface(res) } testConfig := &agentendpointpb.ApplyConfigTask{ OsPolicies: []*agentendpointpb.ApplyConfigTask_OSPolicy{ genTestPolicy("p1"), }, } tests := []struct { name string wantComReq *agentendpointpb.ReportTaskCompleteRequest step *agentendpointpb.ApplyConfigTask callsBeforeCancel int callsBeforeErr int stepsBeforeErr int startInDesiredState bool }{ // Normal cases: { "InDesiredState", configOutputGen("", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{ genTestPolicyResult("p1", 2, true), }, ), testConfig, 5, 5, 5, true, }, { "EnforceDesiredState", configOutputGen("", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{ genTestPolicyResult("p1", 5, false), }, ), testConfig, 5, 5, 5, false, }, { "NilPolicies", configOutputGen("", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{}), &agentendpointpb.ApplyConfigTask{OsPolicies: nil}, 5, 5, 5, false, }, { "NoPolicies", configOutputGen("", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{}), &agentendpointpb.ApplyConfigTask{OsPolicies: nil}, 5, 5, 5, false, }, // Step error cases { "ValidateError", configOutputGen("", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{ genTestPolicyResult("p1", 1, false), }, ), testConfig, 5, 5, 0, false, }, { "CheckStateError", configOutputGen("", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{ genTestPolicyResult("p1", 2, false), }, ), testConfig, 5, 5, 1, false, }, { "EnforceError", configOutputGen("", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{ genTestPolicyResult("p1", 3, false), }, ), testConfig, 5, 5, 2, false, }, { "PostCheckError", configOutputGen("", agentendpointpb.ApplyConfigTaskOutput_SUCCEEDED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{ genTestPolicyResult("p1", 4, false), }, ), testConfig, 5, 5, 3, false, }, // Cases where task is canceled by server at various points. { "CancelAfterSTARTED", // No results generated. configOutputGen(errServerCancel.Error(), agentendpointpb.ApplyConfigTaskOutput_CANCELLED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{}), testConfig, 0, 5, 5, false, }, // Cases where task has task level error. { "ErrorReportingSTARTED", // No results configOutputGen(`Error reporting continuing state: error reporting task progress STARTED: error calling ReportTaskProgress: code: "Unimplemented", message: "", details: []`, agentendpointpb.ApplyConfigTaskOutput_FAILED, []*agentendpointpb.ApplyConfigTaskOutput_OSPolicyResult{}), testConfig, 5, 0, 5, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { srv := &agentEndpointServiceConfigTestServer{ progressError: make(chan struct{}, tt.callsBeforeErr), progressCancel: make(chan struct{}, tt.callsBeforeCancel), } tc, err := newTestClient(ctx, srv) if err != nil { t.Fatal(err) } defer tc.close() res.inDesiredState = tt.startInDesiredState res.steps = tt.stepsBeforeErr if err := tc.client.RunApplyConfig(ctx, &agentendpointpb.Task{TaskDetails: &agentendpointpb.Task_ApplyConfigTask{ApplyConfigTask: tt.step}}); err != nil { t.Fatal(err) } if diff := cmp.Diff(tt.wantComReq, srv.lastReportTaskCompleteRequest, protocmp.Transform()); diff != "" { t.Fatalf("ReportTaskCompleteRequest mismatch (-want +got):\n%s", diff) } }) } } osconfig-20210219.00/agentendpoint/exec_task.go000066400000000000000000000165141401404514100211360ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint import ( "context" "errors" "fmt" "os" "os/exec" "path" "path/filepath" "runtime" "time" "cloud.google.com/go/storage" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/external" "github.com/GoogleCloudPlatform/osconfig/util" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) var ( winRoot = os.Getenv("SystemRoot") sh = "/bin/sh" winPowershell string winCmd string winPowershellArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass"} goos = runtime.GOOS errLinuxPowerShell = errors.New("interpreter POWERSHELL cannot be used on non-Windows system") errWinNoInt = fmt.Errorf("interpreter must be specified for a Windows system") ) func init() { if winRoot == "" { winRoot = `C:\Windows` } winPowershell = filepath.Join(winRoot, `System32\WindowsPowerShell\v1.0\PowerShell.exe`) winCmd = filepath.Join(winRoot, `System32\cmd.exe`) } var run = func(cmd *exec.Cmd) ([]byte, error) { return cmd.CombinedOutput() } func getGCSObject(ctx context.Context, bkt, obj string, gen int64) (string, error) { cl, err := storage.NewClient(ctx) if err != nil { return "", fmt.Errorf("error creating gcs client: %v", err) } reader, err := external.FetchGCSObject(ctx, cl, bkt, obj, gen) if err != nil { return "", fmt.Errorf("error fetching GCS object: %v", err) } defer reader.Close() clog.Debugf(ctx, "Fetched GCS object bucket %s object %s generation number %d", bkt, obj, gen) localPath := filepath.Join(os.TempDir(), path.Base(obj)) if _, err := util.AtomicWriteFileStream(reader, "", localPath, 0755); err != nil { return "", fmt.Errorf("error downloading GCS object: %s", err) } clog.Debugf(ctx, "Downloaded to local path %s", localPath) return localPath, nil } func executeCommand(ctx context.Context, path string, args []string) (int32, error) { clog.Debugf(ctx, "Running command %s with args %s", path, args) cmd := exec.Command(path, args...) out, err := run(cmd) var exitCode int32 if cmd.ProcessState != nil { exitCode = int32(cmd.ProcessState.ExitCode()) clog.Infof(ctx, "Command exit code: %d, out:\n%s", exitCode, out) } if err != nil { if _, ok := err.(*exec.ExitError); !ok { return -1, err } } return exitCode, nil } type execTask struct { client *Client TaskID string Task *execStepTask StartedAt time.Time `json:",omitempty"` } type execStepTask struct { *agentendpointpb.ExecStepTask } func (e *execTask) reportCompletedState(ctx context.Context, errMsg string, output *agentendpointpb.ReportTaskCompleteRequest_ExecStepTaskOutput) error { req := &agentendpointpb.ReportTaskCompleteRequest{ TaskId: e.TaskID, TaskType: agentendpointpb.TaskType_EXEC_STEP_TASK, ErrorMessage: errMsg, Output: output, } if err := e.client.reportTaskComplete(ctx, req); err != nil { return fmt.Errorf("error reporting completed state: %v", err) } return nil } func (e *execTask) run(ctx context.Context) error { clog.Infof(ctx, "Beginning exec task") e.StartedAt = time.Now() req := &agentendpointpb.ReportTaskProgressRequest{ TaskId: e.TaskID, TaskType: agentendpointpb.TaskType_EXEC_STEP_TASK, Progress: &agentendpointpb.ReportTaskProgressRequest_ExecStepTaskProgress{ ExecStepTaskProgress: &agentendpointpb.ExecStepTaskProgress{State: agentendpointpb.ExecStepTaskProgress_STARTED}, }, } res, err := e.client.reportTaskProgress(ctx, req) if err != nil { return fmt.Errorf("error reporting state %s: %v", agentendpointpb.ExecStepTaskProgress_STARTED, err) } if res.GetTaskDirective() == agentendpointpb.TaskDirective_STOP { return e.reportCompletedState(ctx, errServerCancel.Error(), &agentendpointpb.ReportTaskCompleteRequest_ExecStepTaskOutput{ ExecStepTaskOutput: &agentendpointpb.ExecStepTaskOutput{State: agentendpointpb.ExecStepTaskOutput_CANCELLED}, }) } stepConfig := e.Task.GetExecStep().GetLinuxExecStepConfig() if goos == "windows" { stepConfig = e.Task.GetExecStep().GetWindowsExecStepConfig() } if stepConfig == nil { // The given ExecTask does not apply to this OS. return e.reportCompletedState(ctx, "", &agentendpointpb.ReportTaskCompleteRequest_ExecStepTaskOutput{ ExecStepTaskOutput: &agentendpointpb.ExecStepTaskOutput{ State: agentendpointpb.ExecStepTaskOutput_COMPLETED, ExitCode: 0, }, }) } localPath := stepConfig.GetLocalPath() if gcsObject := stepConfig.GetGcsObject(); gcsObject != nil { var err error localPath, err = getGCSObject(ctx, gcsObject.GetBucket(), gcsObject.GetObject(), gcsObject.GetGenerationNumber()) if err != nil { msg := fmt.Sprintf("Error downloading GCS object: %v", err) clog.Errorf(ctx, msg) return e.reportCompletedState(ctx, msg, &agentendpointpb.ReportTaskCompleteRequest_ExecStepTaskOutput{ ExecStepTaskOutput: &agentendpointpb.ExecStepTaskOutput{ State: agentendpointpb.ExecStepTaskOutput_COMPLETED, ExitCode: -1, }, }) } defer func() { if err := os.Remove(localPath); err != nil { clog.Errorf(ctx, "error removing downloaded file %s", err) } }() } exitCode := int32(-1) switch stepConfig.GetInterpreter() { case agentendpointpb.ExecStepConfig_INTERPRETER_UNSPECIFIED: if goos == "windows" { err = errWinNoInt } else { exitCode, err = executeCommand(ctx, localPath, nil) } case agentendpointpb.ExecStepConfig_SHELL: if goos == "windows" { exitCode, err = executeCommand(ctx, winCmd, []string{"/c", localPath}) } else { exitCode, err = executeCommand(ctx, sh, []string{localPath}) } case agentendpointpb.ExecStepConfig_POWERSHELL: if goos == "windows" { exitCode, err = executeCommand(ctx, winPowershell, append(winPowershellArgs, "-File", localPath)) } else { err = errLinuxPowerShell } default: err = fmt.Errorf("invalid interpreter %q", stepConfig.GetInterpreter()) } if err != nil { msg := fmt.Sprintf("Error running exec task: %v", err) clog.Errorf(ctx, msg) return e.reportCompletedState(ctx, msg, &agentendpointpb.ReportTaskCompleteRequest_ExecStepTaskOutput{ ExecStepTaskOutput: &agentendpointpb.ExecStepTaskOutput{ State: agentendpointpb.ExecStepTaskOutput_COMPLETED, ExitCode: exitCode, }, }) } return e.reportCompletedState(ctx, "", &agentendpointpb.ReportTaskCompleteRequest_ExecStepTaskOutput{ ExecStepTaskOutput: &agentendpointpb.ExecStepTaskOutput{ State: agentendpointpb.ExecStepTaskOutput_COMPLETED, ExitCode: exitCode, }, }) } // RunExecStep runs an exec step task. func (c *Client) RunExecStep(ctx context.Context, task *agentendpointpb.Task) error { ctx = clog.WithLabels(ctx, task.GetServiceLabels()) e := &execTask{ TaskID: task.GetTaskId(), client: c, Task: &execStepTask{task.GetExecStepTask()}, } return e.run(ctx) } osconfig-20210219.00/agentendpoint/exec_task_test.go000066400000000000000000000203001401404514100221610ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint import ( "context" "os/exec" "testing" "github.com/google/go-cmp/cmp" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/testing/protocmp" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) type agentEndpointServiceExecTestServer struct { lastReportTaskCompleteRequest *agentendpointpb.ReportTaskCompleteRequest } func (*agentEndpointServiceExecTestServer) ReceiveTaskNotification(req *agentendpointpb.ReceiveTaskNotificationRequest, srv agentendpointpb.AgentEndpointService_ReceiveTaskNotificationServer) error { return status.Errorf(codes.Unimplemented, "method ReceiveTaskNotification not implemented") } func (*agentEndpointServiceExecTestServer) StartNextTask(ctx context.Context, req *agentendpointpb.StartNextTaskRequest) (*agentendpointpb.StartNextTaskResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method StartNextTask not implemented") } func (*agentEndpointServiceExecTestServer) ReportTaskProgress(ctx context.Context, req *agentendpointpb.ReportTaskProgressRequest) (*agentendpointpb.ReportTaskProgressResponse, error) { return &agentendpointpb.ReportTaskProgressResponse{}, nil } func (s *agentEndpointServiceExecTestServer) ReportTaskComplete(ctx context.Context, req *agentendpointpb.ReportTaskCompleteRequest) (*agentendpointpb.ReportTaskCompleteResponse, error) { s.lastReportTaskCompleteRequest = req return &agentendpointpb.ReportTaskCompleteResponse{}, nil } func (*agentEndpointServiceExecTestServer) RegisterAgent(ctx context.Context, req *agentendpointpb.RegisterAgentRequest) (*agentendpointpb.RegisterAgentResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegisterAgent not implemented") } func (*agentEndpointServiceExecTestServer) ReportInventory(ctx context.Context, req *agentendpointpb.ReportInventoryRequest) (*agentendpointpb.ReportInventoryResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ReportInventory not implemented") } func outputGen(id string, msg string, st agentendpointpb.ExecStepTaskOutput_State, exitCode int32) *agentendpointpb.ReportTaskCompleteRequest { if msg != "" { msg = "Error running exec task: " + msg } return &agentendpointpb.ReportTaskCompleteRequest{ TaskId: id, TaskType: agentendpointpb.TaskType_EXEC_STEP_TASK, ErrorMessage: msg, Output: &agentendpointpb.ReportTaskCompleteRequest_ExecStepTaskOutput{ ExecStepTaskOutput: &agentendpointpb.ExecStepTaskOutput{State: st, ExitCode: exitCode}, }, InstanceIdToken: testIDToken, } } func TestRunExecStep(t *testing.T) { ctx := context.Background() srv := &agentEndpointServiceExecTestServer{} tc, err := newTestClient(ctx, srv) if err != nil { t.Fatal(err) } defer tc.close() tests := []struct { name string goos string wantComReq *agentendpointpb.ReportTaskCompleteRequest wantPath string wantArgs []string step *agentendpointpb.ExecStep }{ // Matching script and OS. {"LinuxExec", "linux", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), "foo", []string{"foo"}, &agentendpointpb.ExecStep{LinuxExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}}}}, {"LinuxShell", "linux", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), sh, []string{sh, "foo"}, &agentendpointpb.ExecStep{LinuxExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}, Interpreter: agentendpointpb.ExecStepConfig_SHELL}}}, {"LinuxPowerShell", "linux", outputGen("", errLinuxPowerShell.Error(), agentendpointpb.ExecStepTaskOutput_COMPLETED, -1), "", nil, &agentendpointpb.ExecStep{LinuxExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}, Interpreter: agentendpointpb.ExecStepConfig_POWERSHELL}}}, {"WinExec", "windows", outputGen("", errWinNoInt.Error(), agentendpointpb.ExecStepTaskOutput_COMPLETED, -1), "", nil, &agentendpointpb.ExecStep{WindowsExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}}}}, {"WinShell", "windows", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), winCmd, []string{winCmd, "/c", "foo"}, &agentendpointpb.ExecStep{WindowsExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}, Interpreter: agentendpointpb.ExecStepConfig_SHELL}}}, {"WinPowerShell", "windows", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), winPowershell, []string{winPowershell, "-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "foo"}, &agentendpointpb.ExecStep{WindowsExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}, Interpreter: agentendpointpb.ExecStepConfig_POWERSHELL}}}, // Mismatched script and OS. {"LinuxExec", "windows", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), "", nil, &agentendpointpb.ExecStep{LinuxExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}}}}, {"LinuxShell", "windows", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), "", nil, &agentendpointpb.ExecStep{LinuxExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}, Interpreter: agentendpointpb.ExecStepConfig_SHELL}}}, {"LinuxPowerShell", "windows", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), "", nil, &agentendpointpb.ExecStep{LinuxExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}, Interpreter: agentendpointpb.ExecStepConfig_POWERSHELL}}}, {"WinExec", "linux", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), "", nil, &agentendpointpb.ExecStep{WindowsExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}}}}, {"WinShell", "linux", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), "", nil, &agentendpointpb.ExecStep{WindowsExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}, Interpreter: agentendpointpb.ExecStepConfig_SHELL}}}, {"WinPowerShell", "linux", outputGen("", "", agentendpointpb.ExecStepTaskOutput_COMPLETED, 0), "", nil, &agentendpointpb.ExecStep{WindowsExecStepConfig: &agentendpointpb.ExecStepConfig{Executable: &agentendpointpb.ExecStepConfig_LocalPath{LocalPath: "foo"}, Interpreter: agentendpointpb.ExecStepConfig_POWERSHELL}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var gotPath string var gotArgs []string run = func(cmd *exec.Cmd) ([]byte, error) { gotPath = cmd.Path gotArgs = cmd.Args return nil, nil } goos = tt.goos if err := tc.client.RunExecStep(ctx, &agentendpointpb.Task{TaskDetails: &agentendpointpb.Task_ExecStepTask{ExecStepTask: &agentendpointpb.ExecStepTask{ExecStep: tt.step}}}); err != nil { t.Fatal(err) } if diff := cmp.Diff(tt.wantComReq, srv.lastReportTaskCompleteRequest, protocmp.Transform()); diff != "" { t.Fatalf("ReportTaskCompleteRequest mismatch (-want +got):\n%s", diff) } if gotPath != tt.wantPath { t.Errorf("did not get expected path, want: %q, got: %q", tt.wantPath, gotPath) } if diff := cmp.Diff(tt.wantArgs, gotArgs); diff != "" { t.Fatalf("did not get expected args (-want +got):\n%s", diff) } }) } } osconfig-20210219.00/agentendpoint/inventory.go000066400000000000000000000226401401404514100212220ustar00rootroot00000000000000package agentendpoint import ( "context" "fmt" "reflect" "strings" "time" "github.com/GoogleCloudPlatform/osconfig/agentconfig" "github.com/GoogleCloudPlatform/osconfig/attributes" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/inventory" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/GoogleCloudPlatform/osconfig/retryutil" "github.com/GoogleCloudPlatform/osconfig/util" "google.golang.org/protobuf/types/known/timestamppb" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) const ( inventoryURL = agentconfig.ReportURL + "/guestInventory" ) // ReportInventory writes inventory to guest attributes and reports it to agent endpoint. func (c *Client) ReportInventory(ctx context.Context) { state := inventory.Get(ctx) write(ctx, state, inventoryURL) c.report(ctx, state) } func write(ctx context.Context, state *inventory.InstanceInventory, url string) { clog.Debugf(ctx, "Writing instance inventory to guest attributes.") e := reflect.ValueOf(state).Elem() t := e.Type() for i := 0; i < e.NumField(); i++ { f := e.Field(i) u := fmt.Sprintf("%s/%s", url, t.Field(i).Name) switch f.Kind() { case reflect.String: clog.Debugf(ctx, "postAttribute %s: %+v", u, f) if err := attributes.PostAttribute(u, strings.NewReader(f.String())); err != nil { clog.Errorf(ctx, "postAttribute error: %v", err) } case reflect.Ptr: switch reflect.Indirect(f).Kind() { case reflect.Struct: clog.Debugf(ctx, "postAttributeCompressed %s: %+v", u, f) if err := attributes.PostAttributeCompressed(u, f.Interface()); err != nil { clog.Errorf(ctx, "postAttributeCompressed error: %v", err) } } } } } func (c *Client) report(ctx context.Context, state *inventory.InstanceInventory) { clog.Debugf(ctx, "Reporting instance inventory to agent endpoint.") inventory := formatInventory(ctx, state) reportFull := false var res *agentendpointpb.ReportInventoryResponse var err error f := func() error { res, err = c.reportInventory(ctx, inventory, reportFull) if err != nil { return err } clog.Debugf(ctx, "ReportInventory response:\n%s", util.PrettyFmt(res)) return nil } if err = retryutil.RetryAPICall(ctx, apiRetrySec*time.Second, "ReportInventory", f); err != nil { clog.Errorf(ctx, "Error reporting inventory checksum: %v", err) return } if res.GetReportFullInventory() { reportFull = true if err = retryutil.RetryAPICall(ctx, apiRetrySec*time.Second, "ReportInventory", f); err != nil { clog.Errorf(ctx, "Error reporting full inventory: %v", err) return } } } func formatInventory(ctx context.Context, state *inventory.InstanceInventory) *agentendpointpb.Inventory { osInfo := &agentendpointpb.Inventory_OsInfo{ Hostname: state.Hostname, LongName: state.LongName, ShortName: state.ShortName, Version: state.Version, Architecture: state.Architecture, KernelVersion: state.KernelVersion, KernelRelease: state.KernelRelease, OsconfigAgentVersion: state.OSConfigAgentVersion, } installedPackages := formatPackages(ctx, state.InstalledPackages, state.ShortName) availablePackages := formatPackages(ctx, state.PackageUpdates, state.ShortName) return &agentendpointpb.Inventory{OsInfo: osInfo, InstalledPackages: installedPackages, AvailablePackages: availablePackages} } func formatPackages(ctx context.Context, packages *packages.Packages, shortName string) []*agentendpointpb.Inventory_SoftwarePackage { var softwarePackages []*agentendpointpb.Inventory_SoftwarePackage if packages == nil { return softwarePackages } if packages.Apt != nil { for _, pkg := range packages.Apt { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatAptPackage(pkg), }) } } if packages.GooGet != nil { for _, pkg := range packages.GooGet { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatGooGetPackage(pkg), }) } } if packages.Yum != nil { for _, pkg := range packages.Yum { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatYumPackage(pkg), }) } } if packages.Zypper != nil { for _, pkg := range packages.Zypper { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatZypperPackage(pkg), }) } } if packages.ZypperPatches != nil { for _, pkg := range packages.ZypperPatches { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatZypperPatch(pkg), }) } } if packages.WUA != nil { for _, pkg := range packages.WUA { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatWUAPackage(pkg), }) } } if packages.QFE != nil { for _, pkg := range packages.QFE { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatQFEPackage(ctx, pkg), }) } } // Map Deb packages to Apt packages. if packages.Deb != nil { for _, pkg := range packages.Deb { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatAptPackage(pkg), }) } } // Map Rpm packages to Yum or Zypper packages depending on the OS. if packages.Rpm != nil { if shortName == "sles" { for _, pkg := range packages.Rpm { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatZypperPackage(pkg), }) } } else { for _, pkg := range packages.Rpm { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatYumPackage(pkg), }) } } } if packages.COS != nil { for _, pkg := range packages.COS { softwarePackages = append(softwarePackages, &agentendpointpb.Inventory_SoftwarePackage{ Details: formatCOSPackage(pkg), }) } } // Ignore Pip and Gem packages. return softwarePackages } func formatAptPackage(pkg packages.PkgInfo) *agentendpointpb.Inventory_SoftwarePackage_AptPackage { return &agentendpointpb.Inventory_SoftwarePackage_AptPackage{ AptPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: pkg.Name, Architecture: pkg.Arch, Version: pkg.Version, }} } func formatCOSPackage(pkg packages.PkgInfo) *agentendpointpb.Inventory_SoftwarePackage_CosPackage { return &agentendpointpb.Inventory_SoftwarePackage_CosPackage{ CosPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: pkg.Name, Architecture: pkg.Arch, Version: pkg.Version, }} } func formatGooGetPackage(pkg packages.PkgInfo) *agentendpointpb.Inventory_SoftwarePackage_GoogetPackage { return &agentendpointpb.Inventory_SoftwarePackage_GoogetPackage{ GoogetPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: pkg.Name, Architecture: pkg.Arch, Version: pkg.Version, }} } func formatYumPackage(pkg packages.PkgInfo) *agentendpointpb.Inventory_SoftwarePackage_YumPackage { return &agentendpointpb.Inventory_SoftwarePackage_YumPackage{ YumPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: pkg.Name, Architecture: pkg.Arch, Version: pkg.Version}} } func formatZypperPackage(pkg packages.PkgInfo) *agentendpointpb.Inventory_SoftwarePackage_ZypperPackage { return &agentendpointpb.Inventory_SoftwarePackage_ZypperPackage{ ZypperPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: pkg.Name, Architecture: pkg.Arch, Version: pkg.Version}} } func formatZypperPatch(pkg packages.ZypperPatch) *agentendpointpb.Inventory_SoftwarePackage_ZypperPatch { return &agentendpointpb.Inventory_SoftwarePackage_ZypperPatch{ ZypperPatch: &agentendpointpb.Inventory_ZypperPatch{ PatchName: pkg.Name, Category: pkg.Category, Severity: pkg.Severity, Summary: pkg.Summary, }} } func formatWUAPackage(pkg packages.WUAPackage) *agentendpointpb.Inventory_SoftwarePackage_WuaPackage { var categories []*agentendpointpb.Inventory_WindowsUpdatePackage_WindowsUpdateCategory for idx, category := range pkg.Categories { categories = append(categories, &agentendpointpb.Inventory_WindowsUpdatePackage_WindowsUpdateCategory{ Id: pkg.CategoryIDs[idx], Name: category, }) } return &agentendpointpb.Inventory_SoftwarePackage_WuaPackage{ WuaPackage: &agentendpointpb.Inventory_WindowsUpdatePackage{ Title: pkg.Title, Description: pkg.Description, Categories: categories, KbArticleIds: pkg.KBArticleIDs, SupportUrl: pkg.SupportURL, MoreInfoUrls: pkg.MoreInfoURLs, UpdateId: pkg.UpdateID, RevisionNumber: pkg.RevisionNumber, LastDeploymentChangeTime: timestamppb.New(pkg.LastDeploymentChangeTime), }} } func formatQFEPackage(ctx context.Context, pkg packages.QFEPackage) *agentendpointpb.Inventory_SoftwarePackage_QfePackage { installedTime, err := time.Parse("1/2/2006", pkg.InstalledOn) if err != nil { clog.Warningf(ctx, "Error parsing QFE InstalledOn date: %v", err) } return &agentendpointpb.Inventory_SoftwarePackage_QfePackage{ QfePackage: &agentendpointpb.Inventory_WindowsQuickFixEngineeringPackage{ Caption: pkg.Caption, Description: pkg.Description, HotFixId: pkg.HotFixID, InstallTime: timestamppb.New(installedTime), }} } osconfig-20210219.00/agentendpoint/inventory_test.go000066400000000000000000000462721401404514100222700ustar00rootroot00000000000000// Copyright 2017 Google 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 agentendpoint import ( "bytes" "compress/gzip" "context" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "reflect" "testing" "time" "github.com/GoogleCloudPlatform/osconfig/inventory" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/google/go-cmp/cmp" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/timestamppb" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) type agentEndpointServiceInventoryTestServer struct { lastReportInventoryRequest *agentendpointpb.ReportInventoryRequest reportFullInventory bool } func (*agentEndpointServiceInventoryTestServer) ReceiveTaskNotification(req *agentendpointpb.ReceiveTaskNotificationRequest, srv agentendpointpb.AgentEndpointService_ReceiveTaskNotificationServer) error { return status.Errorf(codes.Unimplemented, "method ReceiveTaskNotification not implemented") } func (*agentEndpointServiceInventoryTestServer) StartNextTask(ctx context.Context, req *agentendpointpb.StartNextTaskRequest) (*agentendpointpb.StartNextTaskResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method StartNextTask not implemented") } func (*agentEndpointServiceInventoryTestServer) ReportTaskProgress(ctx context.Context, req *agentendpointpb.ReportTaskProgressRequest) (*agentendpointpb.ReportTaskProgressResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ReportTaskProgress not implemented") } func (*agentEndpointServiceInventoryTestServer) ReportTaskComplete(ctx context.Context, req *agentendpointpb.ReportTaskCompleteRequest) (*agentendpointpb.ReportTaskCompleteResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ReportTaskComplete not implemented") } func (*agentEndpointServiceInventoryTestServer) RegisterAgent(ctx context.Context, req *agentendpointpb.RegisterAgentRequest) (*agentendpointpb.RegisterAgentResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegisterAgent not implemented") } func (s *agentEndpointServiceInventoryTestServer) ReportInventory(ctx context.Context, req *agentendpointpb.ReportInventoryRequest) (*agentendpointpb.ReportInventoryResponse, error) { s.lastReportInventoryRequest = req resp := &agentendpointpb.ReportInventoryResponse{ReportFullInventory: s.reportFullInventory} if s.reportFullInventory { s.reportFullInventory = false } return resp, nil } func generateInventoryState(shortName string) *inventory.InstanceInventory { return &inventory.InstanceInventory{ Hostname: "Hostname", LongName: "LongName", ShortName: shortName, Version: "Version", Architecture: "Architecture", KernelVersion: "KernelVersion", KernelRelease: "KernelRelease", OSConfigAgentVersion: "OSConfigAgentVersion", InstalledPackages: &packages.Packages{ Yum: []packages.PkgInfo{{Name: "YumInstalledPkg", Arch: "Arch", Version: "Version"}}, Rpm: []packages.PkgInfo{{Name: "RpmInstalledPkg", Arch: "Arch", Version: "Version"}}, Apt: []packages.PkgInfo{{Name: "AptInstalledPkg", Arch: "Arch", Version: "Version"}}, Deb: []packages.PkgInfo{{Name: "DebInstalledPkg", Arch: "Arch", Version: "Version"}}, Zypper: []packages.PkgInfo{{Name: "ZypperInstalledPkg", Arch: "Arch", Version: "Version"}}, ZypperPatches: []packages.ZypperPatch{{Name: "ZypperInstalledPatch", Category: "Category", Severity: "Severity", Summary: "Summary"}}, Gem: []packages.PkgInfo{{Name: "GemInstalledPkg", Arch: "Arch", Version: "Version"}}, Pip: []packages.PkgInfo{{Name: "PipInstalledPkg", Arch: "Arch", Version: "Version"}}, GooGet: []packages.PkgInfo{{Name: "GooGetInstalledPkg", Arch: "Arch", Version: "Version"}}, WUA: []packages.WUAPackage{{ Title: "WUAInstalled", Description: "Description", Categories: []string{"Category"}, CategoryIDs: []string{"CategoryID"}, KBArticleIDs: []string{"KB"}, MoreInfoURLs: []string{"MoreInfoURL"}, SupportURL: "SupportURL", UpdateID: "UpdateID", RevisionNumber: 1, LastDeploymentChangeTime: time.Date(2020, time.November, 10, 23, 0, 0, 0, time.UTC)}}, QFE: []packages.QFEPackage{{Caption: "QFEInstalled", Description: "Description", HotFixID: "HotFixID", InstalledOn: "9/1/2020"}}, COS: []packages.PkgInfo{{Name: "CosInstalledPkg", Arch: "Arch", Version: "Version"}}, }, PackageUpdates: &packages.Packages{ Yum: []packages.PkgInfo{{Name: "YumPkgUpdate", Arch: "Arch", Version: "Version"}}, Rpm: []packages.PkgInfo{{Name: "RpmPkgUpdate", Arch: "Arch", Version: "Version"}}, Apt: []packages.PkgInfo{{Name: "AptPkgUpdate", Arch: "Arch", Version: "Version"}}, Deb: []packages.PkgInfo{{Name: "DebPkgUpdate", Arch: "Arch", Version: "Version"}}, Zypper: []packages.PkgInfo{{Name: "ZypperPkgUpdate", Arch: "Arch", Version: "Version"}}, ZypperPatches: []packages.ZypperPatch{{Name: "ZypperPatchUpdate", Category: "Category", Severity: "Severity", Summary: "Summary"}}, Gem: []packages.PkgInfo{{Name: "GemPkgUpdate", Arch: "Arch", Version: "Version"}}, Pip: []packages.PkgInfo{{Name: "PipPkgUpdate", Arch: "Arch", Version: "Version"}}, GooGet: []packages.PkgInfo{{Name: "GooGetPkgUpdate", Arch: "Arch", Version: "Version"}}, WUA: []packages.WUAPackage{{ Title: "WUAUpdate", Description: "Description", Categories: []string{"Category"}, CategoryIDs: []string{"CategoryID"}, KBArticleIDs: []string{"KB"}, MoreInfoURLs: []string{"MoreInfoURL"}, SupportURL: "SupportURL", UpdateID: "UpdateID", RevisionNumber: 1, LastDeploymentChangeTime: time.Time{}}}, QFE: []packages.QFEPackage{{Caption: "QFEUpdate", Description: "Description", HotFixID: "HotFixID", InstalledOn: "InvalidDate"}}, COS: []packages.PkgInfo{{Name: "CosInstalledPkg", Arch: "Arch", Version: "Version"}}, }, } } func generateInventory(shortName string) *agentendpointpb.Inventory { var mappedRPMInstalledPkg *agentendpointpb.Inventory_SoftwarePackage var mappedRPMPkgUpdate *agentendpointpb.Inventory_SoftwarePackage if shortName == "sles" { mappedRPMInstalledPkg = &agentendpointpb.Inventory_SoftwarePackage{ Details: &agentendpointpb.Inventory_SoftwarePackage_ZypperPackage{ ZypperPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "RpmInstalledPkg", Architecture: "Arch", Version: "Version"}}} mappedRPMPkgUpdate = &agentendpointpb.Inventory_SoftwarePackage{ Details: &agentendpointpb.Inventory_SoftwarePackage_ZypperPackage{ ZypperPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "RpmPkgUpdate", Architecture: "Arch", Version: "Version"}}} } else { mappedRPMInstalledPkg = &agentendpointpb.Inventory_SoftwarePackage{ Details: &agentendpointpb.Inventory_SoftwarePackage_YumPackage{ YumPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "RpmInstalledPkg", Architecture: "Arch", Version: "Version"}}} mappedRPMPkgUpdate = &agentendpointpb.Inventory_SoftwarePackage{ Details: &agentendpointpb.Inventory_SoftwarePackage_YumPackage{ YumPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "RpmPkgUpdate", Architecture: "Arch", Version: "Version"}}} } return &agentendpointpb.Inventory{ OsInfo: &agentendpointpb.Inventory_OsInfo{ Hostname: "Hostname", LongName: "LongName", ShortName: shortName, Version: "Version", Architecture: "Architecture", KernelVersion: "KernelVersion", KernelRelease: "KernelRelease", OsconfigAgentVersion: "OSConfigAgentVersion", }, InstalledPackages: []*agentendpointpb.Inventory_SoftwarePackage{ { Details: &agentendpointpb.Inventory_SoftwarePackage_AptPackage{ AptPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "AptInstalledPkg", Architecture: "Arch", Version: "Version"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_GoogetPackage{ GoogetPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "GooGetInstalledPkg", Architecture: "Arch", Version: "Version"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_YumPackage{ YumPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "YumInstalledPkg", Architecture: "Arch", Version: "Version"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_ZypperPackage{ ZypperPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "ZypperInstalledPkg", Architecture: "Arch", Version: "Version"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_ZypperPatch{ ZypperPatch: &agentendpointpb.Inventory_ZypperPatch{ PatchName: "ZypperInstalledPatch", Category: "Category", Severity: "Severity", Summary: "Summary"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_WuaPackage{ WuaPackage: &agentendpointpb.Inventory_WindowsUpdatePackage{ Title: "WUAInstalled", Description: "Description", Categories: []*agentendpointpb.Inventory_WindowsUpdatePackage_WindowsUpdateCategory{{ Id: "CategoryID", Name: "Category"}}, KbArticleIds: []string{"KB"}, SupportUrl: "SupportURL", MoreInfoUrls: []string{"MoreInfoURL"}, UpdateId: "UpdateID", RevisionNumber: 1, LastDeploymentChangeTime: timestamppb.New(time.Date(2020, time.November, 10, 23, 0, 0, 0, time.UTC)), }}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_QfePackage{ QfePackage: &agentendpointpb.Inventory_WindowsQuickFixEngineeringPackage{ Caption: "QFEInstalled", Description: "Description", HotFixId: "HotFixID", InstallTime: timestamppb.New(time.Date(2020, time.September, 1, 0, 0, 0, 0, time.UTC))}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_AptPackage{ AptPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "DebInstalledPkg", Architecture: "Arch", Version: "Version"}}}, mappedRPMInstalledPkg, { Details: &agentendpointpb.Inventory_SoftwarePackage_CosPackage{ CosPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "CosInstalledPkg", Architecture: "Arch", Version: "Version"}}}, }, AvailablePackages: []*agentendpointpb.Inventory_SoftwarePackage{ { Details: &agentendpointpb.Inventory_SoftwarePackage_AptPackage{ AptPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "AptPkgUpdate", Architecture: "Arch", Version: "Version"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_GoogetPackage{ GoogetPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "GooGetPkgUpdate", Architecture: "Arch", Version: "Version"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_YumPackage{ YumPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "YumPkgUpdate", Architecture: "Arch", Version: "Version"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_ZypperPackage{ ZypperPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "ZypperPkgUpdate", Architecture: "Arch", Version: "Version"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_ZypperPatch{ ZypperPatch: &agentendpointpb.Inventory_ZypperPatch{ PatchName: "ZypperPatchUpdate", Category: "Category", Severity: "Severity", Summary: "Summary"}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_WuaPackage{ WuaPackage: &agentendpointpb.Inventory_WindowsUpdatePackage{ Title: "WUAUpdate", Description: "Description", Categories: []*agentendpointpb.Inventory_WindowsUpdatePackage_WindowsUpdateCategory{{ Id: "CategoryID", Name: "Category"}}, KbArticleIds: []string{"KB"}, SupportUrl: "SupportURL", MoreInfoUrls: []string{"MoreInfoURL"}, UpdateId: "UpdateID", RevisionNumber: 1, LastDeploymentChangeTime: timestamppb.New(time.Time{})}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_QfePackage{ QfePackage: &agentendpointpb.Inventory_WindowsQuickFixEngineeringPackage{ Caption: "QFEUpdate", Description: "Description", HotFixId: "HotFixID", InstallTime: timestamppb.New(time.Time{})}}}, { Details: &agentendpointpb.Inventory_SoftwarePackage_AptPackage{ AptPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "DebPkgUpdate", Architecture: "Arch", Version: "Version"}}}, mappedRPMPkgUpdate, { Details: &agentendpointpb.Inventory_SoftwarePackage_CosPackage{ CosPackage: &agentendpointpb.Inventory_VersionedPackage{ PackageName: "CosInstalledPkg", Architecture: "Arch", Version: "Version"}}}, }, } } func decodePackages(str string) *packages.Packages { decoded, _ := base64.StdEncoding.DecodeString(str) zr, _ := gzip.NewReader(bytes.NewReader(decoded)) var buf bytes.Buffer io.Copy(&buf, zr) zr.Close() var pkgs packages.Packages json.Unmarshal(buf.Bytes(), &pkgs) return &pkgs } func TestWrite(t *testing.T) { inv := &inventory.InstanceInventory{ Hostname: "Hostname", LongName: "LongName", ShortName: "ShortName", Architecture: "Architecture", KernelVersion: "KernelVersion", KernelRelease: "KernelRelease", Version: "Version", InstalledPackages: &packages.Packages{ Yum: []packages.PkgInfo{{Name: "Name", Arch: "Arch", Version: "Version"}}, WUA: []packages.WUAPackage{{Title: "Title"}}, QFE: []packages.QFEPackage{{HotFixID: "HotFixID"}}, }, PackageUpdates: &packages.Packages{ Apt: []packages.PkgInfo{{Name: "Name", Arch: "Arch", Version: "Version"}}, }, OSConfigAgentVersion: "OSConfigAgentVersion", LastUpdated: "LastUpdated", } want := map[string]bool{ "Hostname": false, "LongName": false, "ShortName": false, "Architecture": false, "KernelVersion": false, "Version": false, "InstalledPackages": false, "PackageUpdates": false, "OSConfigAgentVersion": false, } svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := r.URL.String() buf := new(bytes.Buffer) if _, err := buf.ReadFrom(r.Body); err != nil { t.Fatal(err) } switch url { case "/Hostname": if buf.String() != inv.Hostname { t.Errorf("did not get expected Hostname, got: %q, want: %q", buf.String(), inv.Hostname) } want["Hostname"] = true case "/LongName": if buf.String() != inv.LongName { t.Errorf("did not get expected LongName, got: %q, want: %q", buf.String(), inv.LongName) } want["LongName"] = true case "/ShortName": if buf.String() != inv.ShortName { t.Errorf("did not get expected ShortName, got: %q, want: %q", buf.String(), inv.ShortName) } want["ShortName"] = true case "/Architecture": if buf.String() != inv.Architecture { t.Errorf("did not get expected Architecture, got: %q, want: %q", buf.String(), inv.Architecture) } want["Architecture"] = true case "/KernelVersion": if buf.String() != inv.KernelVersion { t.Errorf("did not get expected KernelVersion, got: %q, want: %q", buf.String(), inv.KernelVersion) } want["KernelVersion"] = true case "/KernelRelease": if buf.String() != inv.KernelRelease { t.Errorf("did not get expected KernelRelease, got: %q, want: %q", buf.String(), inv.KernelRelease) } want["KernelRelease"] = true case "/Version": if buf.String() != inv.Version { t.Errorf("did not get expected Version, got: %q, want: %q", buf.String(), inv.Version) } want["Version"] = true case "/InstalledPackages": got := decodePackages(buf.String()) if !reflect.DeepEqual(got, inv.InstalledPackages) { t.Errorf("did not get expected InstalledPackages, got: %+v, want: %+v", got, inv.InstalledPackages) } want["InstalledPackages"] = true case "/PackageUpdates": got := decodePackages(buf.String()) if !reflect.DeepEqual(got, inv.PackageUpdates) { t.Errorf("did not get expected PackageUpdates, got: %+v, want: %+v", got, inv.PackageUpdates) } want["PackageUpdates"] = true case "/OSConfigAgentVersion": if buf.String() != inv.OSConfigAgentVersion { t.Errorf("did not get expected OSConfigAgentVersion, got: %q, want: %q", buf.String(), inv.OSConfigAgentVersion) } want["OSConfigAgentVersion"] = true case "/LastUpdated": if buf.String() != inv.LastUpdated { t.Errorf("did not get expected LastUpdated, got: %q, want: %q", buf.String(), inv.LastUpdated) } want["LastUpdated"] = true default: w.WriteHeader(500) fmt.Fprintln(w, "URL and Method not recognized:", r.Method, url) } })) defer svr.Close() ctx := context.Background() write(ctx, inv, svr.URL) for k, v := range want { if v { continue } t.Errorf("writeInventory call did not write %q", k) } } func TestReport(t *testing.T) { ctx := context.Background() srv := &agentEndpointServiceInventoryTestServer{} tc, err := newTestClient(ctx, srv) if err != nil { t.Fatal(err) } defer tc.close() tests := []struct { name string reportFullInventory bool inventoryState *inventory.InstanceInventory wantInventory *agentendpointpb.Inventory }{ {"ReportChecksumOnly", false, generateInventoryState("rhel"), nil}, {"ReportFullInventoryRpmMapppingToYum", true, generateInventoryState("ubuntu"), generateInventory("ubuntu")}, {"ReportFullInventoryRpmMappingToZypper", true, generateInventoryState("sles"), generateInventory("sles")}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { srv.reportFullInventory = tt.reportFullInventory tc.client.report(ctx, tt.inventoryState) if diff := cmp.Diff(tt.wantInventory, srv.lastReportInventoryRequest.Inventory, protocmp.Transform()); diff != "" { t.Fatalf("ReportInventoryRequest.Inventory mismatch (-want +got):\n%s", diff) } }) } } osconfig-20210219.00/agentendpoint/patch_linux.go000066400000000000000000000071121401404514100215000ustar00rootroot00000000000000// Copyright 2018 Google 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 agentendpoint import ( "context" "errors" "strings" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/ospatch" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/GoogleCloudPlatform/osconfig/retryutil" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) func (r *patchTask) runUpdates(ctx context.Context) error { var errs []string const retryPeriod = 3 * time.Minute // Check for both apt-get and dpkg-query to give us a clean signal. if packages.AptExists && packages.DpkgQueryExists { opts := []ospatch.AptGetUpgradeOption{ ospatch.AptGetDryRun(r.Task.GetDryRun()), ospatch.AptGetExcludes(r.Task.GetPatchConfig().GetApt().GetExcludes()), ospatch.AptGetExclusivePackages(r.Task.GetPatchConfig().GetApt().GetExclusivePackages()), } switch r.Task.GetPatchConfig().GetApt().GetType() { case agentendpointpb.AptSettings_DIST: opts = append(opts, ospatch.AptGetUpgradeType(packages.AptGetDistUpgrade)) } clog.Debugf(ctx, "Installing APT package updates.") if err := retryutil.RetryFunc(ctx, retryPeriod, "installing APT package updates", func() error { return ospatch.RunAptGetUpgrade(ctx, opts...) }); err != nil { errs = append(errs, err.Error()) } } if packages.YumExists && packages.RPMQueryExists { opts := []ospatch.YumUpdateOption{ ospatch.YumUpdateSecurity(r.Task.GetPatchConfig().GetYum().GetSecurity()), ospatch.YumUpdateMinimal(r.Task.GetPatchConfig().GetYum().GetMinimal()), ospatch.YumUpdateExcludes(r.Task.GetPatchConfig().GetYum().GetExcludes()), ospatch.YumExclusivePackages(r.Task.GetPatchConfig().GetYum().GetExclusivePackages()), ospatch.YumDryRun(r.Task.GetDryRun()), } clog.Debugf(ctx, "Installing YUM package updates.") if err := retryutil.RetryFunc(ctx, retryPeriod, "installing YUM package updates", func() error { return ospatch.RunYumUpdate(ctx, opts...) }); err != nil { errs = append(errs, err.Error()) } } if packages.ZypperExists && packages.RPMQueryExists { opts := []ospatch.ZypperPatchOption{ ospatch.ZypperPatchCategories(r.Task.GetPatchConfig().GetZypper().GetCategories()), ospatch.ZypperPatchSeverities(r.Task.GetPatchConfig().GetZypper().GetSeverities()), ospatch.ZypperUpdateWithUpdate(r.Task.GetPatchConfig().GetZypper().GetWithUpdate()), ospatch.ZypperUpdateWithOptional(r.Task.GetPatchConfig().GetZypper().GetWithOptional()), ospatch.ZypperUpdateWithExcludes(r.Task.GetPatchConfig().GetZypper().GetExcludes()), ospatch.ZypperUpdateWithExclusivePatches(r.Task.GetPatchConfig().GetZypper().GetExclusivePatches()), ospatch.ZypperUpdateDryrun(r.Task.GetDryRun()), } clog.Debugf(ctx, "Installing Zypper updates.") if err := retryutil.RetryFunc(ctx, retryPeriod, "installing Zypper updates", func() error { return ospatch.RunZypperPatch(ctx, opts...) }); err != nil { errs = append(errs, err.Error()) } } if errs == nil { return nil } return errors.New(strings.Join(errs, ",\n")) } osconfig-20210219.00/agentendpoint/patch_task.go000066400000000000000000000231471401404514100213110ustar00rootroot00000000000000// Copyright 2018 Google 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 agentendpoint import ( "context" "fmt" "time" "github.com/GoogleCloudPlatform/osconfig/agentconfig" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/ospatch" "google.golang.org/protobuf/encoding/protojson" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) func systemRebootRequired(ctx context.Context) (bool, error) { return ospatch.SystemRebootRequired(ctx) } type patchStep string const ( prePatch = "PrePatch" patching = "Patching" postPatch = "PostPatch" ) type patchTask struct { client *Client lastProgressState map[agentendpointpb.ApplyPatchesTaskProgress_State]time.Time state *taskState TaskID string Task *applyPatchesTask StartedAt time.Time `json:",omitempty"` PatchStep patchStep `json:",omitempty"` RebootCount int // TODO: add Attempts and track number of retries with backoff, jitter, etc. } func (r *patchTask) saveState() error { r.state.PatchTask = r return r.state.save(taskStateFile) } func (r *patchTask) complete(ctx context.Context) { if err := (&taskState{}).save(taskStateFile); err != nil { clog.Errorf(ctx, "Error saving state: %v", err) } } type applyPatchesTask struct { *agentendpointpb.ApplyPatchesTask } // MarshalJSON marshals a patchConfig using protojson. func (a *applyPatchesTask) MarshalJSON() ([]byte, error) { m := &protojson.MarshalOptions{AllowPartial: true, EmitUnpopulated: false} return m.Marshal(a) } // UnmarshalJSON unmarshals a patchConfig using protojson. func (a *applyPatchesTask) UnmarshalJSON(b []byte) error { a.ApplyPatchesTask = &agentendpointpb.ApplyPatchesTask{} un := &protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true} return un.Unmarshal(b, a.ApplyPatchesTask) } func (r *patchTask) setStep(step patchStep) error { r.PatchStep = step if err := r.saveState(); err != nil { return fmt.Errorf("error saving state: %v", err) } return nil } func (r *patchTask) handleErrorState(ctx context.Context, msg string, err error) error { if err == errServerCancel { return r.reportCanceled(ctx) } return r.reportFailed(ctx, msg) } func (r *patchTask) reportFailed(ctx context.Context, msg string) error { clog.Errorf(ctx, msg) return r.reportCompletedState(ctx, msg, &agentendpointpb.ReportTaskCompleteRequest_ApplyPatchesTaskOutput{ ApplyPatchesTaskOutput: &agentendpointpb.ApplyPatchesTaskOutput{State: agentendpointpb.ApplyPatchesTaskOutput_FAILED}, }) } func (r *patchTask) reportCanceled(ctx context.Context) error { clog.Infof(ctx, "Canceling patch execution") return r.reportCompletedState(ctx, errServerCancel.Error(), &agentendpointpb.ReportTaskCompleteRequest_ApplyPatchesTaskOutput{ // Is this right? Maybe there should be a canceled state instead. ApplyPatchesTaskOutput: &agentendpointpb.ApplyPatchesTaskOutput{State: agentendpointpb.ApplyPatchesTaskOutput_FAILED}, }) } func (r *patchTask) reportCompletedState(ctx context.Context, errMsg string, output *agentendpointpb.ReportTaskCompleteRequest_ApplyPatchesTaskOutput) error { req := &agentendpointpb.ReportTaskCompleteRequest{ TaskId: r.TaskID, TaskType: agentendpointpb.TaskType_APPLY_PATCHES, ErrorMessage: errMsg, Output: output, } if err := r.client.reportTaskComplete(ctx, req); err != nil { return fmt.Errorf("error reporting completed state: %v", err) } return nil } func (r *patchTask) reportContinuingState(ctx context.Context, patchState agentendpointpb.ApplyPatchesTaskProgress_State) error { st, ok := r.lastProgressState[patchState] if ok && st.After(time.Now().Add(sameStateTimeWindow)) { // Don't resend the same state more than once every 5s. return nil } req := &agentendpointpb.ReportTaskProgressRequest{ TaskId: r.TaskID, TaskType: agentendpointpb.TaskType_APPLY_PATCHES, Progress: &agentendpointpb.ReportTaskProgressRequest_ApplyPatchesTaskProgress{ ApplyPatchesTaskProgress: &agentendpointpb.ApplyPatchesTaskProgress{State: patchState}, }, } res, err := r.client.reportTaskProgress(ctx, req) if err != nil { return fmt.Errorf("error reporting state %s: %v", patchState, err) } if res.GetTaskDirective() == agentendpointpb.TaskDirective_STOP { return errServerCancel } if r.lastProgressState == nil { r.lastProgressState = make(map[agentendpointpb.ApplyPatchesTaskProgress_State]time.Time) } r.lastProgressState[patchState] = time.Now() return r.saveState() } // TODO: Add MaxRebootCount so we don't loop endlessly. func (r *patchTask) prePatchReboot(ctx context.Context) error { return r.rebootIfNeeded(ctx, true) } func (r *patchTask) postPatchReboot(ctx context.Context) error { return r.rebootIfNeeded(ctx, false) } func (r *patchTask) rebootIfNeeded(ctx context.Context, prePatch bool) error { var reboot bool var err error if r.Task.GetPatchConfig().GetRebootConfig() == agentendpointpb.PatchConfig_ALWAYS && !prePatch && r.RebootCount == 0 { reboot = true clog.Infof(ctx, "PatchConfig RebootConfig set to %s.", agentendpointpb.PatchConfig_ALWAYS) } else { reboot, err = systemRebootRequired(ctx) if err != nil { return fmt.Errorf("error checking if a system reboot is required: %v", err) } if reboot { clog.Infof(ctx, "System indicates a reboot is required.") } else { clog.Infof(ctx, "System indicates a reboot is not required.") } } if !reboot { return nil } if r.Task.GetPatchConfig().GetRebootConfig() == agentendpointpb.PatchConfig_NEVER { clog.Infof(ctx, "Skipping reboot because of PatchConfig RebootConfig set to %s.", agentendpointpb.PatchConfig_NEVER) return nil } if err := r.reportContinuingState(ctx, agentendpointpb.ApplyPatchesTaskProgress_REBOOTING); err != nil { return err } if r.Task.GetDryRun() { clog.Infof(ctx, "Dry run - not rebooting for patch task") return nil } r.RebootCount++ if err := r.saveState(); err != nil { return fmt.Errorf("error saving state: %v", err) } if err := rebootSystem(); err != nil { return fmt.Errorf("failed to reboot system: %v", err) } // Reboot can take a bit, pause here so other activities don't start. for { clog.Debugf(ctx, "Waiting for system reboot.") time.Sleep(1 * time.Minute) } } func (r *patchTask) run(ctx context.Context) (err error) { ctx = clog.WithLabels(ctx, r.state.Labels) clog.Infof(ctx, "Beginning patch task") defer func() { // This should not happen but the WUA libraries are complicated and // recovering with an error is better than crashing. if rec := recover(); rec != nil { err = fmt.Errorf("Recovered from panic: %v", rec) r.reportFailed(ctx, err.Error()) return } r.complete(ctx) if agentconfig.OSInventoryEnabled() { go r.client.ReportInventory(ctx) } }() for { clog.Debugf(ctx, "Running PatchStep %q.", r.PatchStep) switch r.PatchStep { default: return r.reportFailed(ctx, fmt.Sprintf("unknown step: %q", r.PatchStep)) case prePatch: r.StartedAt = time.Now() if err := r.setStep(patching); err != nil { return r.reportFailed(ctx, fmt.Sprintf("Error saving agent step: %v", err)) } if err := r.reportContinuingState(ctx, agentendpointpb.ApplyPatchesTaskProgress_STARTED); err != nil { return r.handleErrorState(ctx, err.Error(), err) } if err := r.prePatchReboot(ctx); err != nil { return r.handleErrorState(ctx, fmt.Sprintf("Error running prePatchReboot: %v", err), err) } case patching: if err := r.reportContinuingState(ctx, agentendpointpb.ApplyPatchesTaskProgress_APPLYING_PATCHES); err != nil { return r.handleErrorState(ctx, err.Error(), err) } if err := r.runUpdates(ctx); err != nil { return r.handleErrorState(ctx, fmt.Sprintf("Failed to apply patches: %v", err), err) } if err := r.postPatchReboot(ctx); err != nil { return r.handleErrorState(ctx, fmt.Sprintf("Error running postPatchReboot: %v", err), err) } // We have not rebooted so patching is complete. if err := r.setStep(postPatch); err != nil { return r.reportFailed(ctx, fmt.Sprintf("Error saving agent step: %v", err)) } case postPatch: isRebootRequired, err := systemRebootRequired(ctx) if err != nil { return r.reportFailed(ctx, fmt.Sprintf("Error checking if system reboot is required: %v", err)) } finalState := agentendpointpb.ApplyPatchesTaskOutput_SUCCEEDED if isRebootRequired { finalState = agentendpointpb.ApplyPatchesTaskOutput_SUCCEEDED_REBOOT_REQUIRED } if err := r.reportCompletedState(ctx, "", &agentendpointpb.ReportTaskCompleteRequest_ApplyPatchesTaskOutput{ ApplyPatchesTaskOutput: &agentendpointpb.ApplyPatchesTaskOutput{State: finalState}, }); err != nil { return fmt.Errorf("failed to report state %s: %v", finalState, err) } clog.Infof(ctx, "Successfully completed patch task") return nil } } } // RunApplyPatches runs a apply patches task. func (c *Client) RunApplyPatches(ctx context.Context, task *agentendpointpb.Task) error { r := &patchTask{ state: &taskState{Labels: task.GetServiceLabels()}, TaskID: task.GetTaskId(), client: c, Task: &applyPatchesTask{task.GetApplyPatchesTask()}, } r.setStep(prePatch) return r.run(ctx) } osconfig-20210219.00/agentendpoint/patch_windows.go000066400000000000000000000116551401404514100220420ustar00rootroot00000000000000// Copyright 2018 Google 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. // +build windows package agentendpoint import ( "context" "fmt" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/ospatch" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/GoogleCloudPlatform/osconfig/retryutil" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) func (r *patchTask) classFilter() ([]string, error) { var classifications = map[agentendpointpb.WindowsUpdateSettings_Classification]string{ agentendpointpb.WindowsUpdateSettings_CRITICAL: "e6cf1350-c01b-414d-a61f-263d14d133b4", agentendpointpb.WindowsUpdateSettings_SECURITY: "0fa1201d-4330-4fa8-8ae9-b877473b6441", agentendpointpb.WindowsUpdateSettings_DEFINITION: "e0789628-ce08-4437-be74-2495b842f43b", agentendpointpb.WindowsUpdateSettings_DRIVER: "ebfc1fc5-71a4-4f7b-9aca-3b9a503104a0", agentendpointpb.WindowsUpdateSettings_FEATURE_PACK: "b54e7d24-7add-428f-8b75-90a396fa584f", agentendpointpb.WindowsUpdateSettings_SERVICE_PACK: "68c5b0a3-d1a6-4553-ae49-01d3a7827828", agentendpointpb.WindowsUpdateSettings_TOOL: "b4832bd8-e735-4761-8daf-37f882276dab", agentendpointpb.WindowsUpdateSettings_UPDATE_ROLLUP: "28bc880e-0592-4cbf-8f95-c79b17911d5f", agentendpointpb.WindowsUpdateSettings_UPDATE: "cd5ffd1e-e932-4e3a-bf74-18bf0b1bbd83", } var cf []string for _, c := range r.Task.GetPatchConfig().GetWindowsUpdate().GetClassifications() { sc, ok := classifications[c] if !ok { return nil, fmt.Errorf("Unknown classification: %s", c) } cf = append(cf, sc) } return cf, nil } func (r *patchTask) installWUAUpdates(ctx context.Context, cf []string) (int32, error) { clog.Infof(ctx, "Searching for available Windows updates.") session, err := packages.NewUpdateSession() if err != nil { return 0, err } defer session.Close() updts, err := ospatch.GetWUAUpdates(ctx, session, cf, r.Task.GetPatchConfig().GetWindowsUpdate().GetExcludes(), r.Task.GetPatchConfig().GetWindowsUpdate().GetExclusivePatches()) if err != nil { return 0, err } defer updts.Release() count, err := updts.Count() if err != nil { return 0, err } if count == 0 { clog.Infof(ctx, "No Windows updates available to install") return 0, nil } clog.Infof(ctx, "%d Windows updates to install", count) if r.Task.GetDryRun() { clog.Infof(ctx, "Running in dryrun mode, not updating.") return 0, nil } for i := int32(0); i < count; i++ { if err := r.reportContinuingState(ctx, agentendpointpb.ApplyPatchesTaskProgress_APPLYING_PATCHES); err != nil { return i, err } updt, err := updts.Item(int(i)) if err != nil { return i, err } defer updt.Release() if err := session.InstallWUAUpdate(ctx, updt); err != nil { return i, fmt.Errorf(`installUpdate(updt): %v`, err) } } return count, nil } func (r *patchTask) wuaUpdates(ctx context.Context) error { cf, err := r.classFilter() if err != nil { return err } // We keep searching for and installing updates until the count == 0, // we get a stop signal, or retries exceed 10. retries := 10 for i := 1; i <= retries; i++ { if err := r.reportContinuingState(ctx, agentendpointpb.ApplyPatchesTaskProgress_APPLYING_PATCHES); err != nil { return err } count, err := r.installWUAUpdates(ctx, cf) if err != nil { clog.Errorf(ctx, "Error installing Windows updates (attempt %d): %v", i, err) time.Sleep(60 * time.Second) continue } if count == 0 { return nil } } return fmt.Errorf("failed to install all updates after trying %d times", retries) } func (r *patchTask) runUpdates(ctx context.Context) error { // Install GooGet updates first as this will allow us to update the agent prior to any potential WUA bugs/errors. if packages.GooGetExists { if err := r.reportContinuingState(ctx, agentendpointpb.ApplyPatchesTaskProgress_APPLYING_PATCHES); err != nil { return err } clog.Debugf(ctx, "Installing GooGet package updates.") opts := []ospatch.GooGetUpdateOption{ ospatch.GooGetDryRun(r.Task.GetDryRun()), } if err := retryutil.RetryFunc(ctx, 3*time.Minute, "installing GooGet package updates", func() error { return ospatch.RunGooGetUpdate(ctx, opts...) }); err != nil { return err } } // Don't use retry function as wuaUpdates handles it's own retries. if err := r.wuaUpdates(ctx); err != nil { return err } return nil } osconfig-20210219.00/agentendpoint/reboot_linux.go000066400000000000000000000024111401404514100216700ustar00rootroot00000000000000// Copyright 2018 Google 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 agentendpoint import ( "os/exec" "syscall" "github.com/GoogleCloudPlatform/osconfig/util" ) const ( systemctl = "/bin/systemctl" reboot = "/bin/reboot" shutdown = "/bin/shutdown" ) func rebootSystem() error { // Start with systemctl and work down a list of reboot methods. if e := util.Exists(systemctl); e { return exec.Command(systemctl, "reboot").Start() } if e := util.Exists(reboot); e { return exec.Command(reboot).Run() } if e := util.Exists(shutdown); e { return exec.Command(shutdown, "-r", "-t", "0").Run() } // Fall back to reboot(2) system call syscall.Sync() return syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) } osconfig-20210219.00/agentendpoint/reboot_windows.go000066400000000000000000000016351401404514100222320ustar00rootroot00000000000000// Copyright 2018 Google 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. // +build windows package agentendpoint import ( "os" "os/exec" "path/filepath" ) func rebootSystem() error { root := os.Getenv("SystemRoot") if root == "" { root = `C:\Windows` } return exec.Command(filepath.Join(root, `System32\shutdown.exe`), "/r", "/t", "00", "/f", "/d", "p:2:3").Run() } osconfig-20210219.00/agentendpoint/task_state.go000066400000000000000000000034271401404514100213310ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint import ( "encoding/json" "io/ioutil" "os" "path/filepath" ) type taskState struct { PatchTask *patchTask `json:",omitempty"` // Reboots during ExecTask is not supported. ExecTask *execTask `json:",omitempty"` Labels map[string]string `json:",omitempty"` } func (s *taskState) save(path string) error { if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return err } if s == nil { return writeFile(path, []byte("{}")) } d, err := json.Marshal(s) if err != nil { return err } return writeFile(path, d) } func loadState(path string) (*taskState, error) { d, err := ioutil.ReadFile(path) if os.IsNotExist(err) { return nil, nil } if err != nil { return nil, err } var st taskState return &st, json.Unmarshal(d, &st) } func writeFile(path string, data []byte) error { // Write state to a temporary file first. tmp, err := ioutil.TempFile(filepath.Dir(path), "") if err != nil { return err } newStateFile := tmp.Name() if _, err = tmp.Write(data); err != nil { return err } if err := tmp.Close(); err != nil { return err } // Move the new temp file to the live path. return os.Rename(newStateFile, path) } osconfig-20210219.00/agentendpoint/task_state_test.go000066400000000000000000000120361401404514100223640ustar00rootroot00000000000000// Copyright 2019 Google 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 agentendpoint import ( "io/ioutil" "os" "path/filepath" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/testing/protocmp" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) var ( testPatchTaskStateString = "{\"PatchTask\":{\"TaskID\":\"foo\",\"Task\":{\"patchConfig\":{\"apt\":{\"type\":\"DIST\",\"excludes\":[\"foo\",\"bar\"],\"exclusivePackages\":[\"foo\",\"bar\"]},\"windowsUpdate\":{\"classifications\":[\"CRITICAL\",\"SECURITY\"],\"excludes\":[\"foo\",\"bar\"],\"exclusivePatches\":[\"foo\",\"bar\"]}}},\"StartedAt\":\"0001-01-01T00:00:00Z\",\"RebootCount\":0},\"Labels\":{\"foo\":\"bar\"}}" testPatchTaskState = &taskState{ Labels: map[string]string{"foo": "bar"}, PatchTask: &patchTask{ TaskID: "foo", Task: &applyPatchesTask{ // This is not exhaustive but it's a good test for having multiple settings. &agentendpointpb.ApplyPatchesTask{ PatchConfig: &agentendpointpb.PatchConfig{ Apt: &agentendpointpb.AptSettings{Type: agentendpointpb.AptSettings_DIST, Excludes: []string{"foo", "bar"}, ExclusivePackages: []string{"foo", "bar"}}, WindowsUpdate: &agentendpointpb.WindowsUpdateSettings{Classifications: []agentendpointpb.WindowsUpdateSettings_Classification{agentendpointpb.WindowsUpdateSettings_CRITICAL, agentendpointpb.WindowsUpdateSettings_SECURITY}, Excludes: []string{"foo", "bar"}, ExclusivePatches: []string{"foo", "bar"}}, }, }, }, }, } ) func TestLoadState(t *testing.T) { td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { t.Fatalf("error creating temp dir: %v", err) } defer os.RemoveAll(td) testState := filepath.Join(td, "testState") // test no state file if _, err := loadState(testState); err != nil { t.Errorf("no state file: unexpected error: %v", err) } // We don't test execTask as reboots during that task type is not supported. var tests = []struct { name string state []byte wantErr bool want *taskState }{ { "BlankState", []byte("{}"), false, &taskState{}, }, { "BadState", []byte("foo"), true, &taskState{}, }, { "PatchTask", []byte(testPatchTaskStateString), false, testPatchTaskState, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := ioutil.WriteFile(testState, tt.state, 0600); err != nil { t.Fatalf("error writing state: %v", err) } st, err := loadState(testState) if err != nil && !tt.wantErr { t.Fatalf("unexpected error: %v", err) } if err == nil && tt.wantErr { t.Fatalf("expected error") } if diff := cmp.Diff(tt.want, st, cmpopts.IgnoreUnexported(patchTask{}), protocmp.Transform()); diff != "" { t.Errorf("State does not match expectation: (-got +want)\n%s", diff) } }) } } func TestStateSave(t *testing.T) { td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { t.Fatalf("error creating temp dir: %v", err) } defer os.RemoveAll(td) testState := filepath.Join(td, "testState") var tests = []struct { desc string state *taskState want string }{ { "NilState", nil, "{}", }, { "BlankState", &taskState{}, "{}", }, { "PatchTask", testPatchTaskState, testPatchTaskStateString, }, { "ExecTask", &taskState{ExecTask: &execTask{TaskID: "foo"}}, "{\"ExecTask\":{\"TaskID\":\"foo\",\"Task\":null,\"StartedAt\":\"0001-01-01T00:00:00Z\"}}", }, } for _, tt := range tests { err := tt.state.save(testState) if err != nil { t.Errorf("%s: unexpected save error: %v", tt.desc, err) continue } got, err := ioutil.ReadFile(testState) if err != nil { t.Errorf("%s: error reading state: %v", tt.desc, err) continue } if string(got) != tt.want { t.Errorf("%s:\ngot:\n%q\nwant:\n%q", tt.desc, got, tt.want) } } } func TestSaveLoadState(t *testing.T) { td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { t.Fatalf("error creating temp dir: %v", err) } defer os.RemoveAll(td) testState := filepath.Join(td, "testState") if err := testPatchTaskState.save(testState); err != nil { t.Errorf("Unexpected save error: %v", err) } st, err := loadState(testState) if err != nil { t.Fatalf("Unexpected load error: %v", err) } if diff := cmp.Diff(testPatchTaskState, st, cmpopts.IgnoreUnexported(patchTask{}), protocmp.Transform()); diff != "" { t.Errorf("State does not match expectation: (-got +want)\n%s", diff) } } osconfig-20210219.00/attributes/000077500000000000000000000000001401404514100161615ustar00rootroot00000000000000osconfig-20210219.00/attributes/attributes.go000066400000000000000000000036361401404514100207060ustar00rootroot00000000000000// Copyright 2018 Google 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 attributes posts data to Guest Attributes. package attributes import ( "bytes" "compress/gzip" "encoding/base64" "encoding/json" "fmt" "io" "io/ioutil" "net/http" ) // PostAttribute posts data to Guest Attributes func PostAttribute(url string, value io.Reader) error { req, err := http.NewRequest("PUT", url, value) if err != nil { return err } req.Header.Add("Metadata-Flavor", "Google") resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, err := ioutil.ReadAll(resp.Body) responseErr := fmt.Sprintf(`received status code %q for request "%s %s"`, resp.Status, req.Method, req.URL.String()) if err == nil { responseErr = fmt.Sprintf("%s\n Error response: %s", responseErr, string(b)) } return fmt.Errorf("%s", responseErr) } return nil } // PostAttributeCompressed compresses and posts data to Guest Attributes func PostAttributeCompressed(url string, body interface{}) error { buf := &bytes.Buffer{} b := base64.NewEncoder(base64.StdEncoding, buf) zw := gzip.NewWriter(b) w := json.NewEncoder(zw) if err := w.Encode(body); err != nil { return err } if err := zw.Close(); err != nil { return err } if err := b.Close(); err != nil { return err } return PostAttribute(url, buf) } osconfig-20210219.00/attributes/attributes_test.go000066400000000000000000000077521401404514100217500ustar00rootroot00000000000000// Copyright 2019 Google 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 attributes import ( "bytes" "compress/gzip" "encoding/base64" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/http/httptest" "strings" "testing" "github.com/GoogleCloudPlatform/osconfig/packages" ) func TestPostAttributeHappyCase(t *testing.T) { testData := "test bytes" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { buf := new(bytes.Buffer) buf.ReadFrom(r.Body) newStr := buf.String() if strings.Compare(testData, newStr) != 0 { // this is just a way to notify client that the data // recieved was different than what was sent w.WriteHeader(http.StatusExpectationFailed) } else { w.WriteHeader(http.StatusOK) } })) defer ts.Close() if err := PostAttribute(ts.URL, strings.NewReader(testData)); err != nil { // PostAttribute throw error if status is not 200 t.Errorf("test failed, should not be an error; got(%s)", err.Error()) } } func TestPostAttributeInvalidUrl(t *testing.T) { err := PostAttribute("http://foo.com/ctl\x80", nil) if err == nil { t.Errorf("test failed, Should be an error") } } func TestPostAttributeStatusNotOk(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) })) defer ts.Close() err := PostAttribute(ts.URL, nil) if err == nil || !strings.Contains(err.Error(), "400 Bad Request") { t.Errorf("test failed, Should be (400 bad request; got(%+v))", err) } } func TestPostAttributeCompressedhappyCase(t *testing.T) { td := packages.Packages{ Apt: []packages.PkgInfo{ { Version: "1.2.3", Name: "test-package", Arch: "amd64", }, }, } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusExpectationFailed) w.Write([]byte("error reading body")) return } pkg, err := getDecompressPackageInfo(string(body)) if td.Apt[0].Name != pkg.Apt[0].Name { w.WriteHeader(http.StatusExpectationFailed) w.Write([]byte(fmt.Sprintf("assert failed! expected(%s)! got(%s)!", td.Apt[0].Name, pkg.Apt[0].Name))) } if td.Apt[0].Version != pkg.Apt[0].Version { w.WriteHeader(http.StatusExpectationFailed) w.Write([]byte(fmt.Sprintf("assert failed! expected(%s)! got(%s)!", td.Apt[0].Version, pkg.Apt[0].Version))) } if td.Apt[0].Arch != pkg.Apt[0].Arch { w.WriteHeader(http.StatusExpectationFailed) w.Write([]byte(fmt.Sprintf("assert failed! expected(%s)! got(%s)!", td.Apt[0].Arch, pkg.Apt[0].Arch))) } })) err := PostAttributeCompressed(ts.URL, td) if err != nil { t.Errorf("test failed, should not be an error; got(%v)", err) } } func getDecompressPackageInfo(encoded string) (*packages.Packages, error) { decoded, err := base64.StdEncoding.DecodeString(encoded) if err != nil { return nil, fmt.Errorf("Error decoding base64: %+v", err) } gzipReader, err := gzip.NewReader(bytes.NewReader(decoded)) if err != nil { return nil, fmt.Errorf("Error creating gzip reader: %+v", err) } defer gzipReader.Close() var buf bytes.Buffer if _, err := io.Copy(&buf, gzipReader); err != nil { return nil, fmt.Errorf("Error reading gzip data: %+v", err) } var pkgs packages.Packages if err := json.Unmarshal(buf.Bytes(), &pkgs); err != nil { return nil, fmt.Errorf("Error unmarshalling json data: %+v", err) } return &pkgs, nil } osconfig-20210219.00/clog/000077500000000000000000000000001401404514100147175ustar00rootroot00000000000000osconfig-20210219.00/clog/clog.go000066400000000000000000000056051401404514100162000ustar00rootroot00000000000000// Copyright 2020 Google 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 clog is a Context logger. package clog import ( "context" "fmt" "sync" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) // https://golang.org/pkg/context/#WithValue type clogKey struct{} var ctxValueKey = clogKey{} type log struct { sync.Mutex labels map[string]string ctx context.Context } func (l *log) log(msg string, sev logger.Severity) { // Set CallDepth 3, one for logger.Log, one for this function, and one for // the calling clog function. logger.Log(logger.LogEntry{Message: msg, Severity: sev, CallDepth: 3, Labels: l.labels}) } // Debugf simulates logger.Debugf and adds context labels. func Debugf(ctx context.Context, format string, args ...interface{}) { fromContext(ctx).log(fmt.Sprintf(format, args...), logger.Debug) } // Infof simulates logger.Infof and adds context labels. func Infof(ctx context.Context, format string, args ...interface{}) { fromContext(ctx).log(fmt.Sprintf(format, args...), logger.Info) } // Warningf simulates logger.Warningf and context labels. func Warningf(ctx context.Context, format string, args ...interface{}) { fromContext(ctx).log(fmt.Sprintf(format, args...), logger.Warning) } // Errorf simulates logger.Errorf and adds context labels. func Errorf(ctx context.Context, format string, args ...interface{}) { fromContext(ctx).log(fmt.Sprintf(format, args...), logger.Error) } func (l *log) clone() *log { l.Lock() defer l.Unlock() labels := map[string]string{} for k, v := range l.labels { labels[k] = v } return &log{ labels: labels, } } func forContext(ctx context.Context) (*log, context.Context) { cv := ctx.Value(ctxValueKey) l, ok := cv.(*log) if !ok { l = &log{labels: map[string]string{}} } else { l = l.clone() } ctx = context.WithValue(ctx, ctxValueKey, l) l.ctx = ctx return l, ctx } func fromContext(ctx context.Context) *log { if ctx == nil { return &log{} } v := ctx.Value(ctxValueKey) l, ok := v.(*log) if !ok { l = &log{} } return l } // WithLabels makes a log and context and adds the labels (overwriting any with the same key). func WithLabels(ctx context.Context, labels map[string]string) context.Context { if len(labels) == 0 { return ctx } l, ctx := forContext(ctx) l.Lock() defer l.Unlock() for k, v := range labels { l.labels[k] = v } return ctx } osconfig-20210219.00/clog/clog_test.go000066400000000000000000000025541401404514100172370ustar00rootroot00000000000000// Copyright 2020 Google 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 clog import ( "context" "testing" "github.com/google/go-cmp/cmp" ) func TestWithLabels(t *testing.T) { ctx := context.Background() tests := []struct { name string labels map[string]string want map[string]string }{ {"NoLables", map[string]string{}, nil}, {"OneLabel", map[string]string{"1": "1"}, map[string]string{"1": "1"}}, {"AddFourLables", map[string]string{"2": "2", "3": "3", "4": "4", "5": "5"}, map[string]string{"1": "1", "2": "2", "3": "3", "4": "4", "5": "5"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx = WithLabels(ctx, tt.labels) got := fromContext(ctx).labels if diff := cmp.Diff(tt.want, got); diff != "" { t.Fatalf("Label mismatch (-want +got):\n%s", diff) } }) } } osconfig-20210219.00/config/000077500000000000000000000000001401404514100152405ustar00rootroot00000000000000osconfig-20210219.00/config/config.go000066400000000000000000000071771401404514100170500ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "context" "errors" "fmt" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) // OSPolicyResource is a single OSPolicy resource. type OSPolicyResource struct { resource *agentendpointpb.OSPolicy_Resource managedResources *ManagedResources inDesiredState bool } // InDesiredState reports whether this resource is in the desired state. // CheckState or EnforceState should be run prior to calling InDesiredState. func (r *OSPolicyResource) InDesiredState() bool { return r.inDesiredState } // ManagedResources returns the resources that this OSPolicyResource manages. func (r *OSPolicyResource) ManagedResources() *ManagedResources { return r.managedResources } type resource interface { validate(context.Context) (*ManagedResources, error) checkState(context.Context) (bool, error) enforceState(context.Context) (bool, error) cleanup(context.Context) error } // ManagedResources are the resources that an OSPolicyResource manages. type ManagedResources struct { Packages []ManagedPackage Repositories []ManagedRepository Files []ManagedFile } // Validate validates this resource. // Validate must be called before other methods. func (r *OSPolicyResource) Validate(ctx context.Context) error { switch x := r.GetResourceType().(type) { case *agentendpointpb.OSPolicy_Resource_Pkg: r.resource = resource(&packageResouce{OSPolicy_Resource_PackageResource: x.Pkg}) case *agentendpointpb.OSPolicy_Resource_Repository: r.resource = resource(&repositoryResource{OSPolicy_Resource_RepositoryResource: x.Repository}) case *agentendpointpb.OSPolicy_Resource_File_: r.resource = resource(&fileResource{OSPolicy_Resource_FileResource: x.File}) /* case *agentendpointpb.ApplyConfigTask_Config_Resource_Exec: case *agentendpointpb.ApplyConfigTask_Config_Resource_Archive: case *agentendpointpb.ApplyConfigTask_Config_Resource_Srvc: */ case nil: return errors.New("ResourceType field not set") default: return fmt.Errorf("ResourceType has unexpected type: %T", x) } var err error r.managedResources, err = r.validate(ctx) return err } // CheckState checks this resources state. // Validate must be called prior to running CheckState. func (r *OSPolicyResource) CheckState(ctx context.Context) error { if r.resource == nil { return errors.New("CheckState run before Validate") } inDesiredState, err := r.checkState(ctx) r.inDesiredState = inDesiredState return err } // EnforceState enforces this resources state. // Validate must be called prior to running EnforceState. func (r *OSPolicyResource) EnforceState(ctx context.Context) error { if r.resource == nil { return errors.New("EnforceState run before Validate") } inDesiredState, err := r.enforceState(ctx) r.inDesiredState = inDesiredState return err } // Cleanup cleans up any temporary files that this resource may have created. func (r *OSPolicyResource) Cleanup(ctx context.Context) error { if r.resource == nil { return errors.New("Cleanup run before Validate") } return r.cleanup(ctx) } osconfig-20210219.00/config/config_test.go000066400000000000000000000016201401404514100200720ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "github.com/GoogleCloudPlatform/osconfig/packages" ) func init() { packages.YumExists = true packages.AptExists = true packages.GooGetExists = true packages.DpkgExists = true packages.RPMExists = true packages.ZypperExists = true packages.MSIExists = true } osconfig-20210219.00/config/exec_resource.go000066400000000000000000000142571401404514100204330ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "context" "fmt" "io/ioutil" "os" "os/exec" "path" "path/filepath" "runtime" "strings" "github.com/GoogleCloudPlatform/osconfig/util" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) var runner = util.CommandRunner(&util.DefaultRunner{}) type execResource struct { *agentendpointpb.OSPolicy_Resource_ExecResource validatePath, enforcePath, tempDir string } // TODO: use a persistent cache for downloaded files so we dont need to redownload them each time func (e *execResource) download(ctx context.Context, execR *agentendpointpb.OSPolicy_Resource_ExecResource_Exec) (string, error) { tmpDir, err := ioutil.TempDir(e.tempDir, "") if err != nil { return "", fmt.Errorf("failed to create temp dir: %s", err) } // File extensions are impoprtant on Windows. var name string switch execR.GetSource().(type) { case *agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script: switch execR.GetInterpreter() { case agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE: if runtime.GOOS == "windows" { name = filepath.Join(tmpDir, "script.cmd") } else { name = filepath.Join(tmpDir, "script") } case agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL: if runtime.GOOS == "windows" { name = filepath.Join(tmpDir, "script.cmd") } else { name = filepath.Join(tmpDir, "script.sh") } case agentendpointpb.OSPolicy_Resource_ExecResource_Exec_POWERSHELL: name = filepath.Join(tmpDir, "script.ps1") default: return "", fmt.Errorf("unsupported interpreter %q", execR.GetInterpreter()) } name := filepath.Join(tmpDir, "") _, err := util.AtomicWriteFileStream(strings.NewReader(execR.GetScript()), "", name, 0644) if err != nil { return "", err } case *agentendpointpb.OSPolicy_Resource_ExecResource_Exec_File: if execR.GetFile().GetLocalPath() != "" { return execR.GetFile().GetLocalPath(), nil } switch { case execR.GetFile().GetGcs().GetObject() != "": name = path.Base(execR.GetFile().GetGcs().GetObject()) case execR.GetFile().GetRemote().GetUri() != "": name = path.Base(execR.GetFile().GetRemote().GetUri()) default: return "", fmt.Errorf("unsupported File %v", execR.GetFile()) } name = filepath.Join(tmpDir, name) if _, err := downloadFile(ctx, name, execR.GetFile()); err != nil { return "", err } default: return "", fmt.Errorf("unrecognized Source type for FileResource: %q", execR.GetSource()) } return name, nil } func (e *execResource) validate(ctx context.Context) (*ManagedResources, error) { tmpDir, err := ioutil.TempDir("", "osconfig_exec_resource_") if err != nil { return nil, fmt.Errorf("failed to create temp dir: %s", err) } e.tempDir = tmpDir e.validatePath, err = e.download(ctx, e.GetValidate()) if err != nil { return nil, err } e.enforcePath, err = e.download(ctx, e.GetEnforce()) if err != nil { return nil, err } return nil, nil } func (e *execResource) run(ctx context.Context, name string, execR *agentendpointpb.OSPolicy_Resource_ExecResource_Exec) ([]byte, []byte, int, error) { var cmd string var args []string switch execR.GetInterpreter() { case agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE: cmd = name case agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL: if runtime.GOOS == "windows" { cmd = name } else { args = append([]string{name}) cmd = "/bin/sh" } case agentendpointpb.OSPolicy_Resource_ExecResource_Exec_POWERSHELL: if runtime.GOOS != "windows" { return nil, nil, 0, fmt.Errorf("interpreter %q can only be used on Windows systems", execR.GetInterpreter()) } args = append([]string{"-File", name}) cmd = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\PowerShell.exe" default: return nil, nil, 0, fmt.Errorf("unsupported interpreter %q", execR.GetInterpreter()) } stdout, stderr, err := runner.Run(ctx, exec.Command(cmd, args...)) code := -1 if err != nil { if v, ok := err.(*exec.ExitError); ok { code = v.ExitCode() } } return stdout, stderr, code, err } func (e *execResource) checkState(ctx context.Context) (inDesiredState bool, err error) { // For validate we expect an exit code of 100 for "correct state" and 101 for "incorrect state". // 100 was chosen over 0 (and 101 vs 1) because we want an explicit indicator of // "correct" vs "incorrect" state and errors. Also Powershell will always exit 0 unless "exit" // is explicitly called. // A code of -1 indicates some other error, so we just return err. stdout, stderr, code, err := e.run(ctx, e.validatePath, e.GetValidate()) switch code { case -1: return false, err case 100: return true, nil case 101: return false, nil default: return false, fmt.Errorf("unexpected return code from validate: %d, stdout: %s, stderr: %s", code, stdout, stderr) } } func (e *execResource) enforceState(ctx context.Context) (inDesiredState bool, err error) { // For enforce we expect an exit code of 100 for "success" and anything positive code is a failure". // 100 was chosen over 0 because we want an explicit indicator of "sucess" vs errors. // Also Powershell will always exit 0 unless "exit" is explicitly called. // A code of -1 indicates some other error, so we just return err. stdout, stderr, code, err := e.run(ctx, e.enforcePath, e.GetEnforce()) switch code { case -1: return false, err case 100: return true, nil default: return false, fmt.Errorf("unexpected return code from enforce: %d, stdout: %s, stderr: %s", code, stdout, stderr) } } func (e *execResource) cleanup(ctx context.Context) error { if e.tempDir != "" { return os.RemoveAll(e.tempDir) } return nil } osconfig-20210219.00/config/file.go000066400000000000000000000040321401404514100165050ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "context" "crypto/sha256" "encoding/hex" "fmt" "io" "net/http" "cloud.google.com/go/storage" "github.com/GoogleCloudPlatform/osconfig/external" "github.com/GoogleCloudPlatform/osconfig/util" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) func checksum(r io.Reader) string { hash := sha256.New() io.Copy(hash, r) return hex.EncodeToString(hash.Sum(nil)) } func downloadFile(ctx context.Context, path string, file *agentendpointpb.OSPolicy_Resource_File) (string, error) { var reader io.ReadCloser var err error var wantChecksum string switch file.GetType().(type) { case *agentendpointpb.OSPolicy_Resource_File_Gcs_: client, err := storage.NewClient(ctx) if err != nil { return "", fmt.Errorf("error creating gcs client: %v", err) } defer client.Close() reader, err = external.FetchGCSObject(ctx, client, file.GetGcs().GetBucket(), file.GetGcs().GetObject(), file.GetGcs().GetGeneration()) if err != nil { return "", err } case *agentendpointpb.OSPolicy_Resource_File_Remote_: reader, err = external.FetchRemoteObjectHTTP(&http.Client{}, file.GetRemote().GetUri()) if err != nil { return "", err } wantChecksum = file.GetRemote().GetSha256Checksum() default: return "", fmt.Errorf("unknown remote File type: %+v", file.GetType()) } defer reader.Close() return util.AtomicWriteFileStream(reader, wantChecksum, path, 0644) } osconfig-20210219.00/config/file_resource.go000066400000000000000000000147031401404514100204220ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "context" "fmt" "io" "io/ioutil" "os" "path/filepath" "strconv" "strings" "github.com/GoogleCloudPlatform/osconfig/util" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) const defaultFilePerms = 0644 type fileResource struct { *agentendpointpb.OSPolicy_Resource_FileResource managedFile ManagedFile } // ManagedFile is the file that this FileResouce manages. type ManagedFile struct { Path string State agentendpointpb.OSPolicy_Resource_FileResource_DesiredState Permisions os.FileMode tempDir string source string checksum string } func parsePermissions(s string) (os.FileMode, error) { if s == "" { return defaultFilePerms, nil } i, err := strconv.ParseUint(s, 8, 32) if err != nil { return 0, err } return os.FileMode(i), nil } // TODO: use a persistent cache for downloaded files so we dont need to redownload them each time. func (f *fileResource) download(ctx context.Context) error { // No need to download if source is a local file. if f.GetFile().GetLocalPath() != "" { return nil } tmpDir, err := ioutil.TempDir("", "osconfig_file_resource_") if err != nil { return fmt.Errorf("failed to create working dir: %s", err) } f.managedFile.tempDir = tmpDir tmpFile := filepath.Join(tmpDir, filepath.Base(f.GetPath())) f.managedFile.source = tmpFile switch f.GetSource().(type) { case *agentendpointpb.OSPolicy_Resource_FileResource_Content: f.managedFile.checksum, err = util.AtomicWriteFileStream(strings.NewReader(f.GetContent()), "", tmpFile, 0644) if err != nil { return err } case *agentendpointpb.OSPolicy_Resource_FileResource_File: f.managedFile.checksum, err = downloadFile(ctx, tmpFile, f.GetFile()) if err != nil { return err } default: return fmt.Errorf("unrecognized Source type for FileResource: %q", f.GetSource()) } return nil } func (f *fileResource) validate(ctx context.Context) (*ManagedResources, error) { switch f.GetState() { case agentendpointpb.OSPolicy_Resource_FileResource_ABSENT, agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH: f.managedFile.State = f.GetState() default: return nil, fmt.Errorf("unrecognized DesiredState for FileResource: %q", f.GetState()) } f.managedFile.Path = f.GetPath() // If desired state is absent, we can return now. if f.GetState() == agentendpointpb.OSPolicy_Resource_FileResource_ABSENT { return &ManagedResources{Files: []ManagedFile{f.managedFile}}, nil } perms, err := parsePermissions(f.GetPermissions()) if err != nil { return nil, fmt.Errorf("can't parse permissions %q: %v", f.GetPermissions(), err) } f.managedFile.Permisions = perms if f.GetFile().GetLocalPath() != "" { f.managedFile.source = f.GetFile().GetLocalPath() file, err := os.Open(f.GetFile().GetLocalPath()) if err != nil { return nil, err } f.managedFile.checksum = checksum(file) file.Close() } switch f.managedFile.State { case agentendpointpb.OSPolicy_Resource_FileResource_ABSENT: case agentendpointpb.OSPolicy_Resource_FileResource_PRESENT: // If the file is already present no need to downloaded it. if !util.Exists(f.managedFile.Path) { if err := f.download(ctx); err != nil { return nil, err } } case agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH: if err := f.download(ctx); err != nil { return nil, err } default: return nil, fmt.Errorf("unrecognized DesiredState for FileResource: %q", f.managedFile.State) } return &ManagedResources{Files: []ManagedFile{f.managedFile}}, nil } func (f *fileResource) checkState(ctx context.Context) (inDesiredState bool, err error) { switch f.managedFile.State { case agentendpointpb.OSPolicy_Resource_FileResource_ABSENT: return !util.Exists(f.managedFile.Path), nil case agentendpointpb.OSPolicy_Resource_FileResource_PRESENT: return util.Exists(f.managedFile.Path), nil case agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH: return contentsMatch(f.managedFile.Path, f.managedFile.checksum) default: return false, fmt.Errorf("unrecognized DesiredState for FileResource: %q", f.managedFile.State) } } func copyFile(dst, src string, perms os.FileMode) (retErr error) { reader, err := os.Open(src) if err != nil { return fmt.Errorf("error opening source file: %v", err) } defer reader.Close() writer, err := os.OpenFile(dst, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms) if err != nil { return fmt.Errorf("error opening destination file: %v", err) } defer func() { if err := writer.Close(); err != nil { if retErr == nil { retErr = fmt.Errorf("error closing destination file: %v", err) } } }() if _, err := io.Copy(writer, reader); err != nil { return err } return writer.Chmod(perms) } func (f *fileResource) enforceState(ctx context.Context) (inDesiredState bool, err error) { switch f.managedFile.State { case agentendpointpb.OSPolicy_Resource_FileResource_ABSENT: if err := os.Remove(f.managedFile.Path); err != nil { return false, fmt.Errorf("error removing %q: %v", f.managedFile.Path, err) } case agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH: // Download now if for some reason we got this point and have not. if f.managedFile.source == "" { if err := f.download(ctx); err != nil { return false, err } } if err := copyFile(f.managedFile.Path, f.managedFile.source, f.managedFile.Permisions); err != nil { return false, fmt.Errorf("error copying %q to %q: %v", f.managedFile.source, f.managedFile.Path, err) } default: return false, fmt.Errorf("unrecognized DesiredState for FileResource: %q", f.managedFile.State) } return true, nil } func (f *fileResource) cleanup(ctx context.Context) error { if f.managedFile.tempDir != "" { return os.RemoveAll(f.managedFile.tempDir) } return nil } osconfig-20210219.00/config/file_resource_test.go000066400000000000000000000250041401404514100214550ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "context" "io/ioutil" "os" "path/filepath" "testing" "github.com/GoogleCloudPlatform/osconfig/util" "github.com/google/go-cmp/cmp" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) func TestFileResourceValidate(t *testing.T) { ctx := context.Background() tmpDir, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) tmpFile := filepath.Join(tmpDir, "foo") if err := ioutil.WriteFile(tmpFile, nil, 0644); err != nil { t.Fatal(err) } var tests = []struct { name string frpb *agentendpointpb.OSPolicy_Resource_FileResource wantMR ManagedFile }{ { "Absent", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_ABSENT, }, ManagedFile{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_ABSENT, }, }, { "Present", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, }, ManagedFile{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, Permisions: defaultFilePerms, }, }, { "ContentsMatch", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, Source: &agentendpointpb.OSPolicy_Resource_FileResource_File{ File: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{ LocalPath: tmpFile, }, }, }, State: agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH, }, ManagedFile{ Path: tmpFile, source: tmpFile, checksum: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", State: agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH, Permisions: defaultFilePerms, }, }, { "Permissions", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, Permissions: "0777", }, ManagedFile{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, Permisions: 0777, }, }, { "LocalPath", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, Source: &agentendpointpb.OSPolicy_Resource_FileResource_File{ File: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{ LocalPath: tmpFile, }, }, }, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, }, ManagedFile{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, Permisions: defaultFilePerms, checksum: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", source: tmpFile, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_File_{ File: tt.frpb, }, }, } if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected error: %v", err) } if diff := cmp.Diff(pr.ManagedResources(), &ManagedResources{Files: []ManagedFile{tt.wantMR}}, cmp.AllowUnexported(ManagedFile{})); diff != "" { t.Errorf("OSPolicyResource does not match expectation: (-got +want)\n%s", diff) } if diff := cmp.Diff(pr.resource.(*fileResource).managedFile, tt.wantMR, cmp.AllowUnexported(ManagedFile{})); diff != "" { t.Errorf("fileResource does not match expectation: (-got +want)\n%s", diff) } }) } } func TestFileResourceCheckState(t *testing.T) { ctx := context.Background() tmpDir, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) tmpFile := filepath.Join(tmpDir, "foo") if err := ioutil.WriteFile(tmpFile, []byte("foo"), 0644); err != nil { t.Fatal(err) } tmpFile2 := filepath.Join(tmpDir, "bar") if err := ioutil.WriteFile(tmpFile2, []byte("bar"), 0644); err != nil { t.Fatal(err) } var tests = []struct { name string frpb *agentendpointpb.OSPolicy_Resource_FileResource wantInDesiredState bool }{ { "AbsentAndAbsent", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: filepath.Join(tmpDir, "dne"), State: agentendpointpb.OSPolicy_Resource_FileResource_ABSENT, }, true, }, { "AbsentAndPresent", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_ABSENT, }, false, }, { "PresentAndAbsent", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: filepath.Join(tmpDir, "dne"), Source: &agentendpointpb.OSPolicy_Resource_FileResource_File{ File: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{ LocalPath: tmpFile, }, }, }, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, }, false, }, { "PresentAndPresent", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, }, true, }, { "ContentsMatchLocalPath", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, Source: &agentendpointpb.OSPolicy_Resource_FileResource_File{ File: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{ LocalPath: tmpFile, }, }, }, State: agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH, }, true, }, { "ContentsDontMatchLocalPath", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile2, Source: &agentendpointpb.OSPolicy_Resource_FileResource_File{ File: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{ LocalPath: tmpFile, }, }, }, State: agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH, }, false, }, { "ContentsDontMatchDNE", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: filepath.Join(tmpDir, "dne"), Source: &agentendpointpb.OSPolicy_Resource_FileResource_File{ File: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{ LocalPath: tmpFile, }, }, }, State: agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH, }, false, }, { "ContentMatchFromContent", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, Source: &agentendpointpb.OSPolicy_Resource_FileResource_Content{ Content: "foo", }, State: agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH, }, true, }, { "ContentsDontMatchFromContent", &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, Source: &agentendpointpb.OSPolicy_Resource_FileResource_Content{ Content: "bar", }, State: agentendpointpb.OSPolicy_Resource_FileResource_CONTENTS_MATCH, }, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_File_{ File: tt.frpb, }, }, } if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected Validate error: %v", err) } if err := pr.CheckState(ctx); err != nil { t.Fatalf("Unexpected CheckState error: %v", err) } if tt.wantInDesiredState != pr.InDesiredState() { t.Fatalf("Unexpected InDesiredState, want: %t, got: %t", tt.wantInDesiredState, pr.InDesiredState()) } }) } } func TestFileResourceEnforceStateAbsent(t *testing.T) { ctx := context.Background() tmpDir, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) tmpFile := filepath.Join(tmpDir, "foo") if err := ioutil.WriteFile(tmpFile, []byte("foo"), 0644); err != nil { t.Fatal(err) } frpb := &agentendpointpb.OSPolicy_Resource_FileResource{ Path: tmpFile, State: agentendpointpb.OSPolicy_Resource_FileResource_ABSENT, } pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_File_{File: frpb}, }, } if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected Validate error: %v", err) } if err := pr.EnforceState(ctx); err != nil { t.Fatalf("Unexpected EnforceState error: %v", err) } if util.Exists(tmpFile) { t.Error("tmpFile still exists after EnforceState") } } func TestFileResourceEnforceStatePresent(t *testing.T) { ctx := context.Background() tmpDir, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) srcFile := filepath.Join(tmpDir, "foo") if err := ioutil.WriteFile(srcFile, []byte("foo"), 0644); err != nil { t.Fatal(err) } wantFile := filepath.Join(tmpDir, "bar") frpb := &agentendpointpb.OSPolicy_Resource_FileResource{ Path: wantFile, Source: &agentendpointpb.OSPolicy_Resource_FileResource_File{ File: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{ LocalPath: srcFile, }, }, }, State: agentendpointpb.OSPolicy_Resource_FileResource_PRESENT, } pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_File_{File: frpb}, }, } if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected Validate error: %v", err) } if err := pr.EnforceState(ctx); err != nil { t.Fatalf("Unexpected EnforceState error: %v", err) } match, err := contentsMatch(wantFile, pr.resource.(*fileResource).managedFile.checksum) if err != nil { t.Fatal(err) } if !match { t.Fatal("Repo file contents do not match after enforcement") } } osconfig-20210219.00/config/package_resource.go000066400000000000000000000404351401404514100210770ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "strings" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/GoogleCloudPlatform/osconfig/util" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) type packageResouce struct { *agentendpointpb.OSPolicy_Resource_PackageResource managedPackage ManagedPackage } // AptPackage describes an apt package resource. type AptPackage struct { PackageResource *agentendpointpb.OSPolicy_Resource_PackageResource_APT DesiredState agentendpointpb.OSPolicy_Resource_PackageResource_DesiredState } // DebPackage describes a deb package resource. type DebPackage struct { PackageResource *agentendpointpb.OSPolicy_Resource_PackageResource_Deb name, localPath string } // GooGetPackage describes a googet package resource. type GooGetPackage struct { PackageResource *agentendpointpb.OSPolicy_Resource_PackageResource_GooGet DesiredState agentendpointpb.OSPolicy_Resource_PackageResource_DesiredState } // MSIPackage describes an msi package resource. type MSIPackage struct { PackageResource *agentendpointpb.OSPolicy_Resource_PackageResource_MSI productName, localPath string } // YumPackage describes a yum package resource. type YumPackage struct { PackageResource *agentendpointpb.OSPolicy_Resource_PackageResource_YUM DesiredState agentendpointpb.OSPolicy_Resource_PackageResource_DesiredState } // ZypperPackage describes a zypper package resource. type ZypperPackage struct { PackageResource *agentendpointpb.OSPolicy_Resource_PackageResource_Zypper DesiredState agentendpointpb.OSPolicy_Resource_PackageResource_DesiredState } // RPMPackage describes an rpm package resource. type RPMPackage struct { PackageResource *agentendpointpb.OSPolicy_Resource_PackageResource_RPM name, localPath string } // ManagedPackage is the package that this PackageResource manages. type ManagedPackage struct { Apt *AptPackage Deb *DebPackage GooGet *GooGetPackage MSI *MSIPackage Yum *YumPackage Zypper *ZypperPackage RPM *RPMPackage tempDir string } func (p *packageResouce) validateFile(file *agentendpointpb.OSPolicy_Resource_File) error { if file.GetLocalPath() != "" { if !util.Exists(file.GetLocalPath()) { return fmt.Errorf("%q does not exist", file.GetLocalPath()) } } return nil } func (p *packageResouce) validate(ctx context.Context) (*ManagedResources, error) { switch p.GetSystemPackage().(type) { case *agentendpointpb.OSPolicy_Resource_PackageResource_Apt: pr := p.GetApt() if !packages.AptExists { return nil, fmt.Errorf("cannot manage Apt package %q because apt-get does not exist on the system", pr.GetName()) } p.managedPackage.Apt = &AptPackage{DesiredState: p.GetDesiredState(), PackageResource: pr} case *agentendpointpb.OSPolicy_Resource_PackageResource_Deb_: pr := p.GetDeb() if !packages.DpkgExists { return nil, fmt.Errorf("cannot manage Deb package because dpkg does not exist on the system") } if p.GetDesiredState() != agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED { return nil, fmt.Errorf("desired state of %q not applicable for deb package", p.GetDesiredState()) } if err := p.validateFile(pr.GetSource()); err != nil { return nil, err } localPath, err := p.download(ctx, "pkg.deb", p.GetDeb().GetSource()) if err != nil { return nil, err } info, err := packages.DebPkgInfo(ctx, localPath) if err != nil { return nil, err } p.managedPackage.Deb = &DebPackage{PackageResource: pr, localPath: localPath, name: info.Name} case *agentendpointpb.OSPolicy_Resource_PackageResource_Googet: pr := p.GetGooget() if !packages.GooGetExists { return nil, fmt.Errorf("cannot manage GooGet package %q because googet does not exist on the system", pr.GetName()) } p.managedPackage.GooGet = &GooGetPackage{DesiredState: p.GetDesiredState(), PackageResource: pr} case *agentendpointpb.OSPolicy_Resource_PackageResource_Msi: pr := p.GetMsi() if !packages.MSIExists { return nil, fmt.Errorf("cannot manage MSI package because msiexec does not exist on the system") } if p.GetDesiredState() != agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED { return nil, fmt.Errorf("desired state of %q not applicable for MSI package", p.GetDesiredState()) } if err := p.validateFile(pr.GetSource()); err != nil { return nil, err } localPath, err := p.download(ctx, "pkg.msi", p.GetMsi().GetSource()) if err != nil { return nil, err } p.managedPackage.MSI = &MSIPackage{PackageResource: pr, localPath: localPath} case *agentendpointpb.OSPolicy_Resource_PackageResource_Yum: pr := p.GetYum() if !packages.YumExists { return nil, fmt.Errorf("cannot manage Yum package %q because yum does not exist on the system", pr.GetName()) } p.managedPackage.Yum = &YumPackage{DesiredState: p.GetDesiredState(), PackageResource: pr} case *agentendpointpb.OSPolicy_Resource_PackageResource_Zypper_: pr := p.GetZypper() if !packages.ZypperExists { return nil, fmt.Errorf("cannot manage Zypper package %q because zypper does not exist on the system", pr.GetName()) } p.managedPackage.Zypper = &ZypperPackage{DesiredState: p.GetDesiredState(), PackageResource: pr} case *agentendpointpb.OSPolicy_Resource_PackageResource_Rpm: pr := p.GetRpm() if !packages.RPMExists { return nil, fmt.Errorf("cannot manage RPM package because rpm does not exist on the system") } if p.GetDesiredState() != agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED { return nil, fmt.Errorf("desired state of %q not applicable for rpm package", p.GetDesiredState()) } if err := p.validateFile(pr.GetSource()); err != nil { return nil, err } localPath, err := p.download(ctx, "pkg.rpm", p.GetRpm().GetSource()) if err != nil { return nil, err } info, err := packages.RPMPkgInfo(ctx, localPath) if err != nil { return nil, err } p.managedPackage.RPM = &RPMPackage{PackageResource: pr, localPath: localPath, name: info.Name} default: return nil, fmt.Errorf("SystemPackage field not set or references unknown package manager: %v", p.GetSystemPackage()) } return &ManagedResources{Packages: []ManagedPackage{p.managedPackage}}, nil } type packageCache struct { cache map[string]struct{} refreshed time.Time } var ( aptInstalled = &packageCache{} debInstalled = &packageCache{} gooInstalled = &packageCache{} yumInstalled = &packageCache{} zypperInstalled = &packageCache{} rpmInstalled = &packageCache{} ) func populateInstalledCache(ctx context.Context, mp ManagedPackage) error { var cache *packageCache var refreshFunc func(context.Context) ([]packages.PkgInfo, error) var err error switch { // TODO: implement apt functions case mp.Apt != nil: cache = aptInstalled refreshFunc = packages.InstalledDebPackages case mp.Deb != nil: cache = debInstalled refreshFunc = packages.InstalledDebPackages case mp.GooGet != nil: cache = gooInstalled refreshFunc = packages.InstalledGooGetPackages case mp.MSI != nil: // We just query per each MSI. return nil // TODO: implement yum functions case mp.Yum != nil: cache = yumInstalled refreshFunc = packages.InstalledRPMPackages // TODO: implement zypper functions case mp.Zypper != nil: cache = zypperInstalled refreshFunc = packages.InstalledRPMPackages case mp.RPM != nil: cache = rpmInstalled refreshFunc = packages.InstalledRPMPackages default: return fmt.Errorf("unknown or unpopulated ManagedPackage package type: %+v", mp) } // Cache already populated within the last 3 min. if cache.cache != nil && cache.refreshed.After(time.Now().Add(-3*time.Minute)) { return nil } pis, err := refreshFunc(ctx) if err != nil { return err } cache.cache = map[string]struct{}{} for _, pkg := range pis { cache.cache[pkg.Name] = struct{}{} } cache.refreshed = time.Now() return nil } // TODO: use a persistent cache for downloaded files so we dont need to redownload them each time func (p *packageResouce) download(ctx context.Context, name string, file *agentendpointpb.OSPolicy_Resource_File) (string, error) { var path string switch { case file.GetLocalPath() != "": path = file.GetLocalPath() default: tmpDir, err := ioutil.TempDir("", "osconfig_package_resource_") if err != nil { return "", fmt.Errorf("failed to create temp dir: %s", err) } p.managedPackage.tempDir = tmpDir path = filepath.Join(p.managedPackage.tempDir, name) if _, err := downloadFile(ctx, path, file); err != nil { return "", err } } return path, nil } func (p *packageResouce) checkState(ctx context.Context) (inDesiredState bool, err error) { if err := populateInstalledCache(ctx, p.managedPackage); err != nil { return false, err } var desiredState agentendpointpb.OSPolicy_Resource_PackageResource_DesiredState var pkgIns bool switch { case p.managedPackage.Apt != nil: desiredState = p.managedPackage.Apt.DesiredState _, pkgIns = aptInstalled.cache[p.managedPackage.Apt.PackageResource.GetName()] case p.managedPackage.Deb != nil: desiredState = agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED _, pkgIns = debInstalled.cache[p.managedPackage.Deb.name] case p.managedPackage.GooGet != nil: desiredState = p.managedPackage.GooGet.DesiredState _, pkgIns = gooInstalled.cache[p.managedPackage.GooGet.PackageResource.GetName()] case p.managedPackage.MSI != nil: desiredState = agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED p.managedPackage.MSI.productName, pkgIns, err = packages.MSIInfo(p.managedPackage.MSI.localPath) if err != nil { return false, err } case p.managedPackage.Yum != nil: desiredState = p.managedPackage.Yum.DesiredState _, pkgIns = yumInstalled.cache[p.managedPackage.Yum.PackageResource.GetName()] case p.managedPackage.Zypper != nil: desiredState = p.managedPackage.Zypper.DesiredState _, pkgIns = zypperInstalled.cache[p.managedPackage.Zypper.PackageResource.GetName()] case p.managedPackage.RPM != nil: desiredState = agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED _, pkgIns = rpmInstalled.cache[p.managedPackage.RPM.name] } switch desiredState { case agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED: if pkgIns { return true, nil } case agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED: if !pkgIns { return true, nil } default: return false, fmt.Errorf("DesiredState field not set or references state: %q", desiredState) } return false, nil } func (p *packageResouce) enforceState(ctx context.Context) (inDesiredState bool, err error) { var ( installing = "installing" removing = "removing" enforcePackage struct { name string action string packageType string actionFunc func() error installedCache *packageCache } ) switch { case p.managedPackage.Apt != nil: enforcePackage.name = p.managedPackage.Apt.PackageResource.GetName() enforcePackage.packageType = "apt" enforcePackage.installedCache = aptInstalled switch p.managedPackage.Apt.DesiredState { case agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED: enforcePackage.action, enforcePackage.actionFunc = installing, func() error { return packages.InstallAptPackages(ctx, []string{enforcePackage.name}) } case agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED: enforcePackage.action, enforcePackage.actionFunc = removing, func() error { return packages.RemoveAptPackages(ctx, []string{enforcePackage.name}) } } case p.managedPackage.Deb != nil: enforcePackage.name = p.managedPackage.Deb.name enforcePackage.packageType = "deb" enforcePackage.installedCache = debInstalled enforcePackage.action = installing if p.GetDeb().GetPullDeps() { enforcePackage.actionFunc = func() error { return packages.InstallAptPackages(ctx, []string{p.managedPackage.Deb.localPath}) } } else { enforcePackage.actionFunc = func() error { return packages.DpkgInstall(ctx, p.managedPackage.Deb.localPath) } } case p.managedPackage.GooGet != nil: enforcePackage.name = p.managedPackage.GooGet.PackageResource.GetName() enforcePackage.packageType = "googet" enforcePackage.installedCache = gooInstalled switch p.managedPackage.GooGet.DesiredState { case agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED: enforcePackage.action, enforcePackage.actionFunc = installing, func() error { return packages.InstallGooGetPackages(ctx, []string{enforcePackage.name}) } case agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED: enforcePackage.action, enforcePackage.actionFunc = removing, func() error { return packages.RemoveGooGetPackages(ctx, []string{enforcePackage.name}) } } case p.managedPackage.MSI != nil: enforcePackage.name = p.managedPackage.MSI.productName enforcePackage.packageType = "msi" enforcePackage.action = installing enforcePackage.actionFunc = func() error { return packages.InstallMSIPackage(ctx, p.managedPackage.MSI.localPath, p.managedPackage.MSI.PackageResource.GetProperties()) } case p.managedPackage.Yum != nil: enforcePackage.name = p.managedPackage.Yum.PackageResource.GetName() enforcePackage.packageType = "yum" enforcePackage.installedCache = yumInstalled switch p.managedPackage.Yum.DesiredState { case agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED: enforcePackage.action, enforcePackage.actionFunc = installing, func() error { return packages.InstallYumPackages(ctx, []string{enforcePackage.name}) } case agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED: enforcePackage.action, enforcePackage.actionFunc = removing, func() error { return packages.RemoveYumPackages(ctx, []string{enforcePackage.name}) } } case p.managedPackage.Zypper != nil: enforcePackage.name = p.managedPackage.Zypper.PackageResource.GetName() enforcePackage.packageType = "zypper" enforcePackage.installedCache = zypperInstalled switch p.managedPackage.Zypper.DesiredState { case agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED: enforcePackage.action, enforcePackage.actionFunc = installing, func() error { return packages.InstallZypperPackages(ctx, []string{enforcePackage.name}) } case agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED: enforcePackage.action, enforcePackage.actionFunc = removing, func() error { return packages.RemoveZypperPackages(ctx, []string{enforcePackage.name}) } } case p.managedPackage.RPM != nil: enforcePackage.name = p.managedPackage.RPM.name enforcePackage.packageType = "rpm" enforcePackage.installedCache = rpmInstalled enforcePackage.action = installing if p.GetRpm().GetPullDeps() { switch { case packages.YumExists: enforcePackage.actionFunc = func() error { return packages.InstallYumPackages(ctx, []string{p.managedPackage.RPM.localPath}) } case packages.ZypperExists: enforcePackage.actionFunc = func() error { return packages.InstallZypperPackages(ctx, []string{p.managedPackage.RPM.localPath}) } default: return false, fmt.Errorf("cannot install rpm %q with 'PullDeps' option as neither yum or zypper exist on system", enforcePackage.name) } } else { enforcePackage.actionFunc = func() error { return packages.RPMInstall(ctx, p.managedPackage.RPM.localPath) } } } clog.Infof(ctx, "%s %s package %q", strings.Title(enforcePackage.action), enforcePackage.packageType, enforcePackage.name) // Reset the cache as we are taking action. enforcePackage.installedCache.cache = nil if err := enforcePackage.actionFunc(); err != nil { return false, fmt.Errorf("error %s %s package %q", enforcePackage.action, enforcePackage.packageType, enforcePackage.name) } return true, nil } func (p *packageResouce) cleanup(ctx context.Context) error { if p.managedPackage.tempDir != "" { return os.RemoveAll(p.managedPackage.tempDir) } return nil } osconfig-20210219.00/config/package_resource_test.go000066400000000000000000000376411401404514100221430ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "context" "io/ioutil" "os" "os/exec" "path/filepath" "testing" "time" "github.com/GoogleCloudPlatform/osconfig/packages" utilmocks "github.com/GoogleCloudPlatform/osconfig/util/mocks" "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/testing/protocmp" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) var ( aptInstalledPR = &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Apt{ Apt: &agentendpointpb.OSPolicy_Resource_PackageResource_APT{Name: "foo"}}} aptRemovedPR = &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Apt{ Apt: &agentendpointpb.OSPolicy_Resource_PackageResource_APT{Name: "foo"}}} googetInstalledPR = &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Googet{ Googet: &agentendpointpb.OSPolicy_Resource_PackageResource_GooGet{Name: "foo"}}} googetRemovedPR = &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Googet{ Googet: &agentendpointpb.OSPolicy_Resource_PackageResource_GooGet{Name: "foo"}}} yumInstalledPR = &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Yum{ Yum: &agentendpointpb.OSPolicy_Resource_PackageResource_YUM{Name: "foo"}}} yumRemovedPR = &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Yum{ Yum: &agentendpointpb.OSPolicy_Resource_PackageResource_YUM{Name: "foo"}}} zypperInstalledPR = &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Zypper_{ Zypper: &agentendpointpb.OSPolicy_Resource_PackageResource_Zypper{Name: "foo"}}} zypperRemovedPR = &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Zypper_{ Zypper: &agentendpointpb.OSPolicy_Resource_PackageResource_Zypper{Name: "foo"}}} ) type fakeCommandRunner struct{} func (m *fakeCommandRunner) Run(_ context.Context, _ *exec.Cmd) ([]byte, []byte, error) { return nil, nil, nil } func TestPackageResourceValidate(t *testing.T) { ctx := context.Background() tmpDir, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) tmpFile := filepath.Join(tmpDir, "foo") if err := ioutil.WriteFile(tmpFile, nil, 0644); err != nil { t.Fatal(err) } mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) packages.SetCommandRunner(mockCommandRunner) var tests = []struct { name string wantErr bool prpb *agentendpointpb.OSPolicy_Resource_PackageResource wantMP ManagedPackage expectedCmd *exec.Cmd expectedCmdReturn []byte }{ { "Blank", true, &agentendpointpb.OSPolicy_Resource_PackageResource{}, ManagedPackage{}, nil, nil, }, { "AptInstalled", false, aptInstalledPR, ManagedPackage{Apt: &AptPackage{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_APT{Name: "foo"}}}, nil, nil, }, { "AptRemoved", false, aptRemovedPR, ManagedPackage{Apt: &AptPackage{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_APT{Name: "foo"}}}, nil, nil, }, { "DebInstalled", false, &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Deb_{ Deb: &agentendpointpb.OSPolicy_Resource_PackageResource_Deb{ Source: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{LocalPath: tmpFile}}}}}, ManagedPackage{Deb: &DebPackage{ localPath: tmpFile, name: "foo", PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_Deb{ Source: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{LocalPath: tmpFile}}}}}, exec.Command("/usr/bin/dpkg-deb", "-I", tmpFile), []byte("Package: foo\nVersion: 1:1dummy-g1\nArchitecture: amd64"), }, { "GoGetInstalled", false, googetInstalledPR, ManagedPackage{GooGet: &GooGetPackage{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_GooGet{Name: "foo"}}}, nil, nil, }, { "GooGetRemoved", false, googetRemovedPR, ManagedPackage{GooGet: &GooGetPackage{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_GooGet{Name: "foo"}}}, nil, nil, }, { "MSIInstalled", false, &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Msi{ Msi: &agentendpointpb.OSPolicy_Resource_PackageResource_MSI{ Source: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{LocalPath: tmpFile}}}}}, ManagedPackage{MSI: &MSIPackage{ localPath: tmpFile, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_MSI{ Source: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{LocalPath: tmpFile}}}}}, nil, nil, }, { "YumInstalled", false, yumInstalledPR, ManagedPackage{Yum: &YumPackage{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_YUM{Name: "foo"}}}, nil, nil, }, { "YumRemoved", false, yumRemovedPR, ManagedPackage{Yum: &YumPackage{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_YUM{Name: "foo"}}}, nil, nil, }, { "ZypperInstalled", false, zypperInstalledPR, ManagedPackage{Zypper: &ZypperPackage{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_Zypper{Name: "foo"}}}, nil, nil, }, { "ZypperRemoved", false, zypperRemovedPR, ManagedPackage{Zypper: &ZypperPackage{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_REMOVED, PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_Zypper{Name: "foo"}}}, nil, nil, }, { "RPMInstalled", false, &agentendpointpb.OSPolicy_Resource_PackageResource{ DesiredState: agentendpointpb.OSPolicy_Resource_PackageResource_INSTALLED, SystemPackage: &agentendpointpb.OSPolicy_Resource_PackageResource_Rpm{ Rpm: &agentendpointpb.OSPolicy_Resource_PackageResource_RPM{ Source: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{LocalPath: tmpFile}}}}}, ManagedPackage{RPM: &RPMPackage{ localPath: tmpFile, name: "foo", PackageResource: &agentendpointpb.OSPolicy_Resource_PackageResource_RPM{ Source: &agentendpointpb.OSPolicy_Resource_File{ Type: &agentendpointpb.OSPolicy_Resource_File_LocalPath{LocalPath: tmpFile}}}}}, exec.Command("/usr/bin/rpmquery", "--queryformat", "%{NAME} %{ARCH} %|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\n", "-p", tmpFile), []byte("foo x86_64 1.2.3-4"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_Pkg{Pkg: tt.prpb}, }, } if tt.expectedCmd != nil { mockCommandRunner.EXPECT().Run(ctx, tt.expectedCmd).Return(tt.expectedCmdReturn, nil, nil) } err := pr.Validate(ctx) if err != nil && !tt.wantErr { t.Fatalf("Unexpected error: %v", err) } if err == nil && tt.wantErr { t.Fatal("Expected error and did not get one.") } wantMR := &ManagedResources{Packages: []ManagedPackage{tt.wantMP}} if err != nil { wantMR = nil } opts := []cmp.Option{protocmp.Transform(), cmp.AllowUnexported(ManagedPackage{}), cmp.AllowUnexported(DebPackage{}), cmp.AllowUnexported(RPMPackage{}), cmp.AllowUnexported(MSIPackage{})} if diff := cmp.Diff(pr.ManagedResources(), wantMR, opts...); diff != "" { t.Errorf("OSPolicyResource does not match expectation: (-got +want)\n%s", diff) } if diff := cmp.Diff(pr.resource.(*packageResouce).managedPackage, tt.wantMP, opts...); diff != "" { t.Errorf("packageResouce does not match expectation: (-got +want)\n%s", diff) } }) } } func TestPopulateInstalledCache(t *testing.T) { ctx := context.Background() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) packages.SetCommandRunner(mockCommandRunner) mockCommandRunner.EXPECT().Run(ctx, exec.Command("googet.exe", "installed")).Return([]byte("Installed Packages:\nfoo.x86_64 1.2.3@4\nbar.noarch 1.2.3@4"), nil, nil).Times(1) if err := populateInstalledCache(ctx, ManagedPackage{GooGet: &GooGetPackage{}}); err != nil { t.Fatalf("Unexpected error from populateInstalledCache: %v", err) } want := map[string]struct{}{"foo": {}, "bar": {}} if diff := cmp.Diff(gooInstalled.cache, want); diff != "" { t.Errorf("OSPolicyResource does not match expectation: (-got +want)\n%s", diff) } } func TestPackageResourceCheckState(t *testing.T) { ctx := context.Background() var tests = []struct { name string installedCache map[string]struct{} cachePointer *packageCache prpb *agentendpointpb.OSPolicy_Resource_PackageResource wantInDesiredState bool }{ // We only need to test the full set once as all the logic is shared. { "AptInstalledNeedsInstalled", map[string]struct{}{"foo": {}}, aptInstalled, aptInstalledPR, true, }, { "AptInstalledNeedsRemoved", map[string]struct{}{"foo": {}}, aptInstalled, aptRemovedPR, false, }, { "AptRemovedNeedsInstalled", map[string]struct{}{}, aptInstalled, aptInstalledPR, false, }, { "AptRemovedNeedsRemoved", map[string]struct{}{}, aptInstalled, aptRemovedPR, true, }, // For the rest of the package types we only need to test one scenario. { "GooGetInstalledNeedsInstalled", map[string]struct{}{"foo": {}}, gooInstalled, googetInstalledPR, true, }, { "YUMInstalledNeedsInstalled", map[string]struct{}{"foo": {}}, yumInstalled, yumInstalledPR, true, }, { "ZypperInstalledNeedsInstalled", map[string]struct{}{"foo": {}}, zypperInstalled, zypperInstalledPR, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_Pkg{Pkg: tt.prpb}, }, } // Run validate first to make sure everything gets setup correctly. // This adds complexity to this 'unit' test and turns it into more // of a integration test but reduces overall test functions and gives // us good coverage. if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected Validate error: %v", err) } tt.cachePointer.cache = tt.installedCache tt.cachePointer.refreshed = time.Now() if err := pr.CheckState(ctx); err != nil { t.Fatalf("Unexpected error: %v", err) } if tt.wantInDesiredState != pr.InDesiredState() { t.Fatalf("Unexpected InDesiredState, want: %t, got: %t", tt.wantInDesiredState, pr.InDesiredState()) } }) } } func TestPackageResourceEnforceState(t *testing.T) { ctx := context.Background() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) packages.SetCommandRunner(mockCommandRunner) var tests = []struct { name string prpb *agentendpointpb.OSPolicy_Resource_PackageResource cachePointer *packageCache expectedCmd *exec.Cmd }{ { "AptInstalled", aptInstalledPR, aptInstalled, func() *exec.Cmd { cmd := exec.Command("/usr/bin/apt-get", "install", "-y", "foo") cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive", ) return cmd }(), }, { "AptRemoved", aptRemovedPR, aptInstalled, func() *exec.Cmd { cmd := exec.Command("/usr/bin/apt-get", "remove", "-y", "foo") cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive", ) return cmd }(), }, { "GooGetInstalled", googetInstalledPR, gooInstalled, exec.Command("googet.exe", "-noconfirm", "install", "foo"), }, { "GooGetRemoved", googetRemovedPR, gooInstalled, exec.Command("googet.exe", "-noconfirm", "remove", "foo"), }, { "YumInstalled", yumInstalledPR, yumInstalled, exec.Command("/usr/bin/yum", "install", "--assumeyes", "foo"), }, { "YumRemoved", yumRemovedPR, yumInstalled, exec.Command("/usr/bin/yum", "remove", "--assumeyes", "foo"), }, { "ZypperInstalled", zypperInstalledPR, zypperInstalled, exec.Command("/usr/bin/zypper", "--gpg-auto-import-keys", "--non-interactive", "install", "--auto-agree-with-licenses", "foo"), }, { "ZypperRemoved", zypperRemovedPR, zypperInstalled, exec.Command("/usr/bin/zypper", "--non-interactive", "remove", "foo"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_Pkg{Pkg: tt.prpb}, }, } // Run Validate first to make sure everything gets setup correctly. // This adds complexity to this 'unit' test and turns it into more // of a integration test but reduces overall test functions and gives // us good coverage. if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected Validate error: %v", err) } tt.cachePointer.cache = map[string]struct{}{"foo": {}} mockCommandRunner.EXPECT().Run(ctx, tt.expectedCmd) if err := pr.EnforceState(ctx); err != nil { t.Fatalf("Unexpected error: %v", err) } if tt.cachePointer.cache != nil { t.Errorf("Enforce function did not set package cache to nil") } }) } } osconfig-20210219.00/config/repository_resource.go000066400000000000000000000247701401404514100217270ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "bytes" "context" "errors" "fmt" "io" "io/ioutil" "net/http" "os" "path/filepath" "github.com/GoogleCloudPlatform/osconfig/agentconfig" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/GoogleCloudPlatform/osconfig/util" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) const aptGPGDir = "/etc/apt/trusted.gpg.d" type repositoryResource struct { *agentendpointpb.OSPolicy_Resource_RepositoryResource managedRepository ManagedRepository } // AptRepository describes an apt repository resource. type AptRepository struct { RepositoryResource *agentendpointpb.OSPolicy_Resource_RepositoryResource_AptRepository GpgFilePath string GpgFileContents []byte GpgChecksum string } // GooGetRepository describes an googet repository resource. type GooGetRepository struct { RepositoryResource *agentendpointpb.OSPolicy_Resource_RepositoryResource_GooRepository } // YumRepository describes an yum repository resource. type YumRepository struct { RepositoryResource *agentendpointpb.OSPolicy_Resource_RepositoryResource_YumRepository } // ZypperRepository describes an zypper repository resource. type ZypperRepository struct { RepositoryResource *agentendpointpb.OSPolicy_Resource_RepositoryResource_ZypperRepository } // ManagedRepository is the repository that this RepositoryResource manages. type ManagedRepository struct { Apt *AptRepository GooGet *GooGetRepository Yum *YumRepository Zypper *ZypperRepository RepoFilePath string RepoFileContents []byte RepoChecksum string } func aptRepoContents(repo *agentendpointpb.OSPolicy_Resource_RepositoryResource_AptRepository) []byte { var debArchiveTypeMap = map[agentendpointpb.OSPolicy_Resource_RepositoryResource_AptRepository_ArchiveType]string{ agentendpointpb.OSPolicy_Resource_RepositoryResource_AptRepository_DEB: "deb", agentendpointpb.OSPolicy_Resource_RepositoryResource_AptRepository_DEB_SRC: "deb-src", } /* # Repo file managed by Google OSConfig agent deb http://repo1-url/ repo main */ var buf bytes.Buffer buf.WriteString("# Repo file managed by Google OSConfig agent\n") archiveType, ok := debArchiveTypeMap[repo.GetArchiveType()] if !ok { archiveType = "deb" } line := fmt.Sprintf("%s %s %s", archiveType, repo.GetUri(), repo.GetDistribution()) for _, c := range repo.GetComponents() { line = fmt.Sprintf("%s %s", line, c) } buf.WriteString(line + "\n") return buf.Bytes() } func googetRepoContents(repo *agentendpointpb.OSPolicy_Resource_RepositoryResource_GooRepository) []byte { /* # Repo file managed by Google OSConfig agent - name: repo1-name url: https://repo1-url */ var buf bytes.Buffer buf.WriteString("# Repo file managed by Google OSConfig agent\n") buf.WriteString(fmt.Sprintf("- name: %s\n", repo.Name)) buf.WriteString(fmt.Sprintf(" url: %s\n", repo.Url)) return buf.Bytes() } func yumRepoContents(repo *agentendpointpb.OSPolicy_Resource_RepositoryResource_YumRepository) []byte { /* # Repo file managed by Google OSConfig agent [Id] name=DisplayName baseurl=https://repo-url enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=http://repo-url/gpg1 http://repo-url/gpg2 */ var buf bytes.Buffer buf.WriteString("# Repo file managed by Google OSConfig agent\n") buf.WriteString(fmt.Sprintf("[%s]\n", repo.Id)) if repo.DisplayName == "" { buf.WriteString(fmt.Sprintf("name=%s\n", repo.Id)) } else { buf.WriteString(fmt.Sprintf("name=%s\n", repo.DisplayName)) } buf.WriteString(fmt.Sprintf("baseurl=%s\n", repo.BaseUrl)) buf.WriteString("enabled=1\ngpgcheck=1\nrepo_gpgcheck=1\n") if len(repo.GpgKeys) > 0 { buf.WriteString(fmt.Sprintf("gpgkey=%s\n", repo.GpgKeys[0])) for _, k := range repo.GpgKeys[1:] { buf.WriteString(fmt.Sprintf(" %s\n", k)) } } return buf.Bytes() } func zypperRepoContents(repo *agentendpointpb.OSPolicy_Resource_RepositoryResource_ZypperRepository) []byte { /* # Repo file managed by Google OSConfig agent [Id] name=DisplayName baseurl=https://repo-url enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=http://repo-url/gpg1 http://repo-url/gpg2 */ var buf bytes.Buffer buf.WriteString("# Repo file managed by Google OSConfig agent\n") buf.WriteString(fmt.Sprintf("[%s]\n", repo.Id)) if repo.DisplayName == "" { buf.WriteString(fmt.Sprintf("name=%s\n", repo.Id)) } else { buf.WriteString(fmt.Sprintf("name=%s\n", repo.DisplayName)) } buf.WriteString(fmt.Sprintf("baseurl=%s\n", repo.BaseUrl)) buf.WriteString("enabled=1\ngpgcheck=1\nrepo_gpgcheck=1\n") if len(repo.GpgKeys) > 0 { buf.WriteString(fmt.Sprintf("gpgkey=%s\n", repo.GpgKeys[0])) for _, k := range repo.GpgKeys[1:] { buf.WriteString(fmt.Sprintf(" %s\n", k)) } } return buf.Bytes() } func fetchGPGKey(key string) ([]byte, error) { resp, err := http.Get(key) if err != nil { return nil, fmt.Errorf("error downloading gpg key: %v", err) } defer resp.Body.Close() if resp.ContentLength > 1024*1024 { return nil, fmt.Errorf("key size of %d too large", resp.ContentLength) } var buf bytes.Buffer tee := io.TeeReader(resp.Body, &buf) decoded, err := armor.Decode(tee) if err != nil && err != io.EOF { return nil, fmt.Errorf("error decoding gpg key: %v", err) } var entity *openpgp.Entity if decoded == nil { entity, err = openpgp.ReadEntity(packet.NewReader(&buf)) } else { entity, err = openpgp.ReadEntity(packet.NewReader(decoded.Body)) } if err != nil { return nil, fmt.Errorf("error reading gpg key: %v", err) } buf.Reset() if err := entity.Serialize(&buf); err != nil { return nil, fmt.Errorf("error serializing gpg key: %v", err) } return buf.Bytes(), nil } func (r *repositoryResource) validate(ctx context.Context) (*ManagedResources, error) { var filePath string switch r.GetRepository().(type) { case *agentendpointpb.OSPolicy_Resource_RepositoryResource_Apt: if !packages.AptExists { return nil, errors.New("cannot manage Apt repository because apt-get does not exist on the system") } gpgkey := r.GetApt().GetGpgKey() r.managedRepository.Apt = &AptRepository{RepositoryResource: r.GetApt()} r.managedRepository.RepoFileContents = aptRepoContents(r.GetApt()) filePath = filepath.Join(agentconfig.AptRepoDir(), "osconfig_managed_%s.repo") if gpgkey != "" { keyContents, err := fetchGPGKey(gpgkey) if err != nil { return nil, fmt.Errorf("error fetching apt gpg key %q: %v", gpgkey, err) } r.managedRepository.Apt.GpgFileContents = keyContents r.managedRepository.Apt.GpgChecksum = checksum(bytes.NewReader(keyContents)) r.managedRepository.Apt.GpgFilePath = filepath.Join(aptGPGDir, "osconfig_added_"+r.managedRepository.Apt.GpgChecksum+".gpg") } case *agentendpointpb.OSPolicy_Resource_RepositoryResource_Goo: if !packages.GooGetExists { return nil, errors.New("cannot manage googet repository because googet does not exist on the system") } r.managedRepository.GooGet = &GooGetRepository{RepositoryResource: r.GetGoo()} r.managedRepository.RepoFileContents = googetRepoContents(r.GetGoo()) filePath = filepath.Join(agentconfig.GooGetRepoDir(), "osconfig_managed_%s.repo") case *agentendpointpb.OSPolicy_Resource_RepositoryResource_Yum: if !packages.YumExists { return nil, errors.New("cannot manage yum repository because yum does not exist on the system") } r.managedRepository.Yum = &YumRepository{RepositoryResource: r.GetYum()} r.managedRepository.RepoFileContents = yumRepoContents(r.GetYum()) filePath = filepath.Join(agentconfig.YumRepoDir(), "osconfig_managed_%s.repo") case *agentendpointpb.OSPolicy_Resource_RepositoryResource_Zypper: if !packages.ZypperExists { return nil, errors.New("cannot manage zypper repository because zypper does not exist on the system") } r.managedRepository.Zypper = &ZypperRepository{RepositoryResource: r.GetZypper()} r.managedRepository.RepoFileContents = zypperRepoContents(r.GetZypper()) filePath = filepath.Join(agentconfig.ZypperRepoDir(), "osconfig_managed_%s.repo") default: return nil, fmt.Errorf("Repository field not set or references unknown repository type: %v", r.GetRepository()) } r.managedRepository.RepoChecksum = checksum(bytes.NewReader(r.managedRepository.RepoFileContents)) r.managedRepository.RepoFilePath = fmt.Sprintf(filePath, r.managedRepository.RepoChecksum[:10]) return &ManagedResources{Repositories: []ManagedRepository{r.managedRepository}}, nil } func contentsMatch(path, chksum string) (bool, error) { file, err := os.OpenFile(path, os.O_RDONLY, 0644) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } defer file.Close() return chksum == checksum(file), nil } func (r *repositoryResource) checkState(ctx context.Context) (inDesiredState bool, err error) { // Check APT gpg key if applicable. if r.managedRepository.Apt != nil && r.managedRepository.Apt.GpgFileContents != nil { match, err := contentsMatch(r.managedRepository.Apt.GpgFilePath, r.managedRepository.Apt.GpgChecksum) if err != nil { return false, err } if !match { return false, nil } } return contentsMatch(r.managedRepository.RepoFilePath, r.managedRepository.RepoChecksum) } func (r *repositoryResource) enforceState(ctx context.Context) (inDesiredState bool, err error) { // Set APT gpg key if applicable. if r.managedRepository.Apt != nil && r.managedRepository.Apt.GpgFileContents != nil { if err := ioutil.WriteFile(r.managedRepository.Apt.GpgFilePath, r.managedRepository.Apt.GpgFileContents, 0644); err != nil { return false, err } } if err := os.MkdirAll(filepath.Dir(r.managedRepository.RepoFilePath), 0755); err != nil { return false, err } if err := util.AtomicWrite(r.managedRepository.RepoFilePath, r.managedRepository.RepoFileContents, 0644); err != nil { return false, err } return true, nil } func (r *repositoryResource) cleanup(ctx context.Context) error { return nil } osconfig-20210219.00/config/repository_resource_test.go000066400000000000000000000211211401404514100227510ustar00rootroot00000000000000// Copyright 2020 Google 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 config import ( "context" "io/ioutil" "os" "path/filepath" "testing" "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/testing/protocmp" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1" ) var ( aptRepositoryResource = &agentendpointpb.OSPolicy_Resource_RepositoryResource_AptRepository{ ArchiveType: agentendpointpb.OSPolicy_Resource_RepositoryResource_AptRepository_DEB, Uri: "uri", Distribution: "distribution", Components: []string{"c1", "c2"}, } gooRepositoryResource = &agentendpointpb.OSPolicy_Resource_RepositoryResource_GooRepository{ Name: "name", Url: "url", } yumRepositoryResource = &agentendpointpb.OSPolicy_Resource_RepositoryResource_YumRepository{ Id: "id", DisplayName: "displayname", BaseUrl: "baseurl", GpgKeys: []string{"key1", "key2"}, } zypperRepositoryResource = &agentendpointpb.OSPolicy_Resource_RepositoryResource_ZypperRepository{ Id: "id", DisplayName: "displayname", BaseUrl: "baseurl", GpgKeys: []string{"key1", "key2"}, } ) func TestRepositoryResourceValidate(t *testing.T) { ctx := context.Background() var tests = []struct { name string rrpb *agentendpointpb.OSPolicy_Resource_RepositoryResource wantMR ManagedRepository }{ { "Apt", &agentendpointpb.OSPolicy_Resource_RepositoryResource{ Repository: &agentendpointpb.OSPolicy_Resource_RepositoryResource_Apt{ Apt: aptRepositoryResource, }, }, ManagedRepository{ Apt: &AptRepository{ RepositoryResource: aptRepositoryResource, }, RepoChecksum: "8faacd43b230b08e7a1da7b670bf6f90fcc59ade1a5e7179a0ccffc9aa3d7cdf", RepoFileContents: []byte("# Repo file managed by Google OSConfig agent\ndeb uri distribution c1 c2\n"), RepoFilePath: "/etc/apt/sources.list.d/osconfig_managed_8faacd43b2.repo", }, }, { "GooGet", &agentendpointpb.OSPolicy_Resource_RepositoryResource{ Repository: &agentendpointpb.OSPolicy_Resource_RepositoryResource_Goo{ Goo: gooRepositoryResource, }, }, ManagedRepository{ GooGet: &GooGetRepository{ RepositoryResource: gooRepositoryResource, }, RepoChecksum: "76ae1ea015cd184a18434ae45e233c22f36e35faa80f495cf21f95495af3b599", RepoFileContents: []byte("# Repo file managed by Google OSConfig agent\n- name: name\n url: url\n"), RepoFilePath: "C:/ProgramData/GooGet/repos/osconfig_managed_76ae1ea015.repo", }, }, { "Yum", &agentendpointpb.OSPolicy_Resource_RepositoryResource{ Repository: &agentendpointpb.OSPolicy_Resource_RepositoryResource_Yum{ Yum: yumRepositoryResource, }, }, ManagedRepository{ Yum: &YumRepository{ RepositoryResource: yumRepositoryResource, }, RepoChecksum: "9eddc09d15a4e9b515068cffb2e0640dab010ccd9cb0eeb63b8c455e06696656", RepoFileContents: []byte("# Repo file managed by Google OSConfig agent\n[id]\nname=displayname\nbaseurl=baseurl\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=key1\n key2\n"), RepoFilePath: "/etc/yum.repos.d/osconfig_managed_9eddc09d15.repo", }, }, { "Zypper", &agentendpointpb.OSPolicy_Resource_RepositoryResource{ Repository: &agentendpointpb.OSPolicy_Resource_RepositoryResource_Zypper{ Zypper: zypperRepositoryResource, }, }, ManagedRepository{ Zypper: &ZypperRepository{ RepositoryResource: zypperRepositoryResource, }, RepoChecksum: "9eddc09d15a4e9b515068cffb2e0640dab010ccd9cb0eeb63b8c455e06696656", RepoFileContents: []byte("# Repo file managed by Google OSConfig agent\n[id]\nname=displayname\nbaseurl=baseurl\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=key1\n key2\n"), RepoFilePath: "/etc/zypp/repos.d/osconfig_managed_9eddc09d15.repo", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_Repository{Repository: tt.rrpb}, }, } if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected error: %v", err) } if diff := cmp.Diff(pr.ManagedResources(), &ManagedResources{Repositories: []ManagedRepository{tt.wantMR}}, protocmp.Transform()); diff != "" { t.Errorf("OSPolicyResource does not match expectation: (-got +want)\n%s", diff) } if diff := cmp.Diff(pr.resource.(*repositoryResource).managedRepository, tt.wantMR, protocmp.Transform()); diff != "" { t.Errorf("packageResouce does not match expectation: (-got +want)\n%s", diff) } }) } } func TestRepositoryResourceCheckState(t *testing.T) { ctx := context.Background() var tests = []struct { name string rrpb *agentendpointpb.OSPolicy_Resource_RepositoryResource contents []byte wantInDesiredState bool }{ { "Matches", &agentendpointpb.OSPolicy_Resource_RepositoryResource{ Repository: &agentendpointpb.OSPolicy_Resource_RepositoryResource_Apt{ Apt: aptRepositoryResource, }, }, []byte("# Repo file managed by Google OSConfig agent\ndeb uri distribution c1 c2\n"), true, }, { "DoesNotMatch", &agentendpointpb.OSPolicy_Resource_RepositoryResource{ Repository: &agentendpointpb.OSPolicy_Resource_RepositoryResource_Apt{ Apt: aptRepositoryResource, }, }, []byte("# Repo file managed by Google OSConfig agent\nsome other repo\n"), false, }, { "NoRepoFile", &agentendpointpb.OSPolicy_Resource_RepositoryResource{ Repository: &agentendpointpb.OSPolicy_Resource_RepositoryResource_Apt{ Apt: aptRepositoryResource, }, }, nil, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_Repository{Repository: tt.rrpb}, }, } if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected Validate error: %v", err) } dir, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } defer os.Remove(dir) path := filepath.Join(dir, "repo") if tt.contents != nil { if err := ioutil.WriteFile(path, tt.contents, 0755); err != nil { t.Fatal(err) } } pr.resource.(*repositoryResource).managedRepository.RepoFilePath = path if err := pr.CheckState(ctx); err != nil { t.Fatalf("Unexpected CheckState error: %v", err) } if tt.wantInDesiredState != pr.InDesiredState() { t.Fatalf("Unexpected InDesiredState, want: %t, got: %t", tt.wantInDesiredState, pr.InDesiredState()) } }) } } func TestRepositoryResourceEnforceState(t *testing.T) { ctx := context.Background() dir, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } defer os.Remove(dir) path := filepath.Join(dir, "repo") if err := ioutil.WriteFile(path, []byte("foo"), 0755); err != nil { t.Fatal(err) } rrpb := &agentendpointpb.OSPolicy_Resource_RepositoryResource{ Repository: &agentendpointpb.OSPolicy_Resource_RepositoryResource_Apt{ Apt: aptRepositoryResource, }, } for _, tt := range []struct { name string path string }{ { "FileExists", path, }, { "FileDNE", filepath.Join(dir, "some/other/dir/repo"), }, } { t.Run(tt.name, func(t *testing.T) { pr := &OSPolicyResource{ OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{ ResourceType: &agentendpointpb.OSPolicy_Resource_Repository{Repository: rrpb}, }, } if err := pr.Validate(ctx); err != nil { t.Fatalf("Unexpected Validate error: %v", err) } pr.resource.(*repositoryResource).managedRepository.RepoFilePath = tt.path if err := pr.EnforceState(ctx); err != nil { t.Fatalf("Unexpected EnforceState error: %v", err) } match, err := contentsMatch(pr.resource.(*repositoryResource).managedRepository.RepoFilePath, pr.resource.(*repositoryResource).managedRepository.RepoChecksum) if err != nil { t.Fatal(err) } if !match { t.Fatal("Repo file contents do not match after enforcement") } }) } } osconfig-20210219.00/e2e_tests/000077500000000000000000000000001401404514100156705ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/Dockerfile000066400000000000000000000017521401404514100176670ustar00rootroot00000000000000# Copyright 2018 Google 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. # We use this image just for ca-certificates.crt FROM golang:alpine COPY . /source WORKDIR /source RUN go mod download && CGO_ENABLED=0 go build -o /e2e_tests main.go RUN chmod +x /e2e_tests FROM gcr.io/compute-image-tools-test/wrapper:latest COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=0 /e2e_tests e2e_tests ENTRYPOINT ["./wrapper", "./e2e_tests"] osconfig-20210219.00/e2e_tests/cloudbuild.yaml000066400000000000000000000004441401404514100207040ustar00rootroot00000000000000steps: - name: 'gcr.io/cloud-builders/docker' args: ['build', '--tag=gcr.io/$PROJECT_ID/osconfig-tests:latest', '--tag=gcr.io/$PROJECT_ID/osconfig-tests:$COMMIT_SHA', './e2e_tests'] images: - 'gcr.io/$PROJECT_ID/osconfig-tests:latest' - 'gcr.io/$PROJECT_ID/osconfig-tests:$COMMIT_SHA' osconfig-20210219.00/e2e_tests/compute/000077500000000000000000000000001401404514100173445ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/compute/instance.go000066400000000000000000000144471401404514100215110ustar00rootroot00000000000000// Copyright 2018 Google 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 compute contains wrappers around the GCE compute API. package compute import ( "context" "fmt" "net/http" "os" "path" "regexp" "strings" "time" daisyCompute "github.com/GoogleCloudPlatform/compute-image-tools/daisy/compute" computeApiBeta "google.golang.org/api/compute/v0.beta" computeApi "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" ) // Instance is a compute instance. type Instance struct { *computeApi.Instance client daisyCompute.Client Project, Zone string } // Cleanup deletes the Instance. func (i *Instance) Cleanup() { if err := i.client.DeleteInstance(i.Project, i.Zone, i.Name); err != nil { fmt.Printf("Error deleting instance: %v\n", err) } } // WaitForGuestAttributes waits for guest attribute (queryPath, variableKey) to appear. func (i *Instance) WaitForGuestAttributes(queryPath string, interval, timeout time.Duration) ([]*computeApiBeta.GuestAttributesEntry, error) { tick := time.Tick(interval) timedout := time.Tick(timeout) for { select { case <-timedout: return nil, fmt.Errorf("timed out waiting for guest attribute %q", queryPath) case <-tick: attr, err := i.GetGuestAttributes(queryPath) if err != nil { apiErr, ok := err.(*googleapi.Error) if ok && apiErr.Code == http.StatusNotFound { continue } return nil, err } return attr, nil } } } // GetGuestAttributes gets guest attributes for an instance. func (i *Instance) GetGuestAttributes(queryPath string) ([]*computeApiBeta.GuestAttributesEntry, error) { resp, err := i.client.GetGuestAttributes(i.Project, i.Zone, i.Name, queryPath, "") if err != nil { return nil, err } if resp.QueryValue == nil { return nil, nil } return resp.QueryValue.Items, nil } // AddMetadata adds metadata to the instance. func (i *Instance) AddMetadata(mdi ...*computeApi.MetadataItems) error { resp, err := i.client.GetInstance(i.Project, i.Zone, i.Name) if err != nil { return err } for _, old := range resp.Metadata.Items { found := false for _, new := range mdi { if old.Key == new.Key { found = true break } } if found { continue } mdi = append(mdi, old) } resp.Metadata.Items = mdi return i.client.SetInstanceMetadata(i.Project, i.Zone, i.Name, resp.Metadata) } // WaitForSerialOutput waits for all positive regex matches and reports error for any negative regex match on a serial port. func (i *Instance) WaitForSerialOutput(positiveRegexes []*regexp.Regexp, negativeRegexes []*regexp.Regexp, port int64, interval, timeout time.Duration) error { var start int64 var errs int matches := make([]bool, len(positiveRegexes)) tick := time.Tick(interval) timedout := time.Tick(timeout) for { select { case <-timedout: var patterns []string for _, regex := range positiveRegexes { patterns = append(patterns, regex.String()) } return fmt.Errorf("timed out waiting for regexes [%s]", strings.Join(patterns, ",")) case <-tick: resp, err := i.client.GetSerialPortOutput(i.Project, i.Zone, i.Name, port, start) if err != nil { status, sErr := i.client.InstanceStatus(i.Project, i.Zone, i.Name) if sErr != nil { err = fmt.Errorf("%v, error getting InstanceStatus: %v", err, sErr) } else { err = fmt.Errorf("%v, InstanceStatus: %q", err, status) } // Wait until machine restarts to evaluate SerialOutput. if isTerminal(status) { continue } // Retry up to 3 times in a row on any error if we successfully got InstanceStatus. if errs < 3 { errs++ continue } return err } start = resp.Next for _, ln := range strings.Split(resp.Contents, "\n") { for _, regex := range negativeRegexes { if regex.MatchString(ln) { return fmt.Errorf("matched negative regexes [%s]", regex) } } for i, regex := range positiveRegexes { if regex.MatchString(ln) { matches[i] = true } } matched := 0 for _, match := range matches { if match { matched++ } } if matched == len(positiveRegexes) { return nil } } errs = 0 } } } // RecordSerialOutput stores the serial output of an instance to GCS bucket func (i *Instance) RecordSerialOutput(ctx context.Context, logsPath string, port int64) { os.MkdirAll(logsPath, 0770) f, err := os.Create(path.Join(logsPath, fmt.Sprintf("%s-serial-port%d.log", i.Name, port))) if err != nil { fmt.Printf("Instance %q: error creating serial log file: %s", i.Name, err) } resp, err := i.client.GetSerialPortOutput(path.Base(i.Project), path.Base(i.Zone), i.Name, port, 0) if err != nil { // Instance is stopped or stopping. status, _ := i.client.InstanceStatus(path.Base(i.Project), path.Base(i.Zone), i.Name) if !isTerminal(status) { fmt.Printf("Instance %q: error getting serial port: %s", i.Name, err) } return } if _, err := f.Write([]byte(resp.Contents)); err != nil { fmt.Printf("Instance %q: error writing serial log file: %s", i.Name, err) } if err := f.Close(); err != nil { fmt.Printf("Instance %q: error closing serial log file: %s", i.Name, err) } } func isTerminal(status string) bool { return status == "TERMINATED" || status == "STOPPED" || status == "STOPPING" } // CreateInstance creates a compute instance. func CreateInstance(client daisyCompute.Client, project, zone string, i *computeApi.Instance) (*Instance, error) { if err := client.CreateInstance(project, zone, i); err != nil { return nil, err } return &Instance{Instance: i, client: client, Project: project, Zone: zone}, nil } // BuildInstanceMetadataItem create an metadata item func BuildInstanceMetadataItem(key, value string) *computeApi.MetadataItems { return &computeApi.MetadataItems{ Key: key, Value: func() *string { v := value; return &v }(), } } osconfig-20210219.00/e2e_tests/config/000077500000000000000000000000001401404514100171355ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/config/config.go000066400000000000000000000100211401404514100207230ustar00rootroot00000000000000// Copyright 2019 Google 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 config import ( "encoding/json" "flag" "fmt" "math" "os" "regexp" "strings" "time" ) var ( agentEndpoint = flag.String("agent_endpoint", "", "API endpoint to use for the agent to use for the tests") endpoint = flag.String("endpoint", "osconfig.googleapis.com:443", "API endpoint to use for the tests") oauthDefault = flag.String("local_oauth", "", "path to service creds file") agentRepo = flag.String("agent_repo", "", "repo to pull agent from (unstable, staging, or stable, leave blank for no agent install)") bucketDefault = "osconfig-agent-end2end-tests" logPushIntervalDefault = 3 * time.Second logsPath = fmt.Sprintf("logs-%s", time.Now().Format("2006-01-02-15:04:05")) testSuiteRegex *regexp.Regexp testSuiteFilter = flag.String("test_suite_filter", "", "test suite filter") testCaseRegex *regexp.Regexp testCaseFilter = flag.String("test_case_filter", "", "test case filter") zones map[string]int testZone = flag.String("test_zone", "", "test zone") testZones = flag.String("test_zones", "{}", "test zones") projects []string testProjectIDs = flag.String("test_project_ids", "", "test project ids") // OutDir is the out directory to use. OutDir = flag.String("out_dir", "/tmp", "artifact directory") ) func init() { flag.Parse() if *testSuiteFilter != "" { var err error testSuiteRegex, err = regexp.Compile(*testSuiteFilter) if err != nil { fmt.Println("-test_suite_filter flag not valid:", err) os.Exit(1) } } if *testCaseFilter != "" { var err error testCaseRegex, err = regexp.Compile(*testCaseFilter) if err != nil { fmt.Println("-test_case_filter flag not valid:", err) os.Exit(1) } } if len(strings.TrimSpace(*testProjectIDs)) == 0 { fmt.Println("-test_project_ids must be specified") os.Exit(1) } projects = strings.Split(*testProjectIDs, ",") zones = make(map[string]int) if len(strings.TrimSpace(*testZone)) != 0 { zones[*testZone] = math.MaxInt32 } else { err := json.Unmarshal([]byte(*testZones), &zones) if err != nil { fmt.Printf("Error parsing zones `%s`\n", *testZones) os.Exit(1) } } if len(zones) == 0 { fmt.Println("Error, no zones specified") os.Exit(1) } } // Projects are the projects to use. func Projects() []string { return projects } // Zones are the zones and associated quota to use. func Zones() map[string]int { return zones } // TestSuiteFilter is the test suite filter regex. func TestSuiteFilter() *regexp.Regexp { return testSuiteRegex } // TestCaseFilter is the test case filter regex. func TestCaseFilter() *regexp.Regexp { return testCaseRegex } // AgentRepo returns the agentRepo func AgentRepo() string { return *agentRepo } // AgentSvcEndpoint returns the agentEndpoint func AgentSvcEndpoint() string { return *agentEndpoint } // SvcEndpoint returns the endpoint func SvcEndpoint() string { return *endpoint } // OauthPath returns the oauthPath file path func OauthPath() string { return *oauthDefault } // LogBucket returns the oauthPath file path func LogBucket() string { return bucketDefault } // LogsPath returns the oauthPath file path func LogsPath() string { return logsPath } // LogPushInterval returns the interval at which the serial console logs are written to GCS func LogPushInterval() time.Duration { return logPushIntervalDefault } osconfig-20210219.00/e2e_tests/gcp_clients/000077500000000000000000000000001401404514100201625ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/gcp_clients/gcp_clients.go000066400000000000000000000041231401404514100230030ustar00rootroot00000000000000// Copyright 2019 Google 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 gcpclients import ( "context" "fmt" osconfigV1beta "cloud.google.com/go/osconfig/apiv1beta" "github.com/GoogleCloudPlatform/compute-image-tools/daisy/compute" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/config" "google.golang.org/api/option" ) var ( computeClient compute.Client osconfigClientV1beta *osconfigV1beta.Client ) // PopulateClients populates the GCP clients. func PopulateClients(ctx context.Context) error { if err := createComputeClient(ctx); err != nil { return err } return createOsConfigClientV1beta(ctx) } func createComputeClient(ctx context.Context) error { var err error computeClient, err = compute.NewClient(ctx, option.WithCredentialsFile(config.OauthPath())) return err } func createOsConfigClientV1beta(ctx context.Context) error { var err error osconfigClientV1beta, err = osconfigV1beta.NewClient(ctx, option.WithCredentialsFile(config.OauthPath()), option.WithEndpoint(config.SvcEndpoint())) return err } // GetComputeClient returns a singleton GCP client for osconfig tests func GetComputeClient() (compute.Client, error) { if computeClient == nil { return nil, fmt.Errorf("compute client was not initialized") } return computeClient, nil } // GetOsConfigClientV1beta returns a singleton GCP client for osconfig tests func GetOsConfigClientV1beta() (*osconfigV1beta.Client, error) { if osconfigClientV1beta == nil { return nil, fmt.Errorf("v1beta osconfig client was not initialized") } return osconfigClientV1beta, nil } osconfig-20210219.00/e2e_tests/go.mod000066400000000000000000000012311401404514100167730ustar00rootroot00000000000000module github.com/GoogleCloudPlatform/osconfig/e2e_tests go 1.13 require ( cloud.google.com/go v0.62.0 github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200806162442-cbe93c2f2b04 github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils v0.0.0-20200806162442-cbe93c2f2b04 github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20200113214433-6cbb518174d4 github.com/GoogleCloudPlatform/osconfig v0.0.0-20200805223003-f44a6489e0f4 github.com/golang/protobuf v1.4.2 github.com/kylelemons/godebug v1.1.0 google.golang.org/api v0.30.0 google.golang.org/genproto v0.0.0-20200804151602-45615f50871c google.golang.org/grpc v1.31.0 ) osconfig-20210219.00/e2e_tests/go.sum000066400000000000000000001577671401404514100170520ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.47.0 h1:1JUtpcY9E7+eTospEwWS2QXP3DEn7poB3E2j0jN74mM= cloud.google.com/go v0.47.0/go.mod h1:5p3Ky/7f3N10VBkhuR5LFtddroTiMyjZV/Kj5qOQFxU= cloud.google.com/go v0.50.0 h1:0E3eE8MX426vUOs7aHfI7aN1BrIzzzf4ccKCSfSjGmc= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.55.0/go.mod h1:ZHmoY+/lIMNkN2+fBmuTiqZ4inFhvQad8ft7MT8IV5Y= cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= cloud.google.com/go v0.61.0 h1:NLQf5e1OMspfNT1RAHOB3ublr1TW3YTXO8OiWwVjK2U= cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw= cloud.google.com/go v0.62.0 h1:RmDygqvj27Zf3fCQjQRtLyC7KwFcHkeJitcO0OoGOcA= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.1.0 h1:Fm4frQlg3F76NQKi1VHb+PskluqTvlBpzdETxNTlbyw= cloud.google.com/go/bigquery v1.1.0/go.mod h1:g4RsfUkOvV3Vi5yRujQETpqwCN0F+faPZ2/ykNYfBJc= cloud.google.com/go/bigquery v1.3.0 h1:sAbMqjY1PEQKZBWfbu6Y6bsupJ9c4QdHnzg/VvYTLcE= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/logging v1.0.0 h1:kaunpnoEh9L4hu6JUsBa8Y20LBfKnCuDhKUgdZp7oK8= cloud.google.com/go/logging v1.0.0/go.mod h1:V1cc3ogwobYzQq5f2R7DS/GvRIrI4FKj01Gs5glwAls= cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0 h1:9/vpR43S4aJaROxqQHQ3nH9lfyKKV0dC3vOmnw8ebQQ= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.1.1/go.mod h1:nbQkUX8zrWh07WKekXr/Phd0q/ERj4IOJnkE+v56Qys= cloud.google.com/go/storage v1.4.0/go.mod h1:ZusYJWlOshgSBGbt6K3GnB3MT3H1xs2id9+TCl4fDBA= cloud.google.com/go/storage v1.5.0 h1:RPUcBvDeYgQFMfQu1eBMq6piD1SXmLH+vK3qjewZPus= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/compute-image-tools/cli_tools v0.0.0-20200114193945-760dc9647411/go.mod h1:928Ttg3SXjfF1mOI37hjnFUahdU4R234SpDcgcBO0I8= github.com/GoogleCloudPlatform/compute-image-tools/cli_tools v0.0.0-20200406182414-bf9021434372/go.mod h1:zkxkF+fdCQyagLcSSWbQeDcvShTFro3HbulNmXzbBJg= github.com/GoogleCloudPlatform/compute-image-tools/cli_tools v0.0.0-20200805192452-5b81051e3e71/go.mod h1:ZUhSc6SjsB6cNgBVCZrYlB8ptdxEgnkDLD0Z/yyizQQ= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200111002017-35d3756114c9/go.mod h1:F+pk+WYiOJPq6m9oQkFZ3Vyw2l4nYJUgDyl4UW58+Q4= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200114193104-06c8be9a6a7d/go.mod h1:F+pk+WYiOJPq6m9oQkFZ3Vyw2l4nYJUgDyl4UW58+Q4= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200114232830-6d2d59acb179/go.mod h1:LyRbPkZMX3sR7WdIxBa1OYQlAO4opSezuhOXjc5xd2I= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200214212030-cdab85f8f241/go.mod h1:6xFWwnAALibczYyaAbXDaQSRIyqZ+A/uzJeGcr3kvcQ= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200406182414-bf9021434372/go.mod h1:OMlTSl6FloIMDs+kZxW4B5fVlj4RudzYDvKEQ+sH15U= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200721213208-00ba2b996712/go.mod h1:OMlTSl6FloIMDs+kZxW4B5fVlj4RudzYDvKEQ+sH15U= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200805192452-5b81051e3e71/go.mod h1:OMlTSl6FloIMDs+kZxW4B5fVlj4RudzYDvKEQ+sH15U= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200806162442-cbe93c2f2b04 h1:bREkuvb9ZJYMestC3GMfPGvYSDpPU52jf/234EqnX6A= github.com/GoogleCloudPlatform/compute-image-tools/daisy v0.0.0-20200806162442-cbe93c2f2b04/go.mod h1:OMlTSl6FloIMDs+kZxW4B5fVlj4RudzYDvKEQ+sH15U= github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils v0.0.0-20200114232830-6d2d59acb179/go.mod h1:2hxlZPfWalIdOXnzbPATwW+Rs7FMMNWD5QnHDE49QZA= github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils v0.0.0-20200128181915-c0775e429077/go.mod h1:2hxlZPfWalIdOXnzbPATwW+Rs7FMMNWD5QnHDE49QZA= github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils v0.0.0-20200806162442-cbe93c2f2b04 h1:yLzl1dXe8RaxfD1DsYaMVEH2MTCK4xx2CzVsh8Nn/pM= github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils v0.0.0-20200806162442-cbe93c2f2b04/go.mod h1:93wnO6RFVF68ebG+Lfz/o4ial0dnufRnFznlvp8KUdM= github.com/GoogleCloudPlatform/compute-image-tools/mocks v0.0.0-20200206014411-426b6301f679/go.mod h1:zDumkSJOQQ31fB0OWcg5h5zlVhU92LvYNXkpGQh2jz8= github.com/GoogleCloudPlatform/compute-image-tools/mocks v0.0.0-20200414213327-359251a2c860/go.mod h1:2Tz4TQzUbAzDwoQE70BFKJfWKLoZnhHuDZ28rJ//2g4= github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20191226203445-d798144cee48/go.mod h1:YWYrZjvs/qwZhDmsDnTaMXcno5Y0MYPN7rhMarLiUmI= github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20200113214433-6cbb518174d4 h1:n38UnKhQmPxZ9+PTKW38D0TJAmdiElHMlTM0uT2LACg= github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20200113214433-6cbb518174d4/go.mod h1:YWYrZjvs/qwZhDmsDnTaMXcno5Y0MYPN7rhMarLiUmI= github.com/GoogleCloudPlatform/osconfig v0.0.0-20200113163233-44035fcbfdd9/go.mod h1:nljBbLz4lHbiK1P6TwNhW0tanjfrRMYgtvLnLTIxygk= github.com/GoogleCloudPlatform/osconfig v0.0.0-20200115000133-a7c6fc334759/go.mod h1:nljBbLz4lHbiK1P6TwNhW0tanjfrRMYgtvLnLTIxygk= github.com/GoogleCloudPlatform/osconfig v0.0.0-20200211005319-080372593330/go.mod h1:nT1RIv+FL9qTdvhWYUd85m6Vpcy2fi7ZpLwS6Yuflb0= github.com/GoogleCloudPlatform/osconfig v0.0.0-20200721210327-c9ab1b6aeb02/go.mod h1:nfw/IdEPsQueLEhAFL9anXqtORP0hQoCF0P1sd+X398= github.com/GoogleCloudPlatform/osconfig v0.0.0-20200805223003-f44a6489e0f4 h1:BrwCk13QXvcAFJns0/FQkeQQ65Ed3SVWxSIr1o2+YFs= github.com/GoogleCloudPlatform/osconfig v0.0.0-20200805223003-f44a6489e0f4/go.mod h1:sgtF91LRn9rYWV9OTLSW7ZSAxXctyKWx7tNTNKk4+hA= github.com/GoogleCloudPlatform/osconfig/e2e_tests v0.0.0-20200128231920-2ddeb2407498/go.mod h1:YHx1ltdJOZNKtDNUBBIwzyiq4Kh4EwQWm1NOcMEU/OA= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/aws/aws-sdk-go v1.33.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= 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= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc h1:55rEp52jU6bkyslZ1+C/7NGfpQsEc6pxGLAGDOctqbw= github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/logger v1.0.1/go.mod h1:w7O8nrRr0xufejBlQMI83MXqRusvREoJdaAxV+CoAB4= github.com/google/logger v1.1.0/go.mod h1:w7O8nrRr0xufejBlQMI83MXqRusvREoJdaAxV+CoAB4= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v0.0.0-20170306145142-6a5e28554805/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/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/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= 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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vmware/govmomi v0.22.0/go.mod h1:Y+Wq4lst78L85Ge/F8+ORXIWiKYqaro1vhAulACy9Lc= github.com/vmware/govmomi v0.22.1/go.mod h1:Y+Wq4lst78L85Ge/F8+ORXIWiKYqaro1vhAulACy9Lc= github.com/vmware/govmomi v0.22.2/go.mod h1:Y+Wq4lst78L85Ge/F8+ORXIWiKYqaro1vhAulACy9Lc= github.com/vmware/govmomi v0.23.1/go.mod h1:Y+Wq4lst78L85Ge/F8+ORXIWiKYqaro1vhAulACy9Lc= github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20190912063710-ac5d2bfcbfe0/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191014171548-69215a2ee97e/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587 h1:5Uz0rkjCFu9BC9gCRN7EkwVvhNyQgGWb8KNJrPwBoHY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299 h1:zQpM52jfKHG6II1ISZY1ZcpygvuSFZpLwfluuF89XOg= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200213203834-85f925bdd4d0/go.mod h1:IX6Eufr4L0ErOUlzqX/aFlHqsiKZRbV42Kb69e9VsTE= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA= golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107160858-eca82077e2d1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200406155108-e3b113bbe6a4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 h1:X9xIZ1YU8bLZA3l6gqDUHSFiD0GFI9S548h6C8nDtOY= golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200805065543-0cf7623e9dbd h1:wefLe/3g5tC0FcXw3NneLA5tHgbyouyZlfcSjNfOdgk= golang.org/x/sys v0.0.0-20200805065543-0cf7623e9dbd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190917162342-3b4f30a44f3b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010171213-8abd42400456/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191017205301-920acffc3e65/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4 h1:Toz2IK7k8rbltAXwNAxKcn9OzqyNfMUhUNjz3sL0NMk= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200107184032-11e9d9cc0042/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200116165751-0508ad4c83ab/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200206152323-64a0f23fc32d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200214201135-548b770e2dfa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200317043434-63da46f3035e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200318150045-ba25ddc85566/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200406172401-903869a8272d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200709181711-e327e1019dfe/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200721223218-6123e77877b2/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200722181740-bd1e9de8d890 h1:Fwx9UWtbBIMKQ+hdL1ltEyRaLkbpvN+ii5XUAz9o2n8= golang.org/x/tools v0.0.0-20200722181740-bd1e9de8d890/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200805200255-b4d825fe358b h1:FoNkEg+WEvE2k8Z7DQ05FJgpT6LxtRQrDjuB0I6s3O8= golang.org/x/tools v0.0.0-20200805200255-b4d825fe358b/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.11.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0 h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.16.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200113173426-e1de0a7b01eb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200205142000-a86caf926a67/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200317114155-1f3552e48f24/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200318110522-7735f76e9fa5/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200406120821-33397c535dc2/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYUO2VM9mDLqo2V6CFBY84lFSZAfoi4= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200709005830-7a2ca40e9dc3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200721032028-5044d0edf986/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200722002428-88e341933a54 h1:ASrBgpl9XvkNTP0m39/j18mid7aoF21npu2ioIBxYnY= google.golang.org/genproto v0.0.0-20200722002428-88e341933a54/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804151602-45615f50871c h1:TQlznJ1HicL4W+4znnmEEyr2A6ncsbY9Il37TcoDWYY= google.golang.org/genproto v0.0.0-20200804151602-45615f50871c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= osconfig-20210219.00/e2e_tests/main.go000066400000000000000000000066771401404514100171630ustar00rootroot00000000000000// Copyright 2018 Google 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 main import ( "bytes" "context" "encoding/xml" "fmt" "io" "io/ioutil" "log" "os" "path/filepath" "regexp" "sync" "github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils/junitxml" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/config" gcpclients "github.com/GoogleCloudPlatform/osconfig/e2e_tests/gcp_clients" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/test_suites/guestpolicies" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/test_suites/inventory" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/test_suites/inventoryreporting" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/test_suites/patch" _ "google.golang.org/genproto/googleapis/rpc/errdetails" ) var testFunctions = []func(context.Context, *sync.WaitGroup, chan *junitxml.TestSuite, *log.Logger, *regexp.Regexp, *regexp.Regexp){ guestpolicies.TestSuite, inventory.TestSuite, inventoryreporting.TestSuite, patch.TestSuite, } type logWriter struct { log *log.Logger } func (l *logWriter) Write(b []byte) (int, error) { l.log.Print(string(b)) return len(b), nil } func main() { ctx := context.Background() if err := gcpclients.PopulateClients(ctx); err != nil { log.Fatal(err) } testLogger := log.New(os.Stdout, "[OsConfigTests] ", 0) testLogger.Println("Starting...") // Initialize logger for any shared function calls. opts := logger.LogOpts{LoggerName: "OsConfigTests", Debug: true, Writers: []io.Writer{&logWriter{log: testLogger}}, DisableCloudLogging: true, DisableLocalLogging: true} logger.Init(ctx, opts) tests := make(chan *junitxml.TestSuite) var wg sync.WaitGroup for _, tf := range testFunctions { wg.Add(1) go tf(ctx, &wg, tests, testLogger, config.TestSuiteFilter(), config.TestCaseFilter()) } go func() { wg.Wait() close(tests) }() var testSuites []*junitxml.TestSuite for ret := range tests { testSuites = append(testSuites, ret) testSuiteOutPath := filepath.Join(*config.OutDir, fmt.Sprintf("junit_%s.xml", ret.Name)) if err := os.MkdirAll(filepath.Dir(testSuiteOutPath), 0770); err != nil { testLogger.Fatal(err) } testLogger.Printf("Creating junit xml file: %s", testSuiteOutPath) d, err := xml.MarshalIndent(ret, " ", " ") if err != nil { testLogger.Fatal(err) } if err := ioutil.WriteFile(testSuiteOutPath, d, 0644); err != nil { testLogger.Fatal(err) } } var buf bytes.Buffer for _, ts := range testSuites { if ts.Failures > 0 { buf.WriteString(fmt.Sprintf("TestSuite %q has errors:\n", ts.Name)) for _, tc := range ts.TestCase { if tc.Failure != nil { buf.WriteString(fmt.Sprintf(" - %q: %s\n", tc.Name, tc.Failure.FailMessage)) } } } } if buf.Len() > 0 { testLogger.Fatalf("%sExiting with exit code 1", buf.String()) } testLogger.Print("All test cases completed successfully.") } osconfig-20210219.00/e2e_tests/osconfig_server/000077500000000000000000000000001401404514100210655ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/osconfig_server/osconfig_data_builder.go000066400000000000000000000067511401404514100257330ustar00rootroot00000000000000// Copyright 2019 Google 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 osconfigserver import ( "fmt" osconfigpb "google.golang.org/genproto/googleapis/cloud/osconfig/v1beta" ) // BuildPackagePolicy creates an package policy. func BuildPackagePolicy(installs, removes, upgrades []string) []*osconfigpb.Package { var pkgs []*osconfigpb.Package for _, p := range installs { pkgs = append(pkgs, &osconfigpb.Package{ DesiredState: osconfigpb.DesiredState_INSTALLED, Name: p, }) } for _, p := range removes { pkgs = append(pkgs, &osconfigpb.Package{ DesiredState: osconfigpb.DesiredState_REMOVED, Name: p, }) } for _, p := range upgrades { pkgs = append(pkgs, &osconfigpb.Package{ DesiredState: osconfigpb.DesiredState_UPDATED, Name: p, }) } return pkgs } // BuildAptRepository create an apt repository object func BuildAptRepository(archiveType osconfigpb.AptRepository_ArchiveType, uri, distribution, keyuri string, components []string) *osconfigpb.PackageRepository_Apt { return &osconfigpb.PackageRepository_Apt{ Apt: &osconfigpb.AptRepository{ ArchiveType: archiveType, Uri: uri, Distribution: distribution, Components: components, GpgKey: keyuri, }, } } // BuildYumRepository create an yum repository object func BuildYumRepository(id, name, baseURL string, gpgkeys []string) *osconfigpb.PackageRepository_Yum { return &osconfigpb.PackageRepository_Yum{ Yum: &osconfigpb.YumRepository{ Id: id, DisplayName: name, BaseUrl: baseURL, GpgKeys: gpgkeys, }, } } // BuildZypperRepository create an zypper repository object func BuildZypperRepository(id, name, baseURL string, gpgkeys []string) *osconfigpb.PackageRepository_Zypper { return &osconfigpb.PackageRepository_Zypper{ Zypper: &osconfigpb.ZypperRepository{ Id: id, DisplayName: name, BaseUrl: baseURL, GpgKeys: gpgkeys, }, } } // BuildGooRepository create an googet repository object func BuildGooRepository(name, url string) *osconfigpb.PackageRepository_Goo { return &osconfigpb.PackageRepository_Goo{ Goo: &osconfigpb.GooRepository{ Name: name, Url: url, }, } } // BuildInstanceFilterExpression creates an instance filter expression to // be used by Assignment func BuildInstanceFilterExpression(instance string) string { return fmt.Sprintf("instance.name==\"%s\"", instance) } // BuildPackage creates an os config package func BuildPackage(name string) *osconfigpb.Package { return &osconfigpb.Package{ Name: name, } } // BuildSoftwareRecipe create a SoftwareRecipe with the specified name, version, artifacts, and installSteps func BuildSoftwareRecipe(name, version string, artifacts []*osconfigpb.SoftwareRecipe_Artifact, installSteps []*osconfigpb.SoftwareRecipe_Step) *osconfigpb.SoftwareRecipe { return &osconfigpb.SoftwareRecipe{ Name: name, Version: version, Artifacts: artifacts, InstallSteps: installSteps, } } osconfig-20210219.00/e2e_tests/test_config/000077500000000000000000000000001401404514100201745ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/test_config/project_config.go000066400000000000000000000055271401404514100235270ustar00rootroot00000000000000// Copyright 2019 Google 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 testconfig import ( "math/rand" "sync" "time" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/config" ) // Project is details of test Project. type Project struct { sync.Mutex TestProjectID string ServiceAccountEmail string ServiceAccountScopes []string testZones map[string]int zoneIndices []string } var mx sync.Mutex var projects = make(map[string]*Project) // GetProject creates a test Project to be used. func GetProject() *Project { projectIDs := config.Projects() projectID := projectIDs[rand.Intn(len(projectIDs))] mx.Lock() defer mx.Unlock() p, ok := projects[projectID] if ok { return p } testZones := map[string]int{} var zoneIndices []string for k, v := range config.Zones() { testZones[k] = v zoneIndices = append(zoneIndices, k) } p = &Project{ TestProjectID: projectID, testZones: testZones, zoneIndices: zoneIndices, ServiceAccountEmail: "default", ServiceAccountScopes: []string{ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/devstorage.full_control", }, } projects[projectID] = p return p } // AcquireZone returns a random zone that still has capacity, or waits until there is one. func (p *Project) AcquireZone() string { timer := time.NewTimer(30 * time.Minute) for { p.Lock() zc := len(p.zoneIndices) if zc == 0 { p.Unlock() select { case <-timer.C: return "Not enough zone quota sepcified. Specify additional quota in `test_zones`." default: time.Sleep(10 * time.Second) continue } } // Pick a random zone. zi := rand.Intn(zc) z := p.zoneIndices[zi] // Decrement the number of instances that this zone can host. p.testZones[z]-- // Remove this zone from zoneIndices if it can't host any more instances. if p.testZones[z] == 0 { p.zoneIndices = append(p.zoneIndices[:zi], p.zoneIndices[zi+1:]...) } p.Unlock() return z } } // ReleaseZone returns a zone so other tests can use it. func (p *Project) ReleaseZone(z string) { p.Lock() defer p.Unlock() n, ok := p.testZones[z] if !ok { // This shouldn't happen, but if it does just ignore it. return } if n == 0 { p.zoneIndices = append(p.zoneIndices, z) } p.testZones[z]++ } osconfig-20210219.00/e2e_tests/test_suites/000077500000000000000000000000001401404514100202435ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/test_suites/guestpolicies/000077500000000000000000000000001401404514100231225ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/test_suites/guestpolicies/guest_policies.go000066400000000000000000000225071401404514100264750ustar00rootroot00000000000000// Copyright 2019 Google 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 guestpolicies GuestPolicy osconfig agent tests. package guestpolicies import ( "context" "fmt" "log" "path" "regexp" "sync" "time" osconfigV1beta "cloud.google.com/go/osconfig/apiv1beta" "github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils/junitxml" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/compute" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/config" gcpclients "github.com/GoogleCloudPlatform/osconfig/e2e_tests/gcp_clients" testconfig "github.com/GoogleCloudPlatform/osconfig/e2e_tests/test_config" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/utils" "github.com/kylelemons/godebug/pretty" computeApi "google.golang.org/api/compute/v1" osconfigpb "google.golang.org/genproto/googleapis/cloud/osconfig/v1beta" ) var ( testSuiteName = "GuestPolicies" ) var ( dump = &pretty.Config{IncludeUnexported: true} ) const ( packageInstallFunction = "pkginstall" packageRemovalFunction = "pkgremoval" packageInstallFromNewRepoFunction = "pkgfromnewrepo" packageUpdateFunction = "pkgupdate" packageNoUpdateFunction = "pkgnoupdate" recipeInstallFunction = "recipeinstall" recipeStepsFunction = "recipesteps" metadataPolicyFunction = "metadatapolicy" ) type guestPolicyTestSetup struct { image string guestPolicyID string instanceName string testName string guestPolicy *osconfigpb.GuestPolicy startup *computeApi.MetadataItems mdPolicy *computeApi.MetadataItems machineType string queryPath string assertTimeout time.Duration } func newGuestPolicyTestSetup(image, instanceName, testName, queryPath, machineType string, gp *osconfigpb.GuestPolicy, startup *computeApi.MetadataItems, assertTimeout time.Duration) *guestPolicyTestSetup { return &guestPolicyTestSetup{ image: image, guestPolicyID: instanceName, instanceName: instanceName, guestPolicy: gp, mdPolicy: nil, testName: testName, machineType: machineType, queryPath: queryPath, assertTimeout: assertTimeout, startup: startup, } } // TestSuite is a OSPackage test suite. func TestSuite(ctx context.Context, tswg *sync.WaitGroup, testSuites chan *junitxml.TestSuite, logger *log.Logger, testSuiteRegex, testCaseRegex *regexp.Regexp) { defer tswg.Done() if testSuiteRegex != nil && !testSuiteRegex.MatchString(testSuiteName) { return } testSuite := junitxml.NewTestSuite(testSuiteName) defer testSuite.Finish(testSuites) logger.Printf("Running TestSuite %q", testSuite.Name) testSetup := generateAllTestSetup() var wg sync.WaitGroup tests := make(chan *junitxml.TestCase) for _, setup := range testSetup { wg.Add(1) go packageManagementTestCase(ctx, setup, tests, &wg, logger, testCaseRegex) } go func() { wg.Wait() close(tests) }() for ret := range tests { testSuite.TestCase = append(testSuite.TestCase, ret) } logger.Printf("Finished TestSuite %q", testSuite.Name) } // We only want to create one GuestPolicy at a time to limit QPS. var gpMx sync.Mutex func createGuestPolicy(ctx context.Context, client *osconfigV1beta.Client, req *osconfigpb.CreateGuestPolicyRequest) (*osconfigpb.GuestPolicy, error) { gpMx.Lock() defer gpMx.Unlock() return client.CreateGuestPolicy(ctx, req) } func runTest(ctx context.Context, testCase *junitxml.TestCase, testSetup *guestPolicyTestSetup, logger *log.Logger) { computeClient, err := gcpclients.GetComputeClient() if err != nil { testCase.WriteFailure("Error getting compute client: %v", err) return } var metadataItems []*computeApi.MetadataItems metadataItems = append(metadataItems, testSetup.startup) metadataItems = append(metadataItems, compute.BuildInstanceMetadataItem("enable-osconfig", "true")) metadataItems = append(metadataItems, compute.BuildInstanceMetadataItem("osconfig-disabled-features", "tasks,osinventory")) testProjectConfig := testconfig.GetProject() zone := testProjectConfig.AcquireZone() defer testProjectConfig.ReleaseZone(zone) testCase.Logf("Creating instance %q with image %q", testSetup.instanceName, testSetup.image) inst, err := utils.CreateComputeInstance(metadataItems, computeClient, testSetup.machineType, testSetup.image, testSetup.instanceName, testProjectConfig.TestProjectID, zone, testProjectConfig.ServiceAccountEmail, testProjectConfig.ServiceAccountScopes) if err != nil { testCase.WriteFailure("Error creating instance: %s", utils.GetStatusFromError(err)) return } defer inst.Cleanup() defer inst.RecordSerialOutput(ctx, path.Join(*config.OutDir, testSuiteName), 1) testCase.Logf("Waiting for agent install to complete") if _, err := inst.WaitForGuestAttributes("osconfig_tests/install_done", 5*time.Second, 10*time.Minute); err != nil { testCase.WriteFailure("Error waiting for osconfig agent install: %v", err) return } // Only create the guest policy after the instance has installed the agent. client, err := gcpclients.GetOsConfigClientV1beta() if err != nil { testCase.WriteFailure("Error getting osconfig client: %v", err) return } if testSetup.guestPolicy != nil { req := &osconfigpb.CreateGuestPolicyRequest{ Parent: fmt.Sprintf("projects/%s", testProjectConfig.TestProjectID), GuestPolicyId: testSetup.guestPolicyID, GuestPolicy: testSetup.guestPolicy, } testCase.Logf("Creating GuestPolicy") res, err := createGuestPolicy(ctx, client, req) if err != nil { testCase.WriteFailure("Error running CreateGuestPolicy: %s", utils.GetStatusFromError(err)) return } defer cleanupGuestPolicy(ctx, testCase, res) } if testSetup.mdPolicy != nil { testCase.Logf("Creating Metadata Policy") if err := inst.AddMetadata(testSetup.mdPolicy); err != nil { testCase.WriteFailure("Error running AddMetadata: %s", utils.GetStatusFromError(err)) return } } testCase.Logf("Restarting agent") if err := inst.AddMetadata(compute.BuildInstanceMetadataItem("restart-agent", "true")); err != nil { testCase.WriteFailure("Error running AddMetadata: %s", utils.GetStatusFromError(err)) return } testCase.Logf("Waiting for signal from GuestAttributes") if _, err := inst.WaitForGuestAttributes(testSetup.queryPath, 10*time.Second, testSetup.assertTimeout); err != nil { testCase.WriteFailure("error while asserting: %v", err) return } } func packageManagementTestCase(ctx context.Context, testSetup *guestPolicyTestSetup, tests chan *junitxml.TestCase, wg *sync.WaitGroup, logger *log.Logger, regex *regexp.Regexp) { defer wg.Done() tc, err := getTestCaseFromTestSetUp(testSetup) if err != nil { logger.Fatalf("invalid testcase: %+v", err) return } if tc.FilterTestCase(regex) { tc.Finish(tests) } else { logger.Printf("Running TestCase %q", tc.Name) runTest(ctx, tc, testSetup, logger) tc.Finish(tests) logger.Printf("TestCase %q finished in %fs", tc.Name, tc.Time) } } // factory method to get testcase from the testsetup func getTestCaseFromTestSetUp(testSetup *guestPolicyTestSetup) (*junitxml.TestCase, error) { var tc *junitxml.TestCase switch testSetup.testName { case packageInstallFunction: tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package installation] [%s]", path.Base(testSetup.image))) case packageRemovalFunction: tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package removal] [%s]", path.Base(testSetup.image))) case packageInstallFromNewRepoFunction: tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Add a new package from new repository] [%s]", path.Base(testSetup.image))) case packageUpdateFunction: tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package update] [%s]", path.Base(testSetup.image))) case packageNoUpdateFunction: tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package install doesn't update] [%s]", path.Base(testSetup.image))) case recipeInstallFunction: tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Recipe installation] [%s]", path.Base(testSetup.image))) case recipeStepsFunction: tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Recipe steps] [%s]", path.Base(testSetup.image))) case metadataPolicyFunction: tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Metadata policy] [%s]", path.Base(testSetup.image))) default: return nil, fmt.Errorf("unknown test function name: %s", testSetup.testName) } return tc, nil } func cleanupGuestPolicy(ctx context.Context, testCase *junitxml.TestCase, gp *osconfigpb.GuestPolicy) { client, err := gcpclients.GetOsConfigClientV1beta() if err != nil { testCase.WriteFailure(fmt.Sprintf("Error while deleting guest policy: %s", utils.GetStatusFromError(err))) } if err := client.DeleteGuestPolicy(ctx, &osconfigpb.DeleteGuestPolicyRequest{Name: gp.GetName()}); err != nil { testCase.WriteFailure(fmt.Sprintf("Error calling DeleteGuestPolicy: %s", utils.GetStatusFromError(err))) } } osconfig-20210219.00/e2e_tests/test_suites/guestpolicies/guest_policies_test_data.go000066400000000000000000000537301401404514100305270ustar00rootroot00000000000000// Copyright 2019 Google 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 guestpolicies import ( "fmt" "path" "strings" "time" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/compute" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/config" osconfigserver "github.com/GoogleCloudPlatform/osconfig/e2e_tests/osconfig_server" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/utils" "github.com/golang/protobuf/jsonpb" osconfigpb "google.golang.org/genproto/googleapis/cloud/osconfig/v1beta" ) const ( packageInstalled = "osconfig_tests/pkg_installed" packageNotInstalled = "osconfig_tests/pkg_not_installed" osconfigTestRepo = "osconfig-agent-test-repository" testResourceBucket = "osconfig-agent-end2end-test-resources" yumTestRepoBaseURL = "https://packages.cloud.google.com/yum/repos/osconfig-agent-test-repository" aptTestRepoBaseURL = "http://packages.cloud.google.com/apt" gooTestRepoURL = "https://packages.cloud.google.com/yuck/repos/osconfig-agent-test-repository" aptRaptureGpgKey = "https://packages.cloud.google.com/apt/doc/apt-key.gpg" ) var ( yumRaptureGpgKeys = []string{"https://packages.cloud.google.com/yum/doc/yum-key.gpg", "https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg"} ) func buildPkgInstallTestSetup(name, image, pkgManager, key string) *guestPolicyTestSetup { assertTimeout := 120 * time.Second testName := packageInstallFunction packageName := "ed" machineType := "e2-standard-2" if pkgManager == "googet" { packageName = "cowsay" machineType = "e2-standard-4" } if strings.Contains(image, "rhel-6") || strings.Contains(image, "centos-6") { packageName = "cowsay" } instanceName := fmt.Sprintf("%s-%s-%s-%s", path.Base(name), testName, key, utils.RandString(3)) gp := &osconfigpb.GuestPolicy{ Packages: osconfigserver.BuildPackagePolicy([]string{packageName}, nil, nil), Assignment: &osconfigpb.Assignment{InstanceNamePrefixes: []string{instanceName}}, } ss := getStartupScript(name, pkgManager, packageName) return newGuestPolicyTestSetup(image, instanceName, testName, packageInstalled, machineType, gp, ss, assertTimeout) } func addPackageInstallTest(key string) []*guestPolicyTestSetup { var pkgTestSetup []*guestPolicyTestSetup for name, image := range utils.HeadAptImages { pkgTestSetup = append(pkgTestSetup, buildPkgInstallTestSetup(name, image, "apt", key)) } for name, image := range utils.HeadELImages { pkgTestSetup = append(pkgTestSetup, buildPkgInstallTestSetup(name, image, "yum", key)) } for name, image := range utils.HeadSUSEImages { pkgTestSetup = append(pkgTestSetup, buildPkgInstallTestSetup(name, image, "zypper", key)) } for name, image := range utils.HeadWindowsImages { pkgTestSetup = append(pkgTestSetup, buildPkgInstallTestSetup(name, image, "googet", key)) } return pkgTestSetup } func buildPkgUpdateTestSetup(name, image, pkgManager, key string) *guestPolicyTestSetup { assertTimeout := 240 * time.Second testName := packageUpdateFunction packageName := "ed" machineType := "e2-standard-2" if pkgManager == "googet" { packageName = "cowsay" machineType = "e2-standard-4" } instanceName := fmt.Sprintf("%s-%s-%s-%s", path.Base(name), testName, key, utils.RandString(3)) gp := &osconfigpb.GuestPolicy{ Packages: osconfigserver.BuildPackagePolicy(nil, nil, []string{packageName}), Assignment: &osconfigpb.Assignment{InstanceNamePrefixes: []string{instanceName}}, } ss := getUpdateStartupScript(name, pkgManager) return newGuestPolicyTestSetup(image, instanceName, testName, packageNotInstalled, machineType, gp, ss, assertTimeout) } func addPackageUpdateTest(key string) []*guestPolicyTestSetup { var pkgTestSetup []*guestPolicyTestSetup for name, image := range utils.HeadAptImages { pkgTestSetup = append(pkgTestSetup, buildPkgUpdateTestSetup(name, image, "apt", key)) } for name, image := range utils.HeadELImages { pkgTestSetup = append(pkgTestSetup, buildPkgUpdateTestSetup(name, image, "yum", key)) } for name, image := range utils.HeadSUSEImages { pkgTestSetup = append(pkgTestSetup, buildPkgUpdateTestSetup(name, image, "zypper", key)) } for name, image := range utils.HeadWindowsImages { pkgTestSetup = append(pkgTestSetup, buildPkgUpdateTestSetup(name, image, "googet", key)) } return pkgTestSetup } func buildPkgDoesNotUpdateTestSetup(name, image, pkgManager, key string) *guestPolicyTestSetup { assertTimeout := 240 * time.Second testName := packageNoUpdateFunction packageName := "ed" machineType := "e2-standard-2" if pkgManager == "googet" { packageName = "cowsay" machineType = "e2-standard-4" } instanceName := fmt.Sprintf("%s-%s-%s-%s", path.Base(name), testName, key, utils.RandString(3)) gp := &osconfigpb.GuestPolicy{ Packages: osconfigserver.BuildPackagePolicy([]string{packageName}, nil, nil), Assignment: &osconfigpb.Assignment{InstanceNamePrefixes: []string{instanceName}}, } ss := getUpdateStartupScript(name, pkgManager) return newGuestPolicyTestSetup(image, instanceName, testName, packageInstalled, machineType, gp, ss, assertTimeout) } func addPackageDoesNotUpdateTest(key string) []*guestPolicyTestSetup { var pkgTestSetup []*guestPolicyTestSetup for name, image := range utils.HeadAptImages { pkgTestSetup = append(pkgTestSetup, buildPkgDoesNotUpdateTestSetup(name, image, "apt", key)) } for name, image := range utils.HeadELImages { pkgTestSetup = append(pkgTestSetup, buildPkgDoesNotUpdateTestSetup(name, image, "yum", key)) } for name, image := range utils.HeadSUSEImages { pkgTestSetup = append(pkgTestSetup, buildPkgDoesNotUpdateTestSetup(name, image, "zypper", key)) } for name, image := range utils.HeadWindowsImages { pkgTestSetup = append(pkgTestSetup, buildPkgDoesNotUpdateTestSetup(name, image, "googet", key)) } return pkgTestSetup } func buildPkgRemoveTestSetup(name, image, pkgManager, key string) *guestPolicyTestSetup { assertTimeout := 180 * time.Second testName := packageRemovalFunction packageName := "vim" machineType := "e2-standard-2" if pkgManager == "googet" { packageName = "certgen" machineType = "e2-standard-4" } instanceName := fmt.Sprintf("%s-%s-%s-%s", path.Base(name), testName, key, utils.RandString(3)) gp := &osconfigpb.GuestPolicy{ Packages: osconfigserver.BuildPackagePolicy(nil, []string{packageName}, nil), Assignment: &osconfigpb.Assignment{InstanceNamePrefixes: []string{instanceName}}, } ss := getStartupScript(name, pkgManager, packageName) return newGuestPolicyTestSetup(image, instanceName, testName, packageNotInstalled, machineType, gp, ss, assertTimeout) } func addPackageRemovalTest(key string) []*guestPolicyTestSetup { var pkgTestSetup []*guestPolicyTestSetup for name, image := range utils.HeadAptImages { pkgTestSetup = append(pkgTestSetup, buildPkgRemoveTestSetup(name, image, "apt", key)) } for name, image := range utils.HeadELImages { pkgTestSetup = append(pkgTestSetup, buildPkgRemoveTestSetup(name, image, "yum", key)) } for name, image := range utils.HeadSUSEImages { pkgTestSetup = append(pkgTestSetup, buildPkgRemoveTestSetup(name, image, "zypper", key)) } for name, image := range utils.HeadWindowsImages { pkgTestSetup = append(pkgTestSetup, buildPkgRemoveTestSetup(name, image, "googet", key)) } return pkgTestSetup } func buildPkgInstallFromNewRepoTestSetup(name, image, pkgManager, key string) *guestPolicyTestSetup { assertTimeout := 120 * time.Second packageName := "osconfig-agent-test" testName := packageInstallFromNewRepoFunction machineType := "e2-standard-2" if pkgManager == "googet" { machineType = "e2-standard-4" } instanceName := fmt.Sprintf("%s-%s-%s-%s", path.Base(name), testName, key, utils.RandString(3)) gp := &osconfigpb.GuestPolicy{ // Test that upgrade also installs. Packages: osconfigserver.BuildPackagePolicy(nil, nil, []string{packageName}), Assignment: &osconfigpb.Assignment{InstanceNamePrefixes: []string{instanceName}}, PackageRepositories: []*osconfigpb.PackageRepository{ {Repository: osconfigserver.BuildAptRepository(osconfigpb.AptRepository_DEB, aptTestRepoBaseURL, osconfigTestRepo, aptRaptureGpgKey, []string{"main"})}, {Repository: osconfigserver.BuildYumRepository(osconfigTestRepo, "Google OSConfig Agent Test Repository", yumTestRepoBaseURL, yumRaptureGpgKeys)}, {Repository: osconfigserver.BuildZypperRepository(osconfigTestRepo, "Google OSConfig Agent Test Repository", yumTestRepoBaseURL, yumRaptureGpgKeys)}, {Repository: osconfigserver.BuildGooRepository("Google OSConfig Agent Test Repository", gooTestRepoURL)}, }, } ss := getStartupScript(name, pkgManager, packageName) return newGuestPolicyTestSetup(image, instanceName, testName, packageInstalled, machineType, gp, ss, assertTimeout) } func addPackageInstallFromNewRepoTest(key string) []*guestPolicyTestSetup { var pkgTestSetup []*guestPolicyTestSetup for name, image := range utils.HeadAptImages { pkgTestSetup = append(pkgTestSetup, buildPkgInstallFromNewRepoTestSetup(name, image, "apt", key)) } for name, image := range utils.HeadELImages { pkgTestSetup = append(pkgTestSetup, buildPkgInstallFromNewRepoTestSetup(name, image, "yum", key)) } for name, image := range utils.HeadSUSEImages { pkgTestSetup = append(pkgTestSetup, buildPkgInstallFromNewRepoTestSetup(name, image, "zypper", key)) } for name, image := range utils.HeadWindowsImages { pkgTestSetup = append(pkgTestSetup, buildPkgInstallFromNewRepoTestSetup(name, image, "googet", key)) } return pkgTestSetup } func addRecipeInstallTest(key string) []*guestPolicyTestSetup { var recipeTestSetup []*guestPolicyTestSetup for name, image := range utils.HeadAptImages { recipeTestSetup = append(recipeTestSetup, buildRecipeInstallTestSetup(name, image, "apt", key)) } for name, image := range utils.HeadELImages { recipeTestSetup = append(recipeTestSetup, buildRecipeInstallTestSetup(name, image, "yum", key)) } for name, image := range utils.HeadSUSEImages { recipeTestSetup = append(recipeTestSetup, buildRecipeInstallTestSetup(name, image, "zypper", key)) } for name, image := range utils.HeadWindowsImages { recipeTestSetup = append(recipeTestSetup, buildRecipeInstallTestSetup(name, image, "googet", key)) } // This ensures we only run cos tests on the "head image" tests. if config.AgentRepo() == "" { for name, image := range utils.HeadCOSImages { recipeTestSetup = append(recipeTestSetup, buildRecipeInstallTestSetup(name, image, "cos", key)) } } return recipeTestSetup } func addMetadataPolicyTest(key string) []*guestPolicyTestSetup { var policyTestSetup []*guestPolicyTestSetup for name, image := range utils.HeadAptImages { policyTestSetup = append(policyTestSetup, buildMetadataPolicyTestSetup(name, image, "apt", key)) } for name, image := range utils.HeadELImages { policyTestSetup = append(policyTestSetup, buildMetadataPolicyTestSetup(name, image, "yum", key)) } for name, image := range utils.HeadSUSEImages { policyTestSetup = append(policyTestSetup, buildMetadataPolicyTestSetup(name, image, "zypper", key)) } for name, image := range utils.HeadWindowsImages { policyTestSetup = append(policyTestSetup, buildMetadataPolicyTestSetup(name, image, "googet", key)) } // This ensures we only run cos tests on the "head image" tests. if config.AgentRepo() == "" { for name, image := range utils.HeadCOSImages { policyTestSetup = append(policyTestSetup, buildMetadataPolicyTestSetup(name, image, "cos", key)) } } return policyTestSetup } func buildRecipeInstallTestSetup(name, image, pkgManager, key string) *guestPolicyTestSetup { assertTimeout := 120 * time.Second testName := recipeInstallFunction recipeName := "testrecipe" machineType := "e2-standard-2" if strings.HasPrefix(image, "windows") { machineType = "e2-standard-4" } instanceName := fmt.Sprintf("%s-%s-%s-%s", path.Base(name), testName, key, utils.RandString(3)) gp := &osconfigpb.GuestPolicy{ Assignment: &osconfigpb.Assignment{InstanceNamePrefixes: []string{instanceName}}, Recipes: []*osconfigpb.SoftwareRecipe{ osconfigserver.BuildSoftwareRecipe(recipeName, "", nil, nil), }, } ss := getRecipeInstallStartupScript(name, recipeName, pkgManager) return newGuestPolicyTestSetup(image, instanceName, testName, packageInstalled, machineType, gp, ss, assertTimeout) } func addRecipeStepsTest(key string) []*guestPolicyTestSetup { var recipeTestSetup []*guestPolicyTestSetup for name, image := range utils.HeadAptImages { recipeTestSetup = append(recipeTestSetup, buildRecipeStepsTestSetup(name, image, "apt", key)) } for name, image := range utils.HeadELImages { recipeTestSetup = append(recipeTestSetup, buildRecipeStepsTestSetup(name, image, "yum", key)) } for name, image := range utils.HeadSUSEImages { recipeTestSetup = append(recipeTestSetup, buildRecipeStepsTestSetup(name, image, "zypper", key)) } for name, image := range utils.HeadWindowsImages { recipeTestSetup = append(recipeTestSetup, buildRecipeStepsTestSetup(name, image, "googet", key)) } // This ensures we only run cos tests on the "head image" tests. if config.AgentRepo() == "" { for name, image := range utils.HeadCOSImages { recipeTestSetup = append(recipeTestSetup, buildRecipeStepsTestSetup(name, image, "cos", key)) } } return recipeTestSetup } func buildRecipeStepsTestSetup(name, image, pkgManager, key string) *guestPolicyTestSetup { assertTimeout := 120 * time.Second testName := recipeStepsFunction recipeName := "testrecipe" machineType := "e2-standard-2" if strings.HasPrefix(image, "windows") { machineType = "e2-standard-4" } instanceName := fmt.Sprintf("%s-%s-%s-%s", path.Base(name), testName, key, utils.RandString(3)) artifacts := []*osconfigpb.SoftwareRecipe_Artifact{ { AllowInsecure: true, Id: "copy-test", Artifact: &osconfigpb.SoftwareRecipe_Artifact_Remote_{ Remote: &osconfigpb.SoftwareRecipe_Artifact_Remote{ Uri: "https://example.com", }, }, }, { AllowInsecure: true, Id: "exec-test-sh", Artifact: &osconfigpb.SoftwareRecipe_Artifact_Gcs_{ Gcs: &osconfigpb.SoftwareRecipe_Artifact_Gcs{ Bucket: testResourceBucket, Object: "software_recipes/exec_test.sh", }, }, }, { AllowInsecure: true, Id: "exec-test-cmd", Artifact: &osconfigpb.SoftwareRecipe_Artifact_Gcs_{ Gcs: &osconfigpb.SoftwareRecipe_Artifact_Gcs{ Bucket: testResourceBucket, Object: "software_recipes/exec_test.cmd", }, }, }, { AllowInsecure: true, Id: "tar-test", Artifact: &osconfigpb.SoftwareRecipe_Artifact_Gcs_{ Gcs: &osconfigpb.SoftwareRecipe_Artifact_Gcs{ Bucket: testResourceBucket, Object: "software_recipes/tar_test.tar.gz", }, }, }, { AllowInsecure: true, Id: "zip-test", Artifact: &osconfigpb.SoftwareRecipe_Artifact_Gcs_{ Gcs: &osconfigpb.SoftwareRecipe_Artifact_Gcs{ Bucket: testResourceBucket, Object: "software_recipes/zip_test.zip", }, }, }, { AllowInsecure: true, Id: "dpkg-test", Artifact: &osconfigpb.SoftwareRecipe_Artifact_Gcs_{ Gcs: &osconfigpb.SoftwareRecipe_Artifact_Gcs{ Bucket: testResourceBucket, Object: "software_recipes/ed_1.15-1_amd64.deb", }, }, }, { AllowInsecure: true, Id: "rpm-test", Artifact: &osconfigpb.SoftwareRecipe_Artifact_Gcs_{ Gcs: &osconfigpb.SoftwareRecipe_Artifact_Gcs{ Bucket: testResourceBucket, Object: "software_recipes/ed-1.1-3.3.el6.x86_64.rpm", }, }, }, } pkgTest := &osconfigpb.SoftwareRecipe_Step{} switch pkgManager { case "apt": pkgTest = &osconfigpb.SoftwareRecipe_Step{Step: &osconfigpb.SoftwareRecipe_Step_DpkgInstallation{ DpkgInstallation: &osconfigpb.SoftwareRecipe_Step_InstallDpkg{ArtifactId: "dpkg-test"}, }} case "yum", "zypper": pkgTest = &osconfigpb.SoftwareRecipe_Step{Step: &osconfigpb.SoftwareRecipe_Step_RpmInstallation{ RpmInstallation: &osconfigpb.SoftwareRecipe_Step_InstallRpm{ArtifactId: "rpm-test"}, }} } gp := &osconfigpb.GuestPolicy{ Assignment: &osconfigpb.Assignment{InstanceNamePrefixes: []string{instanceName}}, Recipes: []*osconfigpb.SoftwareRecipe{ osconfigserver.BuildSoftwareRecipe(recipeName, "", artifacts, []*osconfigpb.SoftwareRecipe_Step{ &osconfigpb.SoftwareRecipe_Step{Step: &osconfigpb.SoftwareRecipe_Step_ScriptRun{ ScriptRun: &osconfigpb.SoftwareRecipe_Step_RunScript{ Script: "echo 'hello world' > /tmp/osconfig-SoftwareRecipe_Step_RunScript_SHELL", Interpreter: osconfigpb.SoftwareRecipe_Step_RunScript_SHELL, }, }}, &osconfigpb.SoftwareRecipe_Step{Step: &osconfigpb.SoftwareRecipe_Step_FileCopy{ FileCopy: &osconfigpb.SoftwareRecipe_Step_CopyFile{ArtifactId: "copy-test", Destination: "/tmp/osconfig-copy-test"}, }}, {Step: &osconfigpb.SoftwareRecipe_Step_ArchiveExtraction{ ArchiveExtraction: &osconfigpb.SoftwareRecipe_Step_ExtractArchive{ArtifactId: "tar-test", Destination: "/tmp/tar-test", Type: osconfigpb.SoftwareRecipe_Step_ExtractArchive_TAR_GZIP}, }}, {Step: &osconfigpb.SoftwareRecipe_Step_ArchiveExtraction{ ArchiveExtraction: &osconfigpb.SoftwareRecipe_Step_ExtractArchive{ArtifactId: "zip-test", Destination: "/tmp/zip-test", Type: osconfigpb.SoftwareRecipe_Step_ExtractArchive_ZIP}, }}, }, ), }, } // COS can not create files with the executable bit set on the root partition. if pkgManager != "cos" { gp.Recipes[0].InstallSteps = append(gp.Recipes[0].InstallSteps, &osconfigpb.SoftwareRecipe_Step{Step: &osconfigpb.SoftwareRecipe_Step_ScriptRun{ ScriptRun: &osconfigpb.SoftwareRecipe_Step_RunScript{ Script: "#!/bin/sh\necho 'hello world' > /tmp/osconfig-SoftwareRecipe_Step_RunScript_INTERPRETER_UNSPECIFIED", Interpreter: osconfigpb.SoftwareRecipe_Step_RunScript_INTERPRETER_UNSPECIFIED, }, }}, &osconfigpb.SoftwareRecipe_Step{Step: &osconfigpb.SoftwareRecipe_Step_FileExec{ FileExec: &osconfigpb.SoftwareRecipe_Step_ExecFile{LocationType: &osconfigpb.SoftwareRecipe_Step_ExecFile_ArtifactId{ArtifactId: "exec-test-sh"}}, }}, pkgTest) } if pkgManager == "googet" { gp = &osconfigpb.GuestPolicy{ Assignment: &osconfigpb.Assignment{InstanceNamePrefixes: []string{instanceName}}, Recipes: []*osconfigpb.SoftwareRecipe{ osconfigserver.BuildSoftwareRecipe(recipeName, "", artifacts, []*osconfigpb.SoftwareRecipe_Step{ {Step: &osconfigpb.SoftwareRecipe_Step_ScriptRun{ ScriptRun: &osconfigpb.SoftwareRecipe_Step_RunScript{ Script: "echo 'hello world' > c:\\osconfig-SoftwareRecipe_Step_RunScript_POWERSHELL", Interpreter: osconfigpb.SoftwareRecipe_Step_RunScript_POWERSHELL, }, }}, {Step: &osconfigpb.SoftwareRecipe_Step_ScriptRun{ ScriptRun: &osconfigpb.SoftwareRecipe_Step_RunScript{ Script: "echo 'hello world' > c:\\osconfig-SoftwareRecipe_Step_RunScript_SHELL", Interpreter: osconfigpb.SoftwareRecipe_Step_RunScript_SHELL, }, }}, {Step: &osconfigpb.SoftwareRecipe_Step_FileExec{ FileExec: &osconfigpb.SoftwareRecipe_Step_ExecFile{LocationType: &osconfigpb.SoftwareRecipe_Step_ExecFile_ArtifactId{ArtifactId: "exec-test-cmd"}}, }}, {Step: &osconfigpb.SoftwareRecipe_Step_FileCopy{ FileCopy: &osconfigpb.SoftwareRecipe_Step_CopyFile{ArtifactId: "copy-test", Destination: "c:\\osconfig-copy-test"}, }}, {Step: &osconfigpb.SoftwareRecipe_Step_ArchiveExtraction{ ArchiveExtraction: &osconfigpb.SoftwareRecipe_Step_ExtractArchive{ArtifactId: "tar-test", Destination: "c:\\tar-test", Type: osconfigpb.SoftwareRecipe_Step_ExtractArchive_TAR_GZIP}, }}, {Step: &osconfigpb.SoftwareRecipe_Step_ArchiveExtraction{ ArchiveExtraction: &osconfigpb.SoftwareRecipe_Step_ExtractArchive{ArtifactId: "zip-test", Destination: "c:\\zip-test", Type: osconfigpb.SoftwareRecipe_Step_ExtractArchive_ZIP}, }}, }, ), }, } } ss := getRecipeStepsStartupScript(name, recipeName, pkgManager) return newGuestPolicyTestSetup(image, instanceName, testName, packageInstalled, machineType, gp, ss, assertTimeout) } func buildMetadataPolicyTestSetup(name, image, pkgManager, key string) *guestPolicyTestSetup { assertTimeout := 60 * time.Second testName := metadataPolicyFunction recipeName := "testrecipe" machineType := "e2-standard-2" if strings.HasPrefix(image, "windows") { machineType = "e2-standard-4" } instanceName := fmt.Sprintf("%s-%s-%s-%s", path.Base(name), testName, key, utils.RandString(3)) ss := getRecipeInstallStartupScript(name, recipeName, pkgManager) ts := newGuestPolicyTestSetup(image, instanceName, testName, packageInstalled, machineType, nil, ss, assertTimeout) marshaler := jsonpb.Marshaler{} recipeString, err := marshaler.MarshalToString(osconfigserver.BuildSoftwareRecipe(recipeName, "", nil, nil)) if err != nil { // An error in the test setup means something seriously wrong. panic(err) } rec := fmt.Sprintf(`{"softwareRecipes": [%s]}`, recipeString) ts.mdPolicy = compute.BuildInstanceMetadataItem("gce-software-declaration", rec) return ts } func generateAllTestSetup() []*guestPolicyTestSetup { key := utils.RandString(3) pkgTestSetup := []*guestPolicyTestSetup{} pkgTestSetup = append(pkgTestSetup, addPackageInstallTest(key)...) pkgTestSetup = append(pkgTestSetup, addPackageRemovalTest(key)...) pkgTestSetup = append(pkgTestSetup, addPackageInstallFromNewRepoTest(key)...) pkgTestSetup = append(pkgTestSetup, addPackageUpdateTest(key)...) pkgTestSetup = append(pkgTestSetup, addPackageDoesNotUpdateTest(key)...) pkgTestSetup = append(pkgTestSetup, addRecipeInstallTest(key)...) pkgTestSetup = append(pkgTestSetup, addRecipeStepsTest(key)...) pkgTestSetup = append(pkgTestSetup, addMetadataPolicyTest(key)...) return pkgTestSetup } osconfig-20210219.00/e2e_tests/test_suites/guestpolicies/guest_policies_utils.go000066400000000000000000000324111401404514100277100ustar00rootroot00000000000000// Copyright 2019 Google 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 guestpolicies import ( "fmt" "path" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/utils" computeApi "google.golang.org/api/compute/v1" ) var ( yumStartupScripts = map[string]string{ "rhel-6": utils.InstallOSConfigEL6(), "rhel-7": utils.InstallOSConfigEL7(), "rhel-8": utils.InstallOSConfigEL8(), "centos-6": utils.InstallOSConfigEL6(), "centos-7": utils.InstallOSConfigEL7(), "centos-8": utils.InstallOSConfigEL8(), } ) var waitForRestartLinux = ` echo 'Waiting for signal to restart agent' while [[ -z $restarted ]]; do sleep 1 restart=$(curl -f "http://metadata.google.internal/computeMetadata/v1/instance/attributes/restart-agent" -H "Metadata-Flavor: Google") if [[ -n $restart ]]; then systemctl restart google-osconfig-agent restart -q -n google-osconfig-agent # required for EL6 restarted=true sleep 30 fi done ` var waitForRestartWin = ` echo 'Waiting for signal to restart agent' while (! $restarted) { sleep 1 $restart = Invoke-WebRequest -UseBasicParsing http://metadata.google.internal/computeMetadata/v1/instance/attributes/restart-agent -Headers @{"Metadata-Flavor"="Google"} if ($restart) { Restart-Service google_osconfig_agent $restarted = $true sleep 30 } } ` func getStartupScript(image, pkgManager, packageName string) *computeApi.MetadataItems { var ss, key string switch pkgManager { case "apt": ss = ` apt-get -y remove %[3]s %[1]s %[2]s while true; do isinstalled=$(/usr/bin/dpkg-query -s %s) if [[ $isinstalled =~ "Status: install ok installed" ]]; then uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%s else uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%s fi curl -X PUT --data "1" $uri -H "Metadata-Flavor: Google" sleep 5 done` ss = fmt.Sprintf(ss, utils.InstallOSConfigDeb(), waitForRestartLinux, packageName, packageInstalled, packageNotInstalled) key = "startup-script" case "yum": ss = ` while ! yum -y remove %[3]s; do if [[ n -gt 5 ]]; then exit 1 fi n=$[$n+1] sleep 10 done %[1]s %[2]s while true; do isinstalled=$(/usr/bin/rpmquery -a %[3]s) if [[ $isinstalled =~ ^%[3]s-* ]]; then uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%s else uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%s fi curl -X PUT --data "1" $uri -H "Metadata-Flavor: Google" sleep 5 done` ss = fmt.Sprintf(ss, yumStartupScripts[path.Base(image)], waitForRestartLinux, packageName, packageInstalled, packageNotInstalled) key = "startup-script" case "googet": ss = ` googet addrepo test https://packages.cloud.google.com/yuck/repos/osconfig-agent-test-repository %s %s while(1) { $installed_packages = googet installed if ($installed_packages -like "*%s*") { $uri = 'http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%s' } else { $uri = 'http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%s' } Invoke-RestMethod -Method PUT -Uri $uri -Headers @{"Metadata-Flavor" = "Google"} -Body 1 sleep 5 }` ss = fmt.Sprintf(ss, utils.InstallOSConfigGooGet(), waitForRestartWin, packageName, packageInstalled, packageNotInstalled) key = "windows-startup-script-ps1" case "zypper": ss = ` zypper -n remove %[3]s %[1]s %[2]s while true; do isinstalled=$(/usr/bin/rpmquery -a %[3]s) if [[ $isinstalled =~ ^%[3]s-* ]]; then uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%s else uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%s fi curl -X PUT --data "1" $uri -H "Metadata-Flavor: Google" sleep 5 done` ss = fmt.Sprintf(ss, utils.InstallOSConfigSUSE(), waitForRestartLinux, packageName, packageInstalled, packageNotInstalled) key = "startup-script" default: fmt.Printf("Invalid package manager: %s", pkgManager) } return &computeApi.MetadataItems{ Key: key, Value: &ss, } } func getUpdateStartupScript(image, pkgManager string) *computeApi.MetadataItems { var ss, key string switch pkgManager { case "apt": ss = ` echo 'Adding test repo' echo 'deb http://packages.cloud.google.com/apt osconfig-agent-test-repository main' >> /etc/apt/sources.list curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do sleep 5 done apt-get update apt-get -y remove ed || exit 1 apt-get -y install ed=1.9-2 || exit 1 %[1]s %[2]s while true; do isinstalled=$(/usr/bin/dpkg-query -f '${Version}' -W ed) if [[ $isinstalled == "1.9-2" ]]; then uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%[3]s else uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/%[4]s fi curl -X PUT --data "1" $uri -H "Metadata-Flavor: Google" sleep 5; done` ss = fmt.Sprintf(ss, utils.InstallOSConfigDeb(), waitForRestartLinux, packageInstalled, packageNotInstalled) key = "startup-script" case "yum": ss = ` echo 'Adding test repo' cat > /etc/yum.repos.d/google-osconfig-agent.repo < /etc/zypp/repos.d/google-osconfig-agent.repo < 0 { testCase.WriteFailure("PatchJob finished with status InstancesSucceededRebootRequired.") return } // If shouldReboot is false that instance should report a pending reboot. if !shouldReboot && pj.GetInstanceDetailsSummary().GetSucceededRebootRequiredInstanceCount() == 0 { testCase.WriteFailure("PatchJob should have finished with status InstancesSucceededRebootRequired.") return } testCase.Logf("Checking reboot count") attr, err := inst.GetGuestAttributes("osconfig_tests/boot_count") if err != nil { testCase.WriteFailure("Error retrieving boot count: %v", err) return } if len(attr) == 0 { testCase.WriteFailure("Error retrieving boot count: osconfig_tests/boot_count attribute empty") return } num, err := strconv.Atoi(attr[0].Value) if err != nil { testCase.WriteFailure("Error parsing boot count: %v", err) return } if shouldReboot && num < 2 { testCase.WriteFailure("Instance should have booted at least 2 times, boot num: %d.", num) return } if !shouldReboot && num > 1 { testCase.WriteFailure("Instance should not have booted more that 1 time, boot num: %d.", num) return } } func isPatchJobFailureState(state osconfigpb.PatchJob_State) bool { return state == osconfigpb.PatchJob_COMPLETED_WITH_ERRORS || state == osconfigpb.PatchJob_TIMED_OUT || state == osconfigpb.PatchJob_CANCELED } func runTestCase(tc *junitxml.TestCase, f func(), tests chan *junitxml.TestCase, wg *sync.WaitGroup, logger *log.Logger, regex *regexp.Regexp) { defer wg.Done() if tc.FilterTestCase(regex) { tc.Finish(tests) } else { logger.Printf("Running TestCase %q", tc.Name) f() tc.Finish(tests) logger.Printf("TestCase %q finished in %fs", tc.Name, tc.Time) } } func patchConfigWithPrePostSteps() *osconfigpb.PatchConfig { linuxPreStepConfig := &osconfigpb.ExecStepConfig{Executable: &osconfigpb.ExecStepConfig_LocalPath{LocalPath: "./linux_local_pre_patch_script.sh"}, Interpreter: osconfigpb.ExecStepConfig_SHELL} windowsPreStepConfig := &osconfigpb.ExecStepConfig{Executable: &osconfigpb.ExecStepConfig_GcsObject{GcsObject: &osconfigpb.GcsObject{Bucket: "osconfig-agent-end2end-test-resources", Object: "OSPatch/windows_gcs_pre_patch_script.ps1", GenerationNumber: 1571249543230832}}, Interpreter: osconfigpb.ExecStepConfig_POWERSHELL} linuxPostStepConfig := &osconfigpb.ExecStepConfig{Executable: &osconfigpb.ExecStepConfig_GcsObject{GcsObject: &osconfigpb.GcsObject{Bucket: "osconfig-agent-end2end-test-resources", Object: "OSPatch/linux_gcs_post_patch_script", GenerationNumber: 1570567792146617}}} windowsPostStepConfig := &osconfigpb.ExecStepConfig{Executable: &osconfigpb.ExecStepConfig_LocalPath{LocalPath: "C:\\Windows\\System32\\windows_local_post_patch_script.ps1"}, Interpreter: osconfigpb.ExecStepConfig_POWERSHELL} preStep := &osconfigpb.ExecStep{LinuxExecStepConfig: linuxPreStepConfig, WindowsExecStepConfig: windowsPreStepConfig} postStep := &osconfigpb.ExecStep{LinuxExecStepConfig: linuxPostStepConfig, WindowsExecStepConfig: windowsPostStepConfig} return &osconfigpb.PatchConfig{PreStep: preStep, PostStep: postStep} } func validatePrePostStepSuccess(inst *compute.Instance, testCase *junitxml.TestCase) { if _, err := inst.WaitForGuestAttributes("osconfig_tests/pre_step_ran", 5*time.Second, 1*time.Minute); err != nil { testCase.WriteFailure("error while asserting: %v", err) return } if _, err := inst.WaitForGuestAttributes("osconfig_tests/post_step_ran", 5*time.Second, 1*time.Minute); err != nil { testCase.WriteFailure("error while asserting: %v", err) return } } osconfig-20210219.00/e2e_tests/test_suites/patch/test_setup.go000066400000000000000000000155021401404514100240730ustar00rootroot00000000000000// Copyright 2019 Google 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 patch import ( "time" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/compute" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/utils" computeApi "google.golang.org/api/compute/v1" ) type patchTestSetup struct { testName string image string metadata []*computeApi.MetadataItems assertTimeout time.Duration machineType string } var ( windowsRecordBoot = ` while ($true) { $uri = 'http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/osconfig_tests/boot_count' $old = Invoke-RestMethod -Method GET -Uri $uri -Headers @{"Metadata-Flavor" = "Google"} $new = $old+1 try { Invoke-RestMethod -Method PUT -Uri $uri -Headers @{"Metadata-Flavor" = "Google"} -Body $new -ErrorAction Stop } catch { Write-Output $_.Exception.Message Start-Sleep 1 continue } break } ` windowsSetWsus = ` $wu_server = '192.168.0.2' $windows_update_path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' $windows_update_au_path = "$windows_update_path\AU" if (Test-Connection $wu_server -Count 1 -ErrorAction SilentlyContinue) { if (-not (Test-Path $windows_update_path -ErrorAction SilentlyContinue)) { New-Item -Path $windows_update_path -Value "" New-Item -Path $windows_update_au_path -Value "" } Set-ItemProperty -Path $windows_update_path -Name WUServer -Value "http://${wu_server}:8530" Set-ItemProperty -Path $windows_update_path -Name WUStatusServer -Value "http://${wu_server}:8530" Set-ItemProperty -Path $windows_update_au_path -Name UseWUServer -Value 1 } ` windowsLocalPostPatchScript = ` $uri = 'http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/osconfig_tests/post_step_ran' New-Item -Path . -Name "windows_local_post_patch_script.ps1" -ItemType "file" -Value "Invoke-RestMethod -Method PUT -Uri $uri -Headers @{'Metadata-Flavor' = 'Google'} -Body 1" ` linuxRecordBoot = ` uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/osconfig_tests/boot_count old=$(curl $uri -H "Metadata-Flavor: Google" -f) new=$(($old + 1)) curl -X PUT --data "${new}" $uri -H "Metadata-Flavor: Google" ` linuxLocalPrePatchScript = ` echo 'curl -X PUT --data "1" http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/osconfig_tests/pre_step_ran -H "Metadata-Flavor: Google"' >> ./linux_local_pre_patch_script.sh chmod +x ./linux_local_pre_patch_script.sh ` enablePatch = compute.BuildInstanceMetadataItem("os-config-enabled-prerelease-features", "ospatch") windowsSetup = &patchTestSetup{ assertTimeout: 60 * time.Minute, metadata: []*computeApi.MetadataItems{ compute.BuildInstanceMetadataItem("sysprep-specialize-script-ps1", windowsSetWsus), compute.BuildInstanceMetadataItem("windows-startup-script-ps1", windowsRecordBoot+utils.InstallOSConfigGooGet()+windowsLocalPostPatchScript), enablePatch, }, machineType: "e2-standard-4", } aptSetup = &patchTestSetup{ assertTimeout: 10 * time.Minute, metadata: []*computeApi.MetadataItems{ compute.BuildInstanceMetadataItem("startup-script", linuxRecordBoot+utils.InstallOSConfigDeb()+linuxLocalPrePatchScript), enablePatch, }, machineType: "e2-standard-2", } el6Setup = &patchTestSetup{ assertTimeout: 15 * time.Minute, metadata: []*computeApi.MetadataItems{ compute.BuildInstanceMetadataItem("startup-script", linuxRecordBoot+utils.InstallOSConfigEL6()+linuxLocalPrePatchScript), enablePatch, }, machineType: "e2-standard-2", } el7Setup = &patchTestSetup{ assertTimeout: 15 * time.Minute, metadata: []*computeApi.MetadataItems{ compute.BuildInstanceMetadataItem("startup-script", linuxRecordBoot+utils.InstallOSConfigEL7()+linuxLocalPrePatchScript), enablePatch, }, machineType: "e2-standard-2", } el8Setup = &patchTestSetup{ assertTimeout: 15 * time.Minute, metadata: []*computeApi.MetadataItems{ compute.BuildInstanceMetadataItem("startup-script", linuxRecordBoot+utils.InstallOSConfigEL8()+linuxLocalPrePatchScript), enablePatch, }, machineType: "e2-standard-2", } suseSetup = &patchTestSetup{ assertTimeout: 15 * time.Minute, metadata: []*computeApi.MetadataItems{ compute.BuildInstanceMetadataItem("startup-script", linuxRecordBoot+utils.InstallOSConfigSUSE()+linuxLocalPrePatchScript), enablePatch, }, machineType: "e2-standard-2", } ) func imageTestSetup(mapping map[*patchTestSetup]map[string]string) (setup []*patchTestSetup) { for s, m := range mapping { for name, image := range m { new := patchTestSetup(*s) new.testName = name new.image = image setup = append(setup, &new) } } return } func headImageTestSetup() []*patchTestSetup { // This maps a specific patchTestSetup to test setup names and associated images. mapping := map[*patchTestSetup]map[string]string{ windowsSetup: utils.HeadWindowsImages, el6Setup: utils.HeadEL6Images, el7Setup: utils.HeadEL7Images, el8Setup: utils.HeadEL8Images, aptSetup: utils.HeadAptImages, suseSetup: utils.HeadSUSEImages, } return imageTestSetup(mapping) } func oldImageTestSetup() []*patchTestSetup { // This maps a specific patchTestSetup to test setup names and associated images. mapping := map[*patchTestSetup]map[string]string{ windowsSetup: utils.OldWindowsImages, el6Setup: utils.OldEL6Images, el7Setup: utils.OldEL7Images, el8Setup: utils.OldEL8Images, aptSetup: utils.OldAptImages, suseSetup: utils.OldSUSEImages, } return imageTestSetup(mapping) } func aptHeadImageTestSetup() []*patchTestSetup { // This maps a specific patchTestSetup to test setup names and associated images. mapping := map[*patchTestSetup]map[string]string{ aptSetup: utils.HeadAptImages, } return imageTestSetup(mapping) } func yumHeadImageTestSetup() []*patchTestSetup { // This maps a specific patchTestSetup to test setup names and associated images. mapping := map[*patchTestSetup]map[string]string{ el6Setup: utils.HeadEL6Images, el7Setup: utils.HeadEL7Images, el8Setup: utils.HeadEL8Images, } return imageTestSetup(mapping) } func suseHeadImageTestSetup() []*patchTestSetup { // This maps a specific patchTestSetup to test setup names and associated images. mapping := map[*patchTestSetup]map[string]string{ suseSetup: utils.HeadSUSEImages, } return imageTestSetup(mapping) } osconfig-20210219.00/e2e_tests/utils/000077500000000000000000000000001401404514100170305ustar00rootroot00000000000000osconfig-20210219.00/e2e_tests/utils/utils.go000066400000000000000000000343151401404514100205250ustar00rootroot00000000000000// Copyright 2019 Google 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 utils contains helper utils for osconfig_tests. package utils import ( "fmt" "math/rand" "time" daisyCompute "github.com/GoogleCloudPlatform/compute-image-tools/daisy/compute" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/compute" "github.com/GoogleCloudPlatform/osconfig/e2e_tests/config" api "google.golang.org/api/compute/v1" "google.golang.org/grpc/status" ) var ( yumInstallAgent = ` sleep 10 systemctl stop google-osconfig-agent stop -q -n google-osconfig-agent # required for EL6 while ! yum install -y google-osconfig-agent; do if [[ n -gt 3 ]]; then exit 1 fi n=$[$n+1] sleep 5 done systemctl start google-osconfig-agent start -q -n google-osconfig-agent # required for EL6` + CurlPost zypperInstallAgent = ` sleep 10 systemctl stop google-osconfig-agent while ! zypper -n -i --no-gpg-checks install google-osconfig-agent; do if [[ n -gt 2 ]]; then # Zypper repos are flaky, we retry 3 times then just continue, the agent may be installed fine. zypper --no-refresh -n -i --no-gpg-checks install google-osconfig-agent break fi n=$[$n+1] sleep 5 done systemctl start google-osconfig-agent` + CurlPost // CurlPost indicates agent is installed. CurlPost = ` uri=http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/osconfig_tests/install_done curl -X PUT --data "1" $uri -H "Metadata-Flavor: Google" ` windowsPost = ` Start-Sleep 10 $uri = 'http://metadata.google.internal/computeMetadata/v1/instance/guest-attributes/osconfig_tests/install_done' Invoke-RestMethod -Method PUT -Uri $uri -Headers @{"Metadata-Flavor" = "Google"} -Body 1 ` yumRepoSetup = ` cat > /etc/yum.repos.d/google-osconfig-agent.repo < /etc/zypp/repos.d/google-osconfig-agent.repo <> /etc/apt/sources.list curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - apt-get update apt-get install -y google-osconfig-agent systemctl start google-osconfig-agent`+CurlPost, config.AgentRepo()) } // InstallOSConfigGooGet installs the osconfig agent on Windows systems. func InstallOSConfigGooGet() string { if config.AgentRepo() == "" { return windowsPost } if config.AgentRepo() == "stable" { return `c:\programdata\googet\googet.exe -noconfirm remove google-osconfig-agent c:\programdata\googet\googet.exe -noconfirm install google-osconfig-agent` + windowsPost } return fmt.Sprintf(`c:\programdata\googet\googet.exe -noconfirm remove google-osconfig-agent c:\programdata\googet\googet.exe -noconfirm install -sources https://packages.cloud.google.com/yuck/repos/google-osconfig-agent-%s google-osconfig-agent `+windowsPost, config.AgentRepo()) } // InstallOSConfigSUSE installs the osconfig agent on suse systems. func InstallOSConfigSUSE() string { if config.AgentRepo() == "" { return "" } if config.AgentRepo() == "staging" || config.AgentRepo() == "stable" { return fmt.Sprintf(zypperRepoSetup+zypperInstallAgent, "el8", config.AgentRepo(), 1) } return fmt.Sprintf(zypperRepoSetup+zypperInstallAgent, "el8", config.AgentRepo(), 0) } // InstallOSConfigEL8 installs the osconfig agent on el8 based systems. func InstallOSConfigEL8() string { if config.AgentRepo() == "" { return CurlPost } if config.AgentRepo() == "stable" { return yumInstallAgent } if config.AgentRepo() == "staging" { return fmt.Sprintf(yumRepoSetup+yumInstallAgent, "el8", config.AgentRepo(), 1) } return fmt.Sprintf(yumRepoSetup+yumInstallAgent, "el8", config.AgentRepo(), 0) } // InstallOSConfigEL7 installs the osconfig agent on el7 based systems. func InstallOSConfigEL7() string { if config.AgentRepo() == "" { return CurlPost } if config.AgentRepo() == "stable" { return yumInstallAgent } if config.AgentRepo() == "staging" { return fmt.Sprintf(yumRepoSetup+yumInstallAgent, "el7", config.AgentRepo(), 1) } return fmt.Sprintf(yumRepoSetup+yumInstallAgent, "el7", config.AgentRepo(), 0) } // InstallOSConfigEL6 installs the osconfig agent on el6 based systems. func InstallOSConfigEL6() string { if config.AgentRepo() == "" { return CurlPost } if config.AgentRepo() == "stable" { return yumInstallAgent } if config.AgentRepo() == "staging" { return fmt.Sprintf(yumRepoSetup+yumInstallAgent, "el6", config.AgentRepo(), 1) } return fmt.Sprintf(yumRepoSetup+yumInstallAgent, "el6", config.AgentRepo(), 0) } // HeadAptImages is a map of names to image paths for public image families that use APT. var HeadAptImages = map[string]string{ // Debian images. "debian-cloud/debian-9": "projects/debian-cloud/global/images/family/debian-9", "debian-cloud/debian-10": "projects/debian-cloud/global/images/family/debian-10", // Ubuntu images. "ubuntu-os-cloud/ubuntu-1604-lts": "projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts", "ubuntu-os-cloud/ubuntu-1804-lts": "projects/ubuntu-os-cloud/global/images/family/ubuntu-1804-lts", "ubuntu-os-cloud/ubuntu-2004-lts": "projects/ubuntu-os-cloud/global/images/family/ubuntu-2004-lts", } // OldAptImages is a map of names to image paths for old images that use APT. var OldAptImages = map[string]string{ // Debian images. "old/debian-9": "projects/debian-cloud/global/images/debian-9-stretch-v20191014", "old/debian-10": "projects/debian-cloud/global/images/debian-10-buster-v20191014", // Ubuntu images. "old/ubuntu-1604-lts": "projects/ubuntu-os-cloud/global/images/ubuntu-1604-xenial-v20191005", "old/ubuntu-1804-lts": "projects/ubuntu-os-cloud/global/images/ubuntu-1804-bionic-v20191002", "old/ubuntu-2004-lts": "projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20200506", } // HeadSUSEImages is a map of names to image paths for public SUSE images. var HeadSUSEImages = map[string]string{ "suse-cloud/sles-12": "projects/suse-cloud/global/images/family/sles-12", "suse-cloud/sles-15": "projects/suse-cloud/global/images/family/sles-15", "opensuse-cloud/opensuse-leap": "projects/opensuse-cloud/global/images/family/opensuse-leap", } // OldSUSEImages is a map of names to image paths for old SUSE images. var OldSUSEImages = map[string]string{ "old/sles-12": "projects/compute-image-tools-test/global/images/sles-12-sp5-v20191209", "old/sles-15": "projects/compute-image-tools-test/global/images/sles-15-sp1-v20190625", "old/opensuse-leap": "projects/opensuse-cloud/global/images/opensuse-leap-15-1-v20190618", } // HeadEL6Images is a map of names to image paths for public EL6 images, we use the last // published image here as EL6 is EOL. // TODO: Either remove support for el6 or move this to a deprected test suite. var HeadEL6Images = map[string]string{ "rhel-cloud/rhel-6": "projects/rhel-cloud/global/images/rhel-6-v20201112", } // OldEL6Images is a map of names to image paths for old EL6 images. var OldEL6Images = map[string]string{ "old/rhel-6": "projects/rhel-cloud/global/images/rhel-6-v20191014", } // HeadEL7Images is a map of names to image paths for public EL7 image families. var HeadEL7Images = map[string]string{ "centos-cloud/centos-7": "projects/centos-cloud/global/images/family/centos-7", "rhel-cloud/rhel-7": "projects/rhel-cloud/global/images/family/rhel-7", } // OldEL7Images is a map of names to image paths for old EL7 images. var OldEL7Images = map[string]string{ "old/centos-7": "projects/centos-cloud/global/images/centos-7-v20191014", "old/rhel-7": "projects/rhel-cloud/global/images/rhel-7-v20191014", } // HeadEL8Images is a map of names to image paths for public EL8 image families. var HeadEL8Images = map[string]string{ "centos-cloud/centos-8": "projects/centos-cloud/global/images/family/centos-8", "rhel-cloud/rhel-8": "projects/rhel-cloud/global/images/family/rhel-8", } // OldEL8Images is a map of names to image paths for old EL8 images. var OldEL8Images = map[string]string{ "old/centos-8": "projects/centos-cloud/global/images/centos-7-v20191014", "old/rhel-8": "projects/rhel-cloud/global/images/rhel-7-v20191014", } // HeadELImages is a map of names to image paths for public EL image families. var HeadELImages = func() (newMap map[string]string) { newMap = make(map[string]string) for k, v := range HeadEL6Images { newMap[k] = v } for k, v := range HeadEL7Images { newMap[k] = v } for k, v := range HeadEL8Images { newMap[k] = v } return }() // HeadWindowsImages is a map of names to image paths for public Windows image families. var HeadWindowsImages = map[string]string{ "windows-cloud/windows-2012-r2": "projects/windows-cloud/global/images/family/windows-2012-r2", "windows-cloud/windows-2012-r2-core": "projects/windows-cloud/global/images/family/windows-2012-r2-core", "windows-cloud/windows-2016": "projects/windows-cloud/global/images/family/windows-2016", "windows-cloud/windows-2016-core": "projects/windows-cloud/global/images/family/windows-2016-core", "windows-cloud/windows-2019": "projects/windows-cloud/global/images/family/windows-2019", "windows-cloud/windows-2019-core": "projects/windows-cloud/global/images/family/windows-2019-core", "windows-cloud/windows-1909-core": "projects/windows-cloud/global/images/family/windows-1909-core", "windows-cloud/windows-2004-core": "projects/windows-cloud/global/images/family/windows-2004-core", } // OldWindowsImages is a map of names to image paths for old Windows images. var OldWindowsImages = map[string]string{ "old/windows-2012-r2": "projects/windows-cloud/global/images/windows-server-2012-r2-dc-v20191008", "old/windows-2012-r2-core": "projects/windows-cloud/global/images/windows-server-2012-r2-dc-core-v20191008", "old/windows-2016": "projects/windows-cloud/global/images/windows-server-2016-dc-v20191008", "old/windows-2016-core": "projects/windows-cloud/global/images/windows-server-2016-dc-core-v20191008", "old/windows-2019": "projects/windows-cloud/global/images/windows-server-2019-dc-v20191008", "old/windows-2019-core": "projects/windows-cloud/global/images/windows-server-2019-dc-core-v20191008", } // HeadCOSImages is a map of names to image paths for public COS image families. var HeadCOSImages = map[string]string{ "cos-cloud/cos-stable": "projects/cos-cloud/global/images/family/cos-stable", "cos-cloud/cos-beta": "projects/cos-cloud/global/images/family/cos-beta", "cos-cloud/cos-dev": "projects/cos-cloud/global/images/family/cos-dev", } // RandString generates a random string of n length. func RandString(n int) string { gen := rand.New(rand.NewSource(time.Now().UnixNano())) letters := "bdghjlmnpqrstvwxyz0123456789" b := make([]byte, n) for i := range b { b[i] = letters[gen.Int63()%int64(len(letters))] } return string(b) } // GetStatusFromError return a string that contains all information // about the error that is created from a status func GetStatusFromError(err error) string { if s, ok := status.FromError(err); ok { return fmt.Sprintf("code: %q, message: %q, details: %q", s.Code(), s.Message(), s.Details()) } return fmt.Sprintf("%v", err) } // This pool is just used for CreateComputeInstance so that we limit our calls to the API during the heavy create process. var pool = make(chan struct{}, 10) // CreateComputeInstance is an utility function to create gce instance func CreateComputeInstance(metadataitems []*api.MetadataItems, client daisyCompute.Client, machineType, image, name, projectID, zone, serviceAccountEmail string, serviceAccountScopes []string) (*compute.Instance, error) { pool <- struct{}{} defer func() { <-pool }() var items []*api.MetadataItems // enable debug logging and guest-attributes for all test instances items = append(items, compute.BuildInstanceMetadataItem("enable-os-config-debug", "true")) items = append(items, compute.BuildInstanceMetadataItem("enable-guest-attributes", "true")) if config.AgentSvcEndpoint() != "" { items = append(items, compute.BuildInstanceMetadataItem("os-config-endpoint", config.AgentSvcEndpoint())) } for _, item := range metadataitems { items = append(items, item) } i := &api.Instance{ Name: name, MachineType: fmt.Sprintf("projects/%s/zones/%s/machineTypes/%s", projectID, zone, machineType), NetworkInterfaces: []*api.NetworkInterface{ { Subnetwork: fmt.Sprintf("projects/%s/regions/%s/subnetworks/default", projectID, zone[:len(zone)-2]), AccessConfigs: []*api.AccessConfig{ { Type: "ONE_TO_ONE_NAT", }, }, }, }, Metadata: &api.Metadata{ Items: items, }, Disks: []*api.AttachedDisk{ { AutoDelete: true, Boot: true, InitializeParams: &api.AttachedDiskInitializeParams{ SourceImage: image, DiskType: fmt.Sprintf("projects/%s/zones/%s/diskTypes/pd-ssd", projectID, zone), }, }, }, ServiceAccounts: []*api.ServiceAccount{ { Email: serviceAccountEmail, Scopes: serviceAccountScopes, }, }, Labels: map[string]string{"name": name}, } inst, err := compute.CreateInstance(client, projectID, zone, i) if err != nil { return nil, err } return inst, nil } osconfig-20210219.00/external/000077500000000000000000000000001401404514100156155ustar00rootroot00000000000000osconfig-20210219.00/external/fetcher.go000066400000000000000000000027071401404514100175720ustar00rootroot00000000000000// Copyright 2018 Google 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 external is responsible for all the external interactions package external import ( "context" "fmt" "io" "net/http" "cloud.google.com/go/storage" ) // FetchGCSObject fetches data from GCS bucket func FetchGCSObject(ctx context.Context, client *storage.Client, bucket, object string, generation int64) (io.ReadCloser, error) { oh := client.Bucket(bucket).Object(object) if generation != 0 { oh = oh.Generation(generation) } return oh.NewReader(ctx) } // FetchRemoteObjectHTTP fetches data from remote location func FetchRemoteObjectHTTP(client *http.Client, url string) (io.ReadCloser, error) { resp, err := client.Get(url) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("got http status %d when attempting to download artifact", resp.StatusCode) } return resp.Body, nil } osconfig-20210219.00/go.mod000066400000000000000000000021231401404514100150770ustar00rootroot00000000000000module github.com/GoogleCloudPlatform/osconfig go 1.13 require ( cloud.google.com/go v0.77.0 cloud.google.com/go/logging v1.1.2 // indirect cloud.google.com/go/storage v1.12.0 cos.googlesource.com/cos/tools.git v0.0.0-20201007223835-69bef9e73b80 github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20200113214433-6cbb518174d4 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d github.com/go-ole/go-ole v1.2.4 github.com/golang/mock v1.4.4 github.com/google/go-cmp v0.5.4 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect github.com/sirupsen/logrus v1.7.0 // indirect github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 github.com/ulikunitz/xz v0.5.8 go.chromium.org/luci v0.0.0-20201204084249-3e81ee3e83fe // indirect golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c google.golang.org/api v0.40.0 google.golang.org/genproto v0.0.0-20210212180131-e7f2df4ecc2d google.golang.org/grpc v1.35.0 google.golang.org/protobuf v1.25.0 ) osconfig-20210219.00/go.sum000066400000000000000000001570241401404514100151370ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.47.0/go.mod h1:5p3Ky/7f3N10VBkhuR5LFtddroTiMyjZV/Kj5qOQFxU= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= cloud.google.com/go v0.71.0/go.mod h1:qZfY4Y7AEIQwG/fQYD3xrxLNkQZ0Xzf3HGeqCkA6LVM= cloud.google.com/go v0.72.0 h1:eWRCuwubtDrCJG0oSUMgnsbD4CmPFQF2ei4OFbXvwww= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.77.0 h1:qA5V5+uQf6Mgr+tmFI8UT3D/ELyhIYkPwNGao/3Y+sQ= cloud.google.com/go v0.77.0/go.mod h1:R8fYSLIilC247Iu8WS2OGHw1E/Ufn7Pd7HiDjTqiURs= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.1.0/go.mod h1:g4RsfUkOvV3Vi5yRujQETpqwCN0F+faPZ2/ykNYfBJc= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/logging v1.0.0/go.mod h1:V1cc3ogwobYzQq5f2R7DS/GvRIrI4FKj01Gs5glwAls= cloud.google.com/go/logging v1.1.2 h1:KNALX0NZn8UJhqKnqoHxhMqyoZfBZoh5wF7CQJZ5XrU= cloud.google.com/go/logging v1.1.2/go.mod h1:KrljuAHIw631j9+QXsnq9vDwsrwmdxfGpivMR68M7DY= cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.1.1/go.mod h1:nbQkUX8zrWh07WKekXr/Phd0q/ERj4IOJnkE+v56Qys= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4= cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= cos.googlesource.com/cos/tools.git v0.0.0-20201007223835-69bef9e73b80 h1:mgjd6oHmTodFOwSQTaMhmyFEKjWwUEzhLHS0wcdKNTY= cos.googlesource.com/cos/tools.git v0.0.0-20201007223835-69bef9e73b80/go.mod h1:Pfpj07EYThBloAdKk2PFZSwilXsARB2V4tSF1gisIh4= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20200113214433-6cbb518174d4 h1:n38UnKhQmPxZ9+PTKW38D0TJAmdiElHMlTM0uT2LACg= github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20200113214433-6cbb518174d4/go.mod h1:YWYrZjvs/qwZhDmsDnTaMXcno5Y0MYPN7rhMarLiUmI= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= 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= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191002201903-404acd9df4cc/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 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/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 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.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck= github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.chromium.org/luci v0.0.0-20200722211809-bab0c30be68b h1:0jYe0tlrrMwhAesVR6p4+3Xryezo1c7jZZYu3+73tuQ= go.chromium.org/luci v0.0.0-20200722211809-bab0c30be68b/go.mod h1:MIQewVTLvOvc0UioV0JNqTNO/RspKFS0XEeoKrOxsdM= go.chromium.org/luci v0.0.0-20201204084249-3e81ee3e83fe h1:qIWCxSxxiH4294whxeqOxsQ9KaW7CW0gGa3tqluP2NA= go.chromium.org/luci v0.0.0-20201204084249-3e81ee3e83fe/go.mod h1:MIQewVTLvOvc0UioV0JNqTNO/RspKFS0XEeoKrOxsdM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20190912063710-ac5d2bfcbfe0/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191014171548-69215a2ee97e/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 h1:Mj83v+wSRNEar42a/MQgxk9X42TdEmrOl9i+y8WbxLo= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 h1:BaN3BAqnopnKjvl+15DYP6LLrbBHfbfmlFYzmFj/Q9Q= golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190917162342-3b4f30a44f3b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010171213-8abd42400456/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191017205301-920acffc3e65/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201030143252-cf7a54d06671/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201105220310-78b158585360/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.11.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts= google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.34.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.35.0 h1:TBCmTTxUrRDA1iTctnK/fIeitxIZ+TQuaf0j29fmCGo= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0 h1:uWrpz12dpVPn7cojP82mk02XDgTJLDPc2KbVTxrWb4A= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201106154455-f9bfe239b0ba/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e h1:wYR00/Ht+i/79g/gzhdehBgLIJCklKoc8Q/NebdzzpY= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210212180131-e7f2df4ecc2d h1:Edhcm0CKDPLQIecHCp5Iz57Lo7MfT6zUFBAlocmOjcY= google.golang.org/genproto v0.0.0-20210212180131-e7f2df4ecc2d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= osconfig-20210219.00/google-osconfig-agent.conf000066400000000000000000000001651401404514100210210ustar00rootroot00000000000000description "Google OSConfig Agent" start on stopped rc RUNLEVEL=[2345] respawn exec /usr/bin/google_osconfig_agent osconfig-20210219.00/google-osconfig-agent.service000066400000000000000000000005011401404514100215260ustar00rootroot00000000000000[Unit] Description=Google OSConfig Agent After=local-fs.target network-online.target Wants=local-fs.target network-online.target [Service] ExecStart=/usr/bin/google_osconfig_agent Restart=always RestartSec=1 StartLimitInterval=120 StartLimitBurst=3 KillMode=mixed KillSignal=SIGTERM [Install] WantedBy=multi-user.targetosconfig-20210219.00/inventory/000077500000000000000000000000001401404514100160305ustar00rootroot00000000000000osconfig-20210219.00/inventory/inventory.go000066400000000000000000000045551401404514100204250ustar00rootroot00000000000000// Copyright 2017 Google 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 inventory scans the current inventory (patches and package installed and available) // and writes them to Guest Attributes. package inventory import ( "context" "time" "github.com/GoogleCloudPlatform/osconfig/agentconfig" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/osinfo" "github.com/GoogleCloudPlatform/osconfig/packages" ) // InstanceInventory is an instances inventory data. type InstanceInventory struct { Hostname string LongName string ShortName string Version string Architecture string KernelVersion string KernelRelease string OSConfigAgentVersion string InstalledPackages *packages.Packages PackageUpdates *packages.Packages LastUpdated string } // Get generates inventory data. func Get(ctx context.Context) *InstanceInventory { clog.Debugf(ctx, "Gathering instance inventory.") hs := &InstanceInventory{} installedPackages, err := packages.GetInstalledPackages(ctx) if err != nil { clog.Errorf(ctx, "packages.GetInstalledPackages() error: %v", err) } packageUpdates, err := packages.GetPackageUpdates(ctx) if err != nil { clog.Errorf(ctx, "packages.GetPackageUpdates() error: %v", err) } oi, err := osinfo.Get() if err != nil { clog.Errorf(ctx, "osinfo.Get() error: %v", err) } hs.Hostname = oi.Hostname hs.LongName = oi.LongName hs.ShortName = oi.ShortName hs.Version = oi.Version hs.KernelVersion = oi.KernelVersion hs.KernelRelease = oi.KernelRelease hs.Architecture = oi.Architecture hs.OSConfigAgentVersion = agentconfig.Version() hs.InstalledPackages = installedPackages hs.PackageUpdates = packageUpdates hs.LastUpdated = time.Now().UTC().Format(time.RFC3339) return hs } osconfig-20210219.00/main.go000066400000000000000000000165501401404514100152550ustar00rootroot00000000000000// Copyright 2018 Google 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. // osconfig_agent interacts with the osconfig api. package main import ( "context" "flag" "fmt" "io" "net/http" "os" "os/signal" "path/filepath" "runtime" "syscall" "time" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/GoogleCloudPlatform/osconfig/agentconfig" "github.com/GoogleCloudPlatform/osconfig/agentendpoint" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/policies" "github.com/GoogleCloudPlatform/osconfig/tasker" "github.com/tarm/serial" _ "net/http/pprof" _ "google.golang.org/genproto/googleapis/rpc/errdetails" ) var ( version string profile = flag.Bool("profile", false, "serve profiling data at localhost:6060/debug/pprof") ) func init() { if version == "" { version = "manual-" + time.Now().Format(time.RFC3339) } // We do this here so the -X value doesn't need the full path. agentconfig.SetVersion(version) os.MkdirAll(filepath.Dir(agentconfig.RestartFile()), 0755) } type serialPort struct { port string } func (s *serialPort) Write(b []byte) (int, error) { c := &serial.Config{Name: s.port, Baud: 115200} p, err := serial.OpenPort(c) if err != nil { return 0, err } defer p.Close() return p.Write(b) } var deferredFuncs []func() func run(ctx context.Context) { // Setup logging. opts := logger.LogOpts{LoggerName: "OSConfigAgent"} if agentconfig.Stdout() { opts.Writers = []io.Writer{os.Stdout} } if runtime.GOOS == "windows" { opts.Writers = append(opts.Writers, &serialPort{"COM1"}) } // If this call to WatchConfig fails (like a metadata error) we can't continue. if err := agentconfig.WatchConfig(ctx); err != nil { logger.Init(ctx, opts) logger.Fatalf(err.Error()) } opts.Debug = agentconfig.Debug() opts.ProjectName = agentconfig.ProjectID() if err := logger.Init(ctx, opts); err != nil { fmt.Printf("Error initializing logger: %v", err) os.Exit(1) } ctx = clog.WithLabels(ctx, map[string]string{"instance_name": agentconfig.Name()}) // Remove any existing restart file. if err := os.Remove(agentconfig.RestartFile()); err != nil && !os.IsNotExist(err) { clog.Errorf(ctx, "Error removing restart signal file: %v", err) } deferredFuncs = append(deferredFuncs, logger.Close, func() { clog.Infof(ctx, "OSConfig Agent (version %s) shutting down.", agentconfig.Version()) }) obtainLock() // obtainLock adds functions to clear the lock at close. logger.DeferredFatalFuncs = append(logger.DeferredFatalFuncs, deferredFuncs...) clog.Infof(ctx, "OSConfig Agent (version %s) started.", agentconfig.Version()) // Call RegisterAgent on start then at least once every day. go func() { for { if agentconfig.TaskNotificationEnabled() || agentconfig.GuestPoliciesEnabled() { if client, err := agentendpoint.NewClient(ctx); err != nil { logger.Errorf(err.Error()) } else if err := client.RegisterAgent(ctx); err != nil { logger.Errorf(err.Error()) } } time.Sleep(24 * time.Hour) } }() switch action := flag.Arg(0); action { case "", "run", "noservice": runServiceLoop(ctx) case "inventory", "osinventory": client, err := agentendpoint.NewClient(ctx) if err != nil { logger.Fatalf(err.Error()) } tasker.Enqueue(ctx, "Report OSInventory", func() { client.ReportInventory(ctx) }) tasker.Close() return case "gp", "policies", "guestpolicies", "ospackage": policies.Run(ctx) tasker.Close() return case "w", "waitfortasknotification", "ospatch": client, err := agentendpoint.NewClient(ctx) if err != nil { logger.Fatalf(err.Error()) } client.WaitForTaskNotification(ctx) select { case <-ctx.Done(): } default: logger.Fatalf("Unknown arg %q", action) } } func runTaskLoop(ctx context.Context, c chan struct{}) { var taskNotificationClient *agentendpoint.Client var err error for { if agentconfig.TaskNotificationEnabled() && (taskNotificationClient == nil || taskNotificationClient.Closed()) { // Start WaitForTaskNotification if we need to. taskNotificationClient, err = agentendpoint.NewClient(ctx) if err != nil { clog.Errorf(ctx, err.Error()) } else { taskNotificationClient.WaitForTaskNotification(ctx) } } else if !agentconfig.TaskNotificationEnabled() && taskNotificationClient != nil && !taskNotificationClient.Closed() { // Cancel WaitForTaskNotification if we need to, this will block if there is // an existing current task running. if err := taskNotificationClient.Close(); err != nil { clog.Errorf(ctx, err.Error()) } } // This is just to signal WaitForTaskNotification has run if needed. select { case c <- struct{}{}: default: } if err := agentconfig.WatchConfig(ctx); err != nil { clog.Errorf(ctx, err.Error()) } select { case <-ctx.Done(): return default: continue } } } func runServiceLoop(ctx context.Context) { // This is just to ensure WaitForTaskNotification runs before any periodocs. c := make(chan struct{}) // Configures WaitForTaskNotification, waits for config changes with WatchConfig. go runTaskLoop(ctx, c) <-c // Runs functions that need to run on a set interval. ticker := time.NewTicker(agentconfig.SvcPollInterval()) defer ticker.Stop() for { if _, err := os.Stat(agentconfig.RestartFile()); err == nil { clog.Infof(ctx, "Restart required marker file exists, beginning agent shutdown, waiting for tasks to complete.") tasker.Close() clog.Infof(ctx, "All tasks completed, stopping agent.") for _, f := range deferredFuncs { f() } os.Exit(2) } if agentconfig.GuestPoliciesEnabled() { policies.Run(ctx) } if agentconfig.OSInventoryEnabled() { // This should always run after ospackage.SetConfig. tasker.Enqueue(ctx, "Report OSInventory", func() { client, err := agentendpoint.NewClient(ctx) if err != nil { logger.Errorf(err.Error()) } client.ReportInventory(ctx) client.Close() }) } select { case <-ticker.C: continue case <-ctx.Done(): return } } } func main() { flag.Parse() ctx, cncl := context.WithCancel(context.Background()) ctx = clog.WithLabels(ctx, map[string]string{"agent_version": agentconfig.Version()}) c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGTERM, syscall.SIGINT) go func() { select { case <-c: cncl() } }() if *profile { go func() { fmt.Println(http.ListenAndServe("localhost:6060", nil)) }() } switch action := flag.Arg(0); action { // wuaupdates just runs the packages.WUAUpdates function and returns it's output // as JSON on stdout. This avoids memory issues with the WUA api since this is // called often for Windows inventory runs. case "wuaupdates": if err := wuaUpdates(flag.Arg(1)); err != nil { fmt.Fprint(os.Stderr, err) os.Exit(1) } os.Exit(0) case "", "run": runService(ctx) default: run(ctx) } for _, f := range deferredFuncs { f() } } osconfig-20210219.00/main_linux.go000066400000000000000000000033401401404514100164650ustar00rootroot00000000000000// Copyright 2019 Google 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 main import ( "context" "errors" "os" "path/filepath" "syscall" "time" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) func runService(ctx context.Context) { run(ctx) } func obtainLock() { lockFile := "/run/lock/osconfig_agent.lock" err := os.Mkdir(filepath.Dir(lockFile), 1777) if err != nil && !os.IsExist(err) { logger.Fatalf("Cannot obtain agent lock: %v", err) } f, err := os.OpenFile(lockFile, os.O_RDWR|os.O_CREATE, 0600) if err != nil && !os.IsExist(err) { logger.Fatalf("Cannot obtain agent lock: %v", err) } c := make(chan error) go func() { c <- syscall.Flock(int(f.Fd()), syscall.LOCK_EX) }() select { case err := <-c: if err != nil { logger.Fatalf("Cannot obtain agent lock, is the agent already running? Error: %v", err) } case <-time.After(time.Second): logger.Fatalf("OSConfig agent lock already held, is the agent already running?") } deferredFuncs = append(deferredFuncs, func() { syscall.Flock(int(f.Fd()), syscall.LOCK_UN); f.Close(); os.Remove(lockFile) }) } func wuaUpdates(_ string) error { return errors.New("wuaUpdates not implemented on linux") } osconfig-20210219.00/main_windows.go000066400000000000000000000077011401404514100170250ustar00rootroot00000000000000// Copyright 2019 Google 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 main import ( "context" "encoding/json" "errors" "fmt" "os" "path/filepath" "syscall" "unsafe" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/GoogleCloudPlatform/osconfig/packages" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" ) var ( kernel32 = windows.NewLazySystemDLL("kernel32.dll") procLockFileEx = kernel32.NewProc("LockFileEx") procUnlockFileEx = kernel32.NewProc("UnlockFileEx") ) const ( serviceName = "google_osconfig_agent" // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-lockfileex LOCKFILE_EXCLUSIVE_LOCK = 2 LOCKFILE_FAIL_IMMEDIATELY = 1 ) func lockFileEx(hFile uintptr, dwFlags, nNumberOfBytesToLockLow, nNumberOfBytesToLockHigh uint32, lpOverlapped *syscall.Overlapped) (err error) { ret, _, _ := procLockFileEx.Call( hFile, uintptr(dwFlags), 0, uintptr(nNumberOfBytesToLockLow), uintptr(nNumberOfBytesToLockHigh), uintptr(unsafe.Pointer(lpOverlapped)), ) // If the function succeeds, the return value is nonzero. if ret == 0 { return errors.New("LockFileEx unable to obtain lock") } return nil } func unlockFileEx(hFile uintptr, nNumberOfBytesToLockLow, nNumberOfBytesToLockHigh uint32, lpOverlapped *syscall.Overlapped) (err error) { ret, _, _ := procUnlockFileEx.Call( hFile, 0, uintptr(nNumberOfBytesToLockLow), uintptr(nNumberOfBytesToLockHigh), uintptr(unsafe.Pointer(lpOverlapped)), ) // If the function succeeds, the return value is nonzero. if ret == 0 { return errors.New("UnlockFileEx unable to unlock") } return nil } func obtainLock() { lockFile := `C:\Program Files\Google\OSConfig\lock` err := os.MkdirAll(filepath.Dir(lockFile), 0755) if err != nil && !os.IsExist(err) { logger.Fatalf("Cannot obtain agent lock: %v", err) } f, err := os.OpenFile(lockFile, os.O_RDWR|os.O_CREATE, 0600) if err != nil && !os.IsExist(err) { logger.Fatalf("Cannot obtain agent lock: %v", err) } if err := lockFileEx(f.Fd(), LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY, 1, 0, &syscall.Overlapped{}); err != nil { logger.Fatalf("OSConfig agent lock already held, is the agent already running?") } deferredFuncs = append(deferredFuncs, func() { unlockFileEx(f.Fd(), 1, 0, &syscall.Overlapped{}); f.Close(); os.Remove(lockFile) }) } type service struct { ctx context.Context run func(context.Context) } func (s *service) Execute(_ []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { status <- svc.Status{State: svc.StartPending} ctx, cncl := context.WithCancel(s.ctx) defer cncl() done := make(chan struct{}) go func() { s.run(ctx) close(done) }() status <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} for { select { case <-done: status <- svc.Status{State: svc.StopPending} return false, 0 case c := <-r: switch c.Cmd { case svc.Interrogate: status <- c.CurrentStatus case svc.Stop, svc.Shutdown: cncl() default: } } } } func runService(ctx context.Context) { if err := svc.Run(serviceName, &service{run: run, ctx: ctx}); err != nil { logger.Fatalf("svc.Run error: %v", err) } } func wuaUpdates(query string) error { updts, err := packages.WUAUpdates(query) if err != nil { return err } data, err := json.Marshal(updts) if err != nil { return err } fmt.Fprint(os.Stdout, string(data)) return nil } osconfig-20210219.00/osinfo/000077500000000000000000000000001401404514100152705ustar00rootroot00000000000000osconfig-20210219.00/osinfo/osinfo.go000066400000000000000000000023531401404514100171170ustar00rootroot00000000000000/* Copyright 2017 Google 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 osinfo provides basic system info functions for Windows and // Linux. package osinfo const ( // Linux is the default shortname used for a Linux system. Linux = "linux" // Windows is the default shortname used for Windows system. Windows = "windows" ) // OSInfo describes an operating system. type OSInfo struct { Hostname, LongName, ShortName, Version, KernelVersion, KernelRelease, Architecture string } // Architecture attempts to standardize architecture naming. func Architecture(arch string) string { switch arch { case "amd64", "64-bit": arch = "x86_64" case "i386", "i686", "32-bit": arch = "x86_32" case "noarch": arch = "all" } return arch } osconfig-20210219.00/osinfo/osinfo_linux.go000066400000000000000000000057511401404514100203430ustar00rootroot00000000000000/* Copyright 2017 Google 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 osinfo import ( "bufio" "bytes" "fmt" "io/ioutil" "regexp" "strings" "github.com/GoogleCloudPlatform/osconfig/util" "golang.org/x/sys/unix" ) var ( entRelVerRgx = regexp.MustCompile(`\d+(\.\d+)?(\.\d+)?`) ) const ( osRelease = "/etc/os-release" oRelease = "/etc/oracle-release" rhRelease = "/etc/redhat-release" ) func parseOsRelease(releaseDetails string) *OSInfo { oi := &OSInfo{} scanner := bufio.NewScanner(bytes.NewReader([]byte(releaseDetails))) for scanner.Scan() { entry := strings.Split(scanner.Text(), "=") switch entry[0] { case "": continue case "PRETTY_NAME": oi.LongName = strings.Trim(entry[1], `"`) case "VERSION_ID": oi.Version = strings.Trim(entry[1], `"`) case "ID": oi.ShortName = strings.Trim(entry[1], `"`) } if oi.LongName != "" && oi.Version != "" && oi.ShortName != "" { break } } if oi.ShortName == "" { oi.ShortName = Linux } return oi } func parseEnterpriseRelease(releaseDetails string) *OSInfo { rel := releaseDetails var sn string switch { case strings.Contains(rel, "CentOS"): sn = "centos" case strings.Contains(rel, "Red Hat"): sn = "rhel" case strings.Contains(rel, "Oracle"): sn = "ol" } return &OSInfo{ ShortName: sn, LongName: strings.Replace(rel, " release ", " ", 1), Version: entRelVerRgx.FindString(rel), } } // Get reports OSInfo. func Get() (*OSInfo, error) { var oi *OSInfo var parseReleaseFunc func(string) *OSInfo var releaseFile string switch { // Check for /etc/os-release first. case util.Exists(osRelease): releaseFile = osRelease parseReleaseFunc = parseOsRelease case util.Exists(oRelease): releaseFile = oRelease parseReleaseFunc = parseEnterpriseRelease case util.Exists(rhRelease): releaseFile = rhRelease parseReleaseFunc = parseEnterpriseRelease } b, err := ioutil.ReadFile(releaseFile) if err != nil { oi = &OSInfo{ShortName: Linux} } else { oi = parseReleaseFunc(string(b)) } var uts unix.Utsname if err := unix.Uname(&uts); err != nil { return oi, fmt.Errorf("unix.Uname error: %v", err) } // unix.Utsname Fields are [65]byte so we need to trim any trailing null characters. oi.Hostname = string(bytes.TrimRight(uts.Nodename[:], "\x00")) oi.Architecture = Architecture(string(bytes.TrimRight(uts.Machine[:], "\x00"))) oi.KernelVersion = string(bytes.TrimRight(uts.Version[:], "\x00")) oi.KernelRelease = string(bytes.TrimRight(uts.Release[:], "\x00")) return oi, nil } osconfig-20210219.00/osinfo/osinfo_linux_test.go000066400000000000000000000063411401404514100213760ustar00rootroot00000000000000// Copyright 2019 Google 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 osinfo import ( "testing" ) // debian system with all details in os-release file // happy case, taken from google desktop func TestGetDistributionInfoOSRelease(t *testing.T) { fcontent := `PRETTY_NAME="Debian buster" NAME="Debian GNU/Linux" VERSION_ID="10" VERSION="10 (buster)" VERSION_CODENAME=buster ID=debian HOME_URL="https://www.debian.org/" SUPPORT_URL="https://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/" ` di := parseOsRelease(fcontent) tests := []struct { expect string actual string errMsg string }{ {"Debian buster", di.LongName, "unexpected long name"}, {"debian", di.ShortName, "unexpected short name"}, {"10", di.Version, "unexpected version id"}, } for _, v := range tests { if v.actual != v.expect { t.Errorf("%s! expected(%s); got(%s)", v.errMsg, v.expect, v.actual) } } } // debian system with empty os-release file // with empty file, the short name should default to Linux func TestGetDistributionInfoEmptyOSRelease(t *testing.T) { fcontent := ` ` di := parseOsRelease(fcontent) tests := []struct { expectation string actual string errMsg string }{ {"", di.LongName, "unexpected long name"}, {"linux", di.ShortName, "unexpected short name"}, } for _, v := range tests { if v.actual != v.expectation { t.Errorf("%s! expected(%s); got(%s)", v.errMsg, v.expectation, v.actual) } } } // os-release // normal details of centos system func TestGetDistributionInfoOracleReleaseCentos(t *testing.T) { fcontent := `CentOS Linux release 7.6.1810 (Core)` di := parseEnterpriseRelease(fcontent) tests := []struct { expect string actual string errMsg string }{ {"CentOS Linux 7.6.1810 (Core)", di.LongName, "unexpected long name"}, {"centos", di.ShortName, "unexpected short name"}, {"7.6.1810", di.Version, "unexpected version id"}, } for _, v := range tests { if v.actual != v.expect { t.Errorf("%s! expected(%s); got(%s)", v.errMsg, v.expect, v.actual) } } } //// redhat-release //// normal details of redhat system func TestGetDistributionInfoRedHatRelease(t *testing.T) { fcontent := `Red Hat Enterprise Linux release 8.0 (Ootpa)` di := parseEnterpriseRelease(fcontent) tests := []struct { expectation string actual string errMsg string }{ {"Red Hat Enterprise Linux 8.0 (Ootpa)", di.LongName, "unexpected long name"}, {"rhel", di.ShortName, "unexpected short name"}, {"8.0", di.Version, "unexpected version id"}, } for _, v := range tests { if v.actual != v.expectation { t.Errorf("%s! expected(%s); got(%s)", v.errMsg, v.expectation, v.actual) } } } //TODO: add test case for oracle release system osconfig-20210219.00/osinfo/osinfo_windows.go000066400000000000000000000121051401404514100206650ustar00rootroot00000000000000/* Copyright 2017 Google 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 osinfo import ( "errors" "fmt" "os" "path/filepath" "runtime" "syscall" "unsafe" "github.com/StackExchange/wmi" "golang.org/x/sys/windows" ) var ( version = windows.NewLazySystemDLL("version.dll") procGetFileVersionInfoSizeW = version.NewProc("GetFileVersionInfoSizeW") procGetFileVersionInfoW = version.NewProc("GetFileVersionInfoW") procVerQueryValueW = version.NewProc("VerQueryValueW") ) // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx func getTranslation(block []byte) (string, error) { var start uint var length uint blockStart := uintptr(unsafe.Pointer(&block[0])) if ret, _, _ := procVerQueryValueW.Call( blockStart, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(`\VarFileInfo\Translation`))), uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&length))); ret == 0 { return "", errors.New("zero return code from VerQueryValueW indicates failure") } begin := int(start) - int(blockStart) // For translation data length is bytes. trans := block[begin : begin+int(length)] // Each 'translation' is 4 bytes long (2 16-bit sections), we just want the // first one for simplicity. t := make([]byte, 4) // 16-bit language ID little endian // https://msdn.microsoft.com/en-us/library/windows/desktop/dd318693(v=vs.85).aspx t[0], t[1] = trans[1], trans[0] // 16-bit code page ID little endian // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx t[2], t[3] = trans[3], trans[2] return fmt.Sprintf("%x", t), nil } // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx func getStringFileInfo(block []byte, langCodePage, name string) (string, error) { var start uint var length uint blockStart := uintptr(unsafe.Pointer(&block[0])) if ret, _, _ := procVerQueryValueW.Call( blockStart, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fmt.Sprintf(`\StringFileInfo\%s\%s`, langCodePage, name)))), uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&length))); ret == 0 { return "", errors.New("zero return code from VerQueryValueW indicates failure") } begin := int(start) - int(blockStart) // For version information length is characters (UTF16). result := block[begin : begin+int(2*length)] // Result is UTF16LE. u16s := make([]uint16, length) for i := range u16s { u16s[i] = uint16(result[i*2+1])<<8 | uint16(result[i*2]) } return syscall.UTF16ToString(u16s), nil } func getVersion(block []byte, langCodePage string) (string, string, error) { ver, err := getStringFileInfo(block, langCodePage, "FileVersion") if err != nil { return "", "", err } rel, err := getStringFileInfo(block, langCodePage, "ProductVersion") return ver, rel, err } func getKernelInfo() (string, string, error) { root := os.Getenv("SystemRoot") if root == "" { root = `C:\Windows` } path := filepath.Join(root, "System32", "ntoskrnl.exe") if _, err := os.Stat(path); err != nil { return "", "", err } pPtr := unsafe.Pointer(syscall.StringToUTF16Ptr(path)) size, _, _ := procGetFileVersionInfoSizeW.Call( uintptr(pPtr)) if size <= 0 { return "", "", errors.New("GetFileVersionInfoSize call failed, data size can not be 0") } info := make([]byte, size) if ret, _, _ := procGetFileVersionInfoW.Call( uintptr(pPtr), 0, uintptr(len(info)), uintptr(unsafe.Pointer(&info[0]))); ret == 0 { return "", "", errors.New("zero return code from GetFileVersionInfoW indicates failure") } // This should be something like 040904b0 for US English UTF16LE. langCodePage, err := getTranslation(info) if err != nil { return "", "", fmt.Errorf("getTranslation() error: %v", err) } return getVersion(info, langCodePage) } type win32OperatingSystem struct { Caption, Version string } // Get reports OSInfo. func Get() (*OSInfo, error) { oi := &OSInfo{ShortName: Windows, Architecture: Architecture(runtime.GOARCH)} hn, err := os.Hostname() if err != nil { return oi, fmt.Errorf("os.Hostname() error: %v", err) } oi.Hostname = hn kVersion, kRelease, err := getKernelInfo() if err != nil { return oi, fmt.Errorf("getKernelInfo() error: %v", err) } oi.KernelVersion = kVersion oi.KernelRelease = kRelease var ops []win32OperatingSystem query := "SELECT Caption, Version FROM Win32_OperatingSystem" if err := wmi.Query(query, &ops); err != nil { return oi, fmt.Errorf("wmi.Query(%q) error: %v", query, err) } if len(ops) == 0 { return oi, fmt.Errorf("wmi.Query(%q) nil output", query) } oi.LongName = ops[0].Caption oi.Version = ops[0].Version return oi, nil } osconfig-20210219.00/ospatch/000077500000000000000000000000001401404514100154345ustar00rootroot00000000000000osconfig-20210219.00/ospatch/apt_upgrade.go000066400000000000000000000055251401404514100202650ustar00rootroot00000000000000// Copyright 2019 Google 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 ospatch import ( "context" "fmt" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" ) type aptGetUpgradeOpts struct { upgradeType packages.AptUpgradeType exclusivePackages []string excludes []string dryrun bool } // AptGetUpgradeOption is an option for apt-get update. type AptGetUpgradeOption func(*aptGetUpgradeOpts) // AptGetUpgradeType returns a AptGetUpgradeOption that specifies upgrade type. func AptGetUpgradeType(upgradeType packages.AptUpgradeType) AptGetUpgradeOption { return func(args *aptGetUpgradeOpts) { args.upgradeType = upgradeType } } // AptGetExcludes excludes these packages from upgrade. func AptGetExcludes(excludes []string) AptGetUpgradeOption { return func(args *aptGetUpgradeOpts) { args.excludes = excludes } } // AptGetExclusivePackages includes only these packages in the upgrade. func AptGetExclusivePackages(exclusivePackages []string) AptGetUpgradeOption { return func(args *aptGetUpgradeOpts) { args.exclusivePackages = exclusivePackages } } // AptGetDryRun performs a dry run. func AptGetDryRun(dryrun bool) AptGetUpgradeOption { return func(args *aptGetUpgradeOpts) { args.dryrun = dryrun } } // RunAptGetUpgrade runs apt-get upgrade. func RunAptGetUpgrade(ctx context.Context, opts ...AptGetUpgradeOption) error { aptOpts := &aptGetUpgradeOpts{ upgradeType: packages.AptGetUpgrade, excludes: nil, exclusivePackages: nil, dryrun: false, } for _, opt := range opts { opt(aptOpts) } pkgs, err := packages.AptUpdates(ctx, packages.AptGetUpgradeType(aptOpts.upgradeType), packages.AptGetUpgradeShowNew(true)) if err != nil { return err } fPkgs, err := filterPackages(pkgs, aptOpts.exclusivePackages, aptOpts.excludes) if err != nil { return err } if len(fPkgs) == 0 { clog.Infof(ctx, "No packages to update.") return nil } var pkgNames []string for _, pkg := range fPkgs { pkgNames = append(pkgNames, pkg.Name) } msg := fmt.Sprintf("%d packages: %s", len(pkgNames), fPkgs) if aptOpts.dryrun { clog.Infof(ctx, "Running in dryrun mode, not updating %s", msg) return nil } clog.Infof(ctx, "Updating %s", msg) return packages.InstallAptPackages(ctx, pkgNames) } osconfig-20210219.00/ospatch/googet_update.go000066400000000000000000000045511401404514100206160ustar00rootroot00000000000000// Copyright 2019 Google 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 ospatch import ( "context" "fmt" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" ) type googetUpdateOpts struct { exclusivePackages []string excludes []string dryrun bool } // GooGetUpdateOption is an option for apt-get update. type GooGetUpdateOption func(*googetUpdateOpts) // GooGetExcludes excludes these packages from upgrade. func GooGetExcludes(excludes []string) GooGetUpdateOption { return func(args *googetUpdateOpts) { args.excludes = excludes } } // GooGetExclusivePackages includes only these packages in the upgrade. func GooGetExclusivePackages(exclusivePackages []string) GooGetUpdateOption { return func(args *googetUpdateOpts) { args.exclusivePackages = exclusivePackages } } // GooGetDryRun performs a dry run. func GooGetDryRun(dryrun bool) GooGetUpdateOption { return func(args *googetUpdateOpts) { args.dryrun = dryrun } } // RunGooGetUpdate runs googet update. func RunGooGetUpdate(ctx context.Context, opts ...GooGetUpdateOption) error { googetOpts := &googetUpdateOpts{} for _, opt := range opts { opt(googetOpts) } pkgs, err := packages.GooGetUpdates(ctx) if err != nil { return err } fPkgs, err := filterPackages(pkgs, googetOpts.exclusivePackages, googetOpts.excludes) if err != nil { return err } if len(fPkgs) == 0 { clog.Infof(ctx, "No packages to update.") return nil } var pkgNames []string for _, pkg := range fPkgs { pkgNames = append(pkgNames, pkg.Name) } msg := fmt.Sprintf("%d packages: %s", len(pkgNames), fPkgs) if googetOpts.dryrun { clog.Infof(ctx, "Running in dryrun mode, not updating %s", msg) return nil } clog.Infof(ctx, "Updating %s", msg) return packages.InstallGooGetPackages(ctx, pkgNames) } osconfig-20210219.00/ospatch/system_linux.go000066400000000000000000000071151401404514100205320ustar00rootroot00000000000000// Copyright 2019 Google 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. //+build !test package ospatch import ( "bytes" "context" "os" "os/exec" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" ) const ( systemctl = "/bin/systemctl" ) // DisableAutoUpdates disables system auto updates. func DisableAutoUpdates(ctx context.Context) { // yum-cron on el systems if _, err := os.Stat("/usr/lib/systemd/system/yum-cron.service"); err == nil { out, err := exec.Command(systemctl, "is-enabled", "yum-cron.service").CombinedOutput() if err != nil { if eerr, ok := err.(*exec.ExitError); ok { // Error code of 1 indicates disabled. if eerr.ExitCode() == 1 { return } } clog.Errorf(ctx, "Error checking status of yum-cron, error: %v, out: %s", err, out) } clog.Debugf(ctx, "Disabling yum-cron") out, err = exec.Command(systemctl, "stop", "yum-cron.service").CombinedOutput() if err != nil { clog.Errorf(ctx, "Error stopping yum-cron, error: %v, out: %s", err, out) } out, err = exec.Command(systemctl, "disable", "yum-cron.service").CombinedOutput() if err != nil { clog.Errorf(ctx, "Error disabling yum-cron, error: %v, out: %s", err, out) } } else if _, err := os.Stat("/usr/sbin/yum-cron"); err == nil { out, err := exec.Command("/sbin/chkconfig", "yum-cron").CombinedOutput() if err != nil { clog.Errorf(ctx, "Error checking status of yum-cron, error: %v, out: %s", err, out) } if bytes.Contains(out, []byte("disabled")) { return } clog.Debugf(ctx, "Disabling yum-cron") out, err = exec.Command("/sbin/chkconfig", "yum-cron", "off").CombinedOutput() if err != nil { clog.Errorf(ctx, "Error disabling yum-cron, error: %v, out: %s", err, out) } } // dnf-automatic on el8 systems if _, err := os.Stat("/usr/lib/systemd/system/dnf-automatic.timer"); err == nil { out, err := exec.Command(systemctl, "list-timers", "dnf-automatic.timer").CombinedOutput() if err != nil { clog.Errorf(ctx, "Error checking status of dnf-automatic, error: %v, out: %s", err, out) } if bytes.Contains(out, []byte("0 timers listed")) { return } clog.Debugf(ctx, "Disabling dnf-automatic") out, err = exec.Command(systemctl, "stop", "dnf-automatic.timer").CombinedOutput() if err != nil { clog.Errorf(ctx, "Error stopping dnf-automatic, error: %v, out: %s", err, out) } out, err = exec.Command(systemctl, "disable", "dnf-automatic.timer").CombinedOutput() if err != nil { clog.Errorf(ctx, "Error disabling dnf-automatic, error: %v, out: %s", err, out) } } // apt unattended-upgrades // TODO: Removing the package is a bit overkill, look into just managing // the configs, this is probably best done by looking through // /etc/apt/apt.conf.d/ and setting APT::Periodic::Unattended-Upgrade to 0. if _, err := os.Stat("/usr/bin/unattended-upgrades"); err == nil { clog.Debugf(ctx, "Removing unattended-upgrades package") if err := packages.RemoveAptPackages(ctx, []string{"unattended-upgrades"}); err != nil { clog.Errorf(ctx, err.Error()) } } } osconfig-20210219.00/ospatch/system_test_stub.go000066400000000000000000000013231401404514100214020ustar00rootroot00000000000000// Copyright 2019 Google 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. //+build test package ospatch func disableAutoUpdates() {} func rebootSystem() error { return nil } osconfig-20210219.00/ospatch/system_windows.go000066400000000000000000000034301401404514100210610ustar00rootroot00000000000000// Copyright 2019 Google 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. //+build !test package ospatch import ( "context" "os" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" "golang.org/x/sys/windows/registry" ) // DisableAutoUpdates disables system auto updates. func DisableAutoUpdates(ctx context.Context) { k, openedExisting, err := registry.CreateKey(registry.LOCAL_MACHINE, `SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU`, registry.ALL_ACCESS) if err != nil { clog.Errorf(ctx, "Error disabling Windows auto updates, error: %v", err) } defer k.Close() if openedExisting { val, _, err := k.GetIntegerValue("NoAutoUpdate") if err == nil && val == 1 { return } } clog.Debugf(ctx, "Disabling Windows Auto Updates") if err := k.SetDWordValue("NoAutoUpdate", 1); err != nil { clog.Errorf(ctx, "Error disabling Windows auto updates, error: %v", err) } if _, err := os.Stat(`C:\Program Files\Google\Compute Engine\tools\auto_updater.ps1`); err == nil { clog.Debugf(ctx, "Removing google-compute-engine-auto-updater package") if err := packages.RemoveGooGetPackages(ctx, []string{"google-compute-engine-auto-updater"}); err != nil { clog.Errorf(ctx, err.Error()) } } } osconfig-20210219.00/ospatch/updates.go000066400000000000000000000072561401404514100174420ustar00rootroot00000000000000// Copyright 2019 Google 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 ospatch import ( "bufio" "bytes" "errors" "fmt" "os" "os/exec" "strconv" "github.com/GoogleCloudPlatform/osconfig/packages" ) const ( rpmquery = "/usr/bin/rpmquery" ) func getBtime(stat string) (int, error) { f, err := os.Open(stat) if err != nil { return 0, fmt.Errorf("error opening %s: %v", stat, err) } defer f.Close() var btime int scnr := bufio.NewScanner(f) for scnr.Scan() { if bytes.HasPrefix(scnr.Bytes(), []byte("btime")) { split := bytes.SplitN(scnr.Bytes(), []byte(" "), 2) if len(split) != 2 { return 0, fmt.Errorf("error parsing btime from %s: %q", stat, scnr.Text()) } btime, err = strconv.Atoi(string(bytes.TrimSpace(split[1]))) if err != nil { return 0, fmt.Errorf("error parsing btime: %v", err) } break } } if err := scnr.Err(); err != nil && btime == 0 { return 0, fmt.Errorf("error scanning %s: %v", stat, err) } if btime == 0 { return 0, fmt.Errorf("could not find btime in %s", stat) } return btime, nil } func rpmRebootRequired(pkgs []byte, btime int) bool { // Scanning this output is best effort, false negatives are much prefered // to false positives, and keeping this as simple as possible is // beneficial. scnr := bufio.NewScanner(bytes.NewReader(pkgs)) for scnr.Scan() { itime, err := strconv.Atoi(scnr.Text()) if err != nil { continue } if itime > btime { return true } } return false } // rpmReboot returns whether an rpm based system should reboot in order to // finish installing updates. // To get this signal we look at a set of well known packages and whether // install time > system boot time. This list is not meant to be exhastive, // just to provide a signal when core system packages are updated. func rpmReboot() (bool, error) { provides := []string{ // Common packages. "kernel", "glibc", "gnutls", // EL packages. "linux-firmware", "openssl-libs", "dbus", // Suse packages. "kernel-firmware", "libopenssl1_1", "libopenssl1_0_0", "dbus-1", } args := append([]string{"--queryformat", "%{INSTALLTIME}\n", "--whatprovides"}, provides...) out, err := exec.Command(rpmquery, args...).Output() if err != nil { // We don't care about return codes as we know some of these packages won't be installed. if _, ok := err.(*exec.ExitError); !ok { return false, fmt.Errorf("error running %s: %v", rpmquery, err) } } btime, err := getBtime("/proc/stat") if err != nil { return false, err } return rpmRebootRequired(out, btime), nil } func containsString(ss []string, c string) bool { for _, s := range ss { if s == c { return true } } return false } func filterPackages(pkgs []packages.PkgInfo, exclusivePackages, excludes []string) ([]packages.PkgInfo, error) { if len(exclusivePackages) != 0 && len(excludes) != 0 { return nil, errors.New("exclusivePackages and excludes can not both be non 0") } var fPkgs []packages.PkgInfo for _, pkg := range pkgs { if containsString(excludes, pkg.Name) { continue } if exclusivePackages == nil || containsString(exclusivePackages, pkg.Name) { fPkgs = append(fPkgs, pkg) } } return fPkgs, nil } osconfig-20210219.00/ospatch/updates_linux.go000066400000000000000000000035111401404514100206470ustar00rootroot00000000000000// Copyright 2018 Google 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. //+build !test package ospatch import ( "context" "errors" "io/ioutil" "os" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/GoogleCloudPlatform/osconfig/util" ) // SystemRebootRequired checks whether a system reboot is required. func SystemRebootRequired(ctx context.Context) (bool, error) { if packages.AptExists { clog.Debugf(ctx, "Checking if reboot required by looking at /var/run/reboot-required.") data, err := ioutil.ReadFile("/var/run/reboot-required") if os.IsNotExist(err) { clog.Debugf(ctx, "/var/run/reboot-required does not exist, indicating no reboot is required.") return false, nil } if err != nil { return false, err } clog.Debugf(ctx, "/var/run/reboot-required exists indicating a reboot is required, content:\n%s", string(data)) return true, nil } if ok := util.Exists(rpmquery); ok { clog.Debugf(ctx, "Checking if reboot required by querying rpm database.") return rpmReboot() } return false, errors.New("no recognized package manager installed, can't determine if reboot is required") } // InstallWUAUpdates is the linux stub for InstallWUAUpdates. func InstallWUAUpdates(ctx context.Context) error { return nil } osconfig-20210219.00/ospatch/updates_test.go000066400000000000000000000047531401404514100205000ustar00rootroot00000000000000// Copyright 2019 Google 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 ospatch import ( "io/ioutil" "os" "testing" ) func TestGetBtime(t *testing.T) { tests := []struct { name string in string want int wantErr bool }{ {"NormalCase", "procs_running 2\nprocs_blocked 0\nctxt 22762852599\nbtime 1561478350\nprocesses 15504510", 1561478350, false}, {"NoBtime", "procs_running 2\nprocs_blocked 0\nctxt 22762852599\nprocesses 15504510", 0, true}, {"CantParseInt", "procs_running 2\nprocs_blocked 0\nctxt 22762852599\nbtime notanint\nprocesses 15504510", 0, true}, {"CantParseLine", "procs_running 2\nprocs_blocked 0\nctxt 22762852599\nbtime1561478350\nprocesses 15504510", 0, true}, } for _, tt := range tests { td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { t.Fatalf("error creating temp dir: %v", err) } defer os.RemoveAll(td) t.Run(tt.name, func(t *testing.T) { f, err := ioutil.TempFile(td, "") if err != nil { t.Fatalf("error creating temp file: %v", err) } if _, err := f.Write([]byte(tt.in)); err != nil { t.Fatalf("error writing temp file: %v", err) } if err := f.Close(); err != nil { t.Fatalf("error writing temp file: %v", err) } got, err := getBtime(f.Name()) if (err != nil) != tt.wantErr { t.Errorf("getBtime() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("getBtime() = %v, want %v", got, tt.want) } }) } } func TestRpmRebootRequired(t *testing.T) { type args struct { pkgs []byte btime int } tests := []struct { name string args args want bool }{ {"RebootRequired", args{[]byte("1\n3\n2\n6"), 5}, true}, {"NoRebootRequired", args{[]byte("1\n3\n2\n5"), 5}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := rpmRebootRequired(tt.args.pkgs, tt.args.btime) if got != tt.want { t.Errorf("rpmRebootRequired() = %v, want %v", got, tt.want) } }) } } osconfig-20210219.00/ospatch/updates_test_stub.go000066400000000000000000000015301401404514100215230ustar00rootroot00000000000000// Copyright 2019 Google 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. //+build test package ospatch import ( "context" ) func systemRebootRequired() (bool, error) { return false, nil } func runUpdates(ctx context.Context, r *patchRun) error { return nil } func rebootSystem() error { return nil } osconfig-20210219.00/ospatch/updates_windows.go000066400000000000000000000152411401404514100212050ustar00rootroot00000000000000// Copyright 2018 Google 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. //+build !test package ospatch import ( "context" "fmt" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" "golang.org/x/sys/windows/registry" ) // SystemRebootRequired checks whether a system reboot is required. func SystemRebootRequired(ctx context.Context) (bool, error) { // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw#remarks clog.Debugf(ctx, "Checking for PendingFileRenameOperations") k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager`, registry.QUERY_VALUE) if err == nil { val, _, err := k.GetStringsValue("PendingFileRenameOperations") if err == nil { k.Close() if len(val) > 0 { clog.Infof(ctx, "PendingFileRenameOperations indicate a reboot is required: %q", val) return true, nil } } else if err != registry.ErrNotExist { return false, err } } else if err != registry.ErrNotExist { return false, err } regKeys := []string{ `SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired`, // Skip checking CBS for now until we implement rate limiting on reboots, this key // will not be reset in some instances for a few minutes after a reboot. This should // not prevent updates from running as this mainly indicates a feature install. // `SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending`, } for _, key := range regKeys { clog.Debugf(ctx, "Checking if reboot required by testing the existance of %s", key) k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE) if err == nil { k.Close() clog.Infof(ctx, "%s exists indicating a reboot is required.", key) return true, nil } else if err != registry.ErrNotExist { return false, err } } return false, nil } func checkFilters(ctx context.Context, updt *packages.IUpdate, kbExcludes, classFilter, exclusive_patches []string) (ok bool, err error) { title, err := updt.GetProperty("Title") if err != nil { return false, fmt.Errorf(`updt.GetProperty("Title"): %v`, err) } defer title.Clear() defer func() { if ok == true { clog.Debugf(ctx, "Update %q not excluded by any filters.", title.ToString()) } }() kbArticleIDsRaw, err := updt.GetProperty("KBArticleIDs") if err != nil { return false, fmt.Errorf(`updt.GetProperty("KBArticleIDs"): %v`, err) } defer kbArticleIDsRaw.Clear() kbArticleIDs := kbArticleIDsRaw.ToIDispatch() defer kbArticleIDs.Release() kbArticleIDsCount, err := packages.GetCount(kbArticleIDs) if err != nil { return false, err } if len(exclusive_patches) > 0 { for i := 0; i < int(kbArticleIDsCount); i++ { kbRaw, err := kbArticleIDs.GetProperty("Item", i) if err != nil { return false, err } defer kbRaw.Clear() for _, e := range exclusive_patches { if e == kbRaw.ToString() { // until now we have only seen at most 1 kbarticles // in a patch update. So, if we get a match, we just // install the update return true, nil } } } // since there are exclusive_patches to be installed, // other fields like excludes, classfilter are void return false, nil } if len(kbExcludes) > 0 { for i := 0; i < int(kbArticleIDsCount); i++ { kbRaw, err := kbArticleIDs.GetProperty("Item", i) if err != nil { return false, err } defer kbRaw.Clear() for _, e := range kbExcludes { // kbArticleIDs is just the IDs, but users are used to using the KB prefix. if strings.TrimLeft(e, "KkBb") == kbRaw.ToString() { clog.Debugf(ctx, "Update %q (%s) matched exclude filter", title.ToString(), kbRaw.ToString()) return false, nil } } } } if len(classFilter) == 0 { return true, nil } categoriesRaw, err := updt.GetProperty("Categories") if err != nil { return false, fmt.Errorf(`updt.GetProperty("Categories"): %v`, err) } defer categoriesRaw.Clear() categories := categoriesRaw.ToIDispatch() defer categories.Release() categoriesCount, err := packages.GetCount(categories) if err != nil { return false, err } for i := 0; i < int(categoriesCount); i++ { catRaw, err := categories.GetProperty("Item", i) if err != nil { return false, fmt.Errorf(`categories.GetProperty("Item", i): %v`, err) } defer catRaw.Clear() cat := catRaw.ToIDispatch() defer cat.Release() catIdRaw, err := cat.GetProperty("CategoryID") if err != nil { return false, fmt.Errorf(`cat.GetProperty("CategoryID"): %v`, err) } defer catIdRaw.Clear() for _, c := range classFilter { if c == catIdRaw.ToString() { return true, nil } } } clog.Debugf(ctx, "Update %q not found in classification filter", title.ToString()) return false, nil } // GetWUAUpdates gets WUA updates based on optional classFilter and kbExcludes. func GetWUAUpdates(ctx context.Context, session *packages.IUpdateSession, classFilter, kbExcludes, exclusivePatches []string) (*packages.IUpdateCollection, error) { // Search for all not installed updates but filter out ones that will be installed after a reboot. filter := "IsInstalled=0 AND RebootRequired=0" clog.Debugf(ctx, "Searching for WUA updates with query %q", filter) updts, err := session.GetWUAUpdateCollection(filter) if err != nil { return nil, fmt.Errorf("GetWUAUpdateCollection error: %v", err) } if len(classFilter) == 0 && len(kbExcludes) == 0 { return updts, nil } defer updts.Release() count, err := updts.Count() if err != nil { return nil, err } clog.Debugf(ctx, "Found %d total updates avaiable (pre filter).", count) newUpdts, err := packages.NewUpdateCollection() if err != nil { return nil, err } clog.Debugf(ctx, "Using filters: Excludes: %q, Classifications: %q, ExclusivePatches: %q", kbExcludes, classFilter, exclusivePatches) for i := 0; i < int(count); i++ { updt, err := updts.Item(i) if err != nil { return nil, err } ok, err := checkFilters(ctx, updt, kbExcludes, classFilter, exclusivePatches) if err != nil { return nil, err } if !ok { continue } if err := newUpdts.Add(updt); err != nil { return nil, err } } return newUpdts, nil } osconfig-20210219.00/ospatch/yum_update.go000066400000000000000000000063521401404514100201450ustar00rootroot00000000000000// Copyright 2019 Google 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 ospatch import ( "context" "fmt" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" ) const yum = "/usr/bin/yum" var ( yumUpdateArgs = []string{"update", "-y"} yumUpdateMinimalArgs = []string{"update-minimal", "-y"} ) type yumUpdateOpts struct { security bool minimal bool exclusivePackages []string excludes []string dryrun bool } // YumUpdateOption is an option for yum update. type YumUpdateOption func(*yumUpdateOpts) // YumUpdateSecurity returns a YumUpdateOption that specifies the --security flag should // be used. func YumUpdateSecurity(security bool) YumUpdateOption { return func(args *yumUpdateOpts) { args.security = security } } // YumUpdateMinimal returns a YumUpdateOption that specifies the update-minimal // command should be used. func YumUpdateMinimal(minimal bool) YumUpdateOption { return func(args *yumUpdateOpts) { args.minimal = minimal } } // YumUpdateExcludes returns a YumUpdateOption that specifies what packages to add to // the --exclude flag. func YumUpdateExcludes(excludes []string) YumUpdateOption { return func(args *yumUpdateOpts) { args.excludes = excludes } } // YumExclusivePackages includes only these packages in the upgrade. func YumExclusivePackages(exclusivePackages []string) YumUpdateOption { return func(args *yumUpdateOpts) { args.exclusivePackages = exclusivePackages } } // YumDryRun performs a dry run. func YumDryRun(dryrun bool) YumUpdateOption { return func(args *yumUpdateOpts) { args.dryrun = dryrun } } // RunYumUpdate runs yum update. func RunYumUpdate(ctx context.Context, opts ...YumUpdateOption) error { yumOpts := &yumUpdateOpts{ security: false, minimal: false, dryrun: false, } for _, opt := range opts { opt(yumOpts) } pkgs, err := packages.YumUpdates(ctx, packages.YumUpdateMinimal(yumOpts.minimal), packages.YumUpdateSecurity(yumOpts.security), packages.YumExcludes(yumOpts.excludes)) if err != nil { return err } // Yum excludes are already excluded while listing yumUpdates, so we send // and empty list. fPkgs, err := filterPackages(pkgs, yumOpts.exclusivePackages, []string{}) if err != nil { return err } if len(fPkgs) == 0 { clog.Infof(ctx, "No packages to update.") return nil } var pkgNames []string for _, pkg := range fPkgs { pkgNames = append(pkgNames, pkg.Name) } msg := fmt.Sprintf("%d packages: %s", len(pkgNames), fPkgs) if yumOpts.dryrun { clog.Infof(ctx, "Running in dryrun mode, not updating %s", msg) return nil } clog.Infof(ctx, "Updating %s", msg) return packages.InstallYumPackages(ctx, pkgNames) } osconfig-20210219.00/ospatch/yum_update_test.go000066400000000000000000000127221401404514100212020ustar00rootroot00000000000000// Copyright 2020 Google 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 ospatch import ( "context" "os" "os/exec" "testing" "github.com/GoogleCloudPlatform/osconfig/packages" utilmocks "github.com/GoogleCloudPlatform/osconfig/util/mocks" "github.com/golang/mock/gomock" ) func TestRunYumUpdateWithSecurity(t *testing.T) { data := []byte(` ================================================================================================================================================================================= Package Arch Version Repository Size ================================================================================================================================================================================= Upgrading: foo noarch 2.0.0-1 BaseOS 361 k blah `) ctx := context.Background() if os.Getenv("EXIT100") == "1" { os.Exit(100) } cmd := exec.Command(os.Args[0], "-test.run=TestRunYumUpdateWithSecurity") cmd.Env = append(os.Environ(), "EXIT100=1") err := cmd.Run() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) packages.SetCommandRunner(mockCommandRunner) checkUpdateCall := mockCommandRunner.EXPECT().Run(ctx, exec.Command("/usr/bin/yum", []string{"check-update", "--assumeyes"}...)).Return([]byte("stdout"), []byte("stderr"), err).Times(1) // yum install call to install package mockCommandRunner.EXPECT().Run(ctx, exec.Command("/usr/bin/yum", []string{"install", "--assumeyes", "foo"}...)).After(checkUpdateCall).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) packages.SetPtyCommandRunner(mockCommandRunner) mockCommandRunner.EXPECT().Run(ctx, exec.Command("/usr/bin/yum", []string{"update", "--assumeno", "--cacheonly", "--color=never", "--security"}...)).Return(data, []byte("stderr"), nil).Times(1) err = RunYumUpdate(ctx, YumUpdateMinimal(false), YumUpdateSecurity(true)) if err != nil { t.Errorf("did not expect error: %+v", err) } } func TestRunYumUpdateWithSecurityWithExclusives(t *testing.T) { data := []byte(` ================================================================================================================================================================================= Package Arch Version Repository Size ================================================================================================================================================================================= Installing: kernel x86_64 2.6.32-754.24.3.el6 updates 32 M replacing kernel.x86_64 1.0.0-4 Upgrading: foo noarch 2.0.0-1 BaseOS 361 k bar x86_64 2.0.0-1 repo 10 M Obsoleting: baz noarch 2.0.0-1 repo 10 M `) ctx := context.Background() exclusivePackages := []string{"foo", "bar"} if os.Getenv("EXIT100") == "1" { os.Exit(100) } cmd := exec.Command(os.Args[0], "-test.run=TestRunYumUpdateWithSecurityWithExclusives") cmd.Env = append(os.Environ(), "EXIT100=1") err := cmd.Run() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) packages.SetCommandRunner(mockCommandRunner) checkUpdateCall := mockCommandRunner.EXPECT().Run(ctx, exec.Command("/usr/bin/yum", []string{"check-update", "--assumeyes"}...)).Return([]byte("stdout"), []byte("stderr"), err).Times(1) // yum install call to install package, make sure only 2 packages are installed. mockCommandRunner.EXPECT().Run(ctx, exec.Command("/usr/bin/yum", []string{"install", "--assumeyes", "foo", "bar"}...)).After(checkUpdateCall).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) packages.SetPtyCommandRunner(mockCommandRunner) mockCommandRunner.EXPECT().Run(ctx, exec.Command("/usr/bin/yum", []string{"update", "--assumeno", "--cacheonly", "--color=never", "--security"}...)).Return(data, []byte("stderr"), nil).Times(1) err = RunYumUpdate(ctx, YumUpdateMinimal(false), YumUpdateSecurity(true), YumExclusivePackages(exclusivePackages)) if err != nil { t.Errorf("did not expect error: %+v", err) } } osconfig-20210219.00/ospatch/zypper_patch.go000066400000000000000000000137371401404514100205060ustar00rootroot00000000000000// Copyright 2019 Google 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 ospatch import ( "context" "fmt" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" ) const zypper = "/usr/bin/zypper" var ( zypperPatchArgs = []string{"patch", "-y"} ) type zypperPatchOpts struct { categories []string severities []string withOptional bool withUpdate bool excludes []string exclusivePatches []string dryrun bool } // ZypperPatchOption is an option for zypper patch. type ZypperPatchOption func(*zypperPatchOpts) // ZypperPatchCategories returns a ZypperUpdateOption that specifies what // categories to add to the --categories flag. func ZypperPatchCategories(categories []string) ZypperPatchOption { return func(args *zypperPatchOpts) { args.categories = categories } } // ZypperPatchSeverities returns a ZypperUpdateOption that specifies what // categories to add to the --categories flag. func ZypperPatchSeverities(severities []string) ZypperPatchOption { return func(args *zypperPatchOpts) { args.severities = severities } } // ZypperUpdateWithOptional returns a ZypperUpdateOption that specifies the // --with-optional flag should be used. func ZypperUpdateWithOptional(withOptional bool) ZypperPatchOption { return func(args *zypperPatchOpts) { args.withOptional = withOptional } } // ZypperUpdateWithUpdate returns a ZypperUpdateOption that specifies the // --with-update flag should be used. func ZypperUpdateWithUpdate(withUpdate bool) ZypperPatchOption { return func(args *zypperPatchOpts) { args.withUpdate = withUpdate } } // ZypperUpdateWithExcludes returns a ZypperUpdateOption that specifies // list of packages to be excluded from update func ZypperUpdateWithExcludes(excludes []string) ZypperPatchOption { return func(args *zypperPatchOpts) { args.excludes = excludes } } // ZypperUpdateWithExclusivePatches returns a ZypperUpdateOption that specifies // list of exclusive packages to be updated func ZypperUpdateWithExclusivePatches(exclusivePatches []string) ZypperPatchOption { return func(args *zypperPatchOpts) { args.exclusivePatches = exclusivePatches } } // ZypperUpdateDryrun returns a ZypperUpdateOption that specifies the runner. func ZypperUpdateDryrun(dryrun bool) ZypperPatchOption { return func(args *zypperPatchOpts) { args.dryrun = dryrun } } // RunZypperPatch runs zypper patch. func RunZypperPatch(ctx context.Context, opts ...ZypperPatchOption) error { zOpts := &zypperPatchOpts{ excludes: nil, exclusivePatches: nil, categories: nil, severities: nil, withOptional: false, withUpdate: false, } for _, opt := range opts { opt(zOpts) } zListOpts := []packages.ZypperListOption{ packages.ZypperListPatchCategories(zOpts.categories), packages.ZypperListPatchSeverities(zOpts.severities), packages.ZypperListPatchWithOptional(zOpts.withOptional), // if there is no filter on category and severity, // zypper fetches all available patch updates } patches, err := packages.ZypperPatches(ctx, zListOpts...) if err != nil { return err } // if user specifies, --with-update get the necessary patch/package // information and then runfilter on them var pkgToPatchesMap map[string][]string var pkgUpdates []packages.PkgInfo if zOpts.withUpdate { pkgUpdates, err = packages.ZypperUpdates(ctx) if err != nil { return nil } pkgToPatchesMap, err = packages.ZypperPackagesInPatch(ctx, patches) if err != nil { return nil } } fPatches, fpkgs, err := runFilter(patches, zOpts.exclusivePatches, zOpts.excludes, pkgUpdates, pkgToPatchesMap, zOpts.withUpdate) if len(fPatches) == 0 && len(fpkgs) == 0 { clog.Infof(ctx, "No updates required.") return nil } if len(fPatches) == 0 { clog.Infof(ctx, "No patches to install.") } else { msg := fmt.Sprintf("%d patches: %s", len(fPatches), fPatches) if zOpts.dryrun { clog.Infof(ctx, "Running in dryrun mode, not installing %s", msg) } else { clog.Infof(ctx, "Installing %s", msg) } } if len(fpkgs) == 0 { clog.Infof(ctx, "No non-patch packages to update.") } else { msg := fmt.Sprintf("%d patches: %s", len(fpkgs), fpkgs) if zOpts.dryrun { clog.Infof(ctx, "Running in dryrun mode, not Updating %s", msg) } else { clog.Infof(ctx, "Updating %s", msg) } } if zOpts.dryrun { return nil } return packages.ZypperInstall(ctx, fPatches, fpkgs) } func runFilter(patches []packages.ZypperPatch, exclusivePatches, excludes []string, pkgUpdates []packages.PkgInfo, pkgToPatchesMap map[string][]string, withUpdate bool) ([]packages.ZypperPatch, []packages.PkgInfo, error) { // exclusive patches var fPatches []packages.ZypperPatch var fPkgs []packages.PkgInfo if len(exclusivePatches) > 0 { for _, patch := range patches { if containsString(exclusivePatches, patch.Name) { fPatches = append(fPatches, patch) } } return fPatches, fPkgs, nil } // if --with-update is specified, filter out the packages // that will be updated as a part of a patch update if withUpdate { for _, pkg := range pkgUpdates { if _, ok := pkgToPatchesMap[pkg.Name]; !ok { fPkgs = append(fPkgs, pkg) } } } // we have the list of patches which is already filtered // as per the configurations provided by user; // we remove the excluded patches from the list for _, patch := range patches { if !containsString(excludes, patch.Name) { fPatches = append(fPatches, patch) } } return fPatches, fPkgs, nil } osconfig-20210219.00/ospatch/zypper_patch_test.go000066400000000000000000000121201401404514100215260ustar00rootroot00000000000000package ospatch import ( "strings" "testing" "github.com/GoogleCloudPlatform/osconfig/packages" ) func TestRunFilter(t *testing.T) { patches, pkgUpdates, pkgToPatchesMap := prepareTestCase() type input struct { patches []packages.ZypperPatch pkgUpdates []packages.PkgInfo pkgToPatchesMap map[string][]string exclusiveIncludes []string excludes []string withUpdate bool } type expect struct { patches []string pkgUpdates []string err error } tests := []struct { name string input input expect expect }{ {name: "runfilterwithexclusivepatches", input: input{patches: patches, pkgUpdates: pkgUpdates, pkgToPatchesMap: pkgToPatchesMap, exclusiveIncludes: []string{"patch-3"}, excludes: []string{}, withUpdate: false}, expect: expect{patches: []string{"patch-3"}, pkgUpdates: []string{}, err: nil}, }, {name: "runFilterwithUpdatewithexcludes", // withupdate, exclude a patch that has input: input{patches: patches, pkgUpdates: pkgUpdates, pkgToPatchesMap: pkgToPatchesMap, exclusiveIncludes: []string{}, excludes: []string{"patch-3"}, withUpdate: true}, expect: expect{patches: []string{"patch-1", "patch-2"}, pkgUpdates: []string{"pkg6"}, err: nil}, }, {name: "runFilterwithoutUpdatewithexcludes", input: input{patches: patches, pkgUpdates: pkgUpdates, pkgToPatchesMap: pkgToPatchesMap, exclusiveIncludes: []string{}, excludes: []string{"patch-3"}, withUpdate: false}, expect: expect{patches: []string{"patch-1", "patch-2"}, pkgUpdates: []string{}, err: nil}, }, {name: "runFilterwithUpdatewithoutexcludes", input: input{patches: patches, pkgUpdates: pkgUpdates, pkgToPatchesMap: pkgToPatchesMap, exclusiveIncludes: []string{}, excludes: []string{}, withUpdate: true}, expect: expect{patches: []string{"patch-1", "patch-2", "patch-3"}, pkgUpdates: []string{"pkg6"}, err: nil}, }, {name: "runFilterwithoutUpdatewithoutexcludes", input: input{patches: patches, pkgUpdates: pkgUpdates, pkgToPatchesMap: pkgToPatchesMap, exclusiveIncludes: []string{}, excludes: []string{}, withUpdate: false}, expect: expect{patches: []string{"patch-1", "patch-2", "patch-3"}, pkgUpdates: []string{}, err: nil}, }, } for _, tc := range tests { fPatches, fpkgs, err := runFilter(tc.input.patches, tc.input.exclusiveIncludes, tc.input.excludes, tc.input.pkgUpdates, tc.input.pkgToPatchesMap, tc.input.withUpdate) if err != nil { t.Errorf("[%s] unexpected error: got(%+v)", tc.name, err) continue } if len(fPatches) != len(tc.expect.patches) { t.Errorf("[%s] unexpected number of patches: expected(%d), got(%d)", tc.name, len(tc.expect.patches), len(fPatches)) } for _, p := range fPatches { if !isIn(p.Name, tc.expect.patches) { t.Errorf("[%s] unexpected patch name: (%s)! is not in %+v", tc.name, p.Name, tc.expect.patches) } } if len(fpkgs) != len(tc.expect.pkgUpdates) { t.Errorf("[%s] unexpected number of packages: expected(%d), got(%d)", tc.name, len(tc.expect.pkgUpdates), len(fpkgs)) } for _, p := range fpkgs { if !isIn(p.Name, tc.expect.pkgUpdates) { t.Errorf("[%s] unexpected package name: (%s)! is not in %+v", tc.name, p.Name, tc.expect.pkgUpdates) } } } } func isIn(needle string, haystack []string) bool { for _, hay := range haystack { if strings.Compare(hay, needle) == 0 { return true } } return false } func prepareTestCase() ([]packages.ZypperPatch, []packages.PkgInfo, map[string][]string) { var patches []packages.ZypperPatch var pkgUpdates []packages.PkgInfo var pkgToPatchesMap map[string][]string patches = append(patches, packages.ZypperPatch{ Name: "patch-1", Category: "recommended", Severity: "important", Summary: "patch-1", }) patches = append(patches, packages.ZypperPatch{ Name: "patch-2", Category: "security", Severity: "critical", Summary: "patch-2", }) patches = append(patches, packages.ZypperPatch{ Name: "patch-3", Category: "optional", Severity: "low", Summary: "patch-3", }) pkgUpdates = append(pkgUpdates, packages.PkgInfo{ Name: "pkg1", Arch: "noarch", Version: "1.1.1", }) pkgUpdates = append(pkgUpdates, packages.PkgInfo{ Name: "pkg2", Arch: "noarch", Version: "1.1.1", }) pkgUpdates = append(pkgUpdates, packages.PkgInfo{ Name: "pkg3", Arch: "noarch", Version: "1.1.1", }) pkgUpdates = append(pkgUpdates, packages.PkgInfo{ Name: "pkg4", Arch: "noarch", Version: "1.1.1", }) pkgUpdates = append(pkgUpdates, packages.PkgInfo{ Name: "pkg5", Arch: "noarch", Version: "1.1.1", }) // individual package update that is not a part // of a patch. this package only shows up // if user specifies --with-update pkgUpdates = append(pkgUpdates, packages.PkgInfo{ Name: "pkg6", Arch: "noarch", Version: "1.1.1", }) pkgToPatchesMap = make(map[string][]string) pkgToPatchesMap["pkg1"] = []string{"patch-1"} pkgToPatchesMap["pkg2"] = []string{"patch-1"} pkgToPatchesMap["pkg3"] = []string{"patch-2"} pkgToPatchesMap["pkg4"] = []string{"patch-2"} pkgToPatchesMap["pkg5"] = []string{"patch-3"} return patches, pkgUpdates, pkgToPatchesMap } osconfig-20210219.00/packages/000077500000000000000000000000001401404514100155515ustar00rootroot00000000000000osconfig-20210219.00/packages/apt_deb.go000066400000000000000000000244271401404514100175070ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "bytes" "context" "fmt" "os" "os/exec" "runtime" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/osinfo" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( dpkg string dpkgQuery string dpkgDeb string aptGet string dpkgInstallArgs = []string{"--install"} dpkgQueryArgs = []string{"-W", "-f", "${Package} ${Architecture} ${Version}\n"} dpkgRepairArgs = []string{"--configure", "-a"} aptGetInstallArgs = []string{"install", "-y"} aptGetRemoveArgs = []string{"remove", "-y"} aptGetUpdateArgs = []string{"update"} aptGetUpgradeCmd = "upgrade" aptGetFullUpgradeCmd = "full-upgrade" aptGetDistUpgradeCmd = "dist-upgrade" aptGetUpgradableArgs = []string{"--just-print", "-qq"} allowDowngradesArg = "--allow-downgrades" dpkgErr = []byte("dpkg --configure -a") ) func init() { if runtime.GOOS != "windows" { dpkg = "/usr/bin/dpkg" dpkgQuery = "/usr/bin/dpkg-query" dpkgDeb = "/usr/bin/dpkg-deb" aptGet = "/usr/bin/apt-get" } AptExists = util.Exists(aptGet) DpkgExists = util.Exists(dpkg) DpkgQueryExists = util.Exists(dpkgQuery) } // AptUpgradeType is the apt upgrade type. type AptUpgradeType int const ( // AptGetUpgrade specifies apt-get upgrade should be run. AptGetUpgrade AptUpgradeType = iota // AptGetDistUpgrade specifies apt-get dist-upgrade should be run. AptGetDistUpgrade // AptGetFullUpgrade specifies apt-get full-upgrade should be run. AptGetFullUpgrade ) type aptGetUpgradeOpts struct { upgradeType AptUpgradeType showNew bool allowDowngrades bool } // AptGetUpgradeOption is an option for apt-get upgrade. type AptGetUpgradeOption func(*aptGetUpgradeOpts) // AptGetUpgradeType returns a AptGetUpgradeOption that specifies upgrade type. func AptGetUpgradeType(upgradeType AptUpgradeType) AptGetUpgradeOption { return func(args *aptGetUpgradeOpts) { args.upgradeType = upgradeType } } // AptGetUpgradeShowNew returns a AptGetUpgradeOption that indicates whether 'new' packages should be returned. func AptGetUpgradeShowNew(showNew bool) AptGetUpgradeOption { return func(args *aptGetUpgradeOpts) { args.showNew = showNew } } // AptGetUpgradeAllowDowngrades returns a AptGetUpgradeOption that specifies AllowDowngrades. func AptGetUpgradeAllowDowngrades(allowDowngrades bool) AptGetUpgradeOption { return func(args *aptGetUpgradeOpts) { args.allowDowngrades = allowDowngrades } } func dpkgRepair(ctx context.Context, out []byte) bool { // Error code 100 may occur for non repairable errors, just check the output. if !bytes.Contains(out, dpkgErr) { return false } clog.Debugf(ctx, "apt-get error, attempting dpkg repair.") // Ignore error here, just log and rerun apt-get. run(ctx, dpkg, dpkgRepairArgs) return true } func parseDpkgDeb(data []byte) (*PkgInfo, error) { /* new Debian package, version 2.0. size 6731954 bytes: control archive=2138 bytes. 498 bytes, 12 lines control 3465 bytes, 31 lines md5sums 2793 bytes, 65 lines * postinst #!/bin/sh 938 bytes, 28 lines * postrm #!/bin/sh 216 bytes, 7 lines * prerm #!/bin/sh Package: google-guest-agent Version: 1:1dummy-g1 Architecture: amd64 Maintainer: Google Cloud Team Installed-Size: 23279 Depends: init-system-helpers (>= 1.18~) Conflicts: python-google-compute-engine, python3-google-compute-engine Section: misc Priority: optional Description: Google Compute Engine Guest Agent Contains the guest agent and metadata script runner binaries. Git: https://github.com/GoogleCloudPlatform/guest-agent/tree/c3d526e650c4e45ae3258c07836fd72f85fd9fc8 */ lines := bytes.Split(bytes.TrimSpace(data), []byte("\n")) info := &PkgInfo{} for _, ln := range lines { if info.Name != "" && info.Version != "" && info.Arch != "" { break } fields := bytes.Fields(ln) if len(fields) != 2 { continue } if bytes.Contains(fields[0], []byte("Package:")) { info.Name = string(fields[1]) continue } if bytes.Contains(fields[0], []byte("Version:")) { info.Version = string(fields[1]) continue } if bytes.Contains(fields[0], []byte("Architecture:")) { info.Arch = osinfo.Architecture(string(fields[1])) continue } } if info.Name == "" || info.Version == "" || info.Arch == "" { return nil, fmt.Errorf("could not parse dpkg-deb output: %q", data) } return info, nil } // DebPkgInfo gets PkgInfo from a deb package. func DebPkgInfo(ctx context.Context, path string) (*PkgInfo, error) { out, err := run(ctx, dpkgDeb, []string{"-I", path}) if err != nil { return nil, err } return parseDpkgDeb(out) } // InstallAptPackages installs apt packages. func InstallAptPackages(ctx context.Context, pkgs []string) error { args := append(aptGetInstallArgs, pkgs...) install := exec.Command(aptGet, args...) install.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive", ) stdout, stderr, err := runner.Run(ctx, install) if err != nil { if dpkgRepair(ctx, stderr) { stdout, stderr, err = runner.Run(ctx, install) } } if err != nil { err = fmt.Errorf("error running %s with args %q: %v, stdout: %q, stderr: %q", aptGet, args, err, stdout, stderr) } return err } // RemoveAptPackages removes apt packages. func RemoveAptPackages(ctx context.Context, pkgs []string) error { args := append(aptGetRemoveArgs, pkgs...) remove := exec.Command(aptGet, args...) remove.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive", ) stdout, stderr, err := runner.Run(ctx, remove) if err != nil { if dpkgRepair(ctx, stderr) { stdout, stderr, err = runner.Run(ctx, remove) } } if err != nil { err = fmt.Errorf("error running %s with args %q: %v, stdout: %q, stderr: %q", aptGet, args, err, stdout, stderr) } return err } func parseAptUpdates(ctx context.Context, data []byte, showNew bool) []PkgInfo { /* Inst libldap-common [2.4.45+dfsg-1ubuntu1.2] (2.4.45+dfsg-1ubuntu1.3 Ubuntu:18.04/bionic-updates, Ubuntu:18.04/bionic-security [all]) Inst firmware-linux-free (3.4 Debian:9.9/stable [all]) [] Inst google-cloud-sdk [245.0.0-0] (246.0.0-0 cloud-sdk-stretch:cloud-sdk-stretch [all]) Inst linux-image-4.9.0-9-amd64 (4.9.168-1+deb9u2 Debian-Security:9/stable [amd64]) Inst linux-image-amd64 [4.9+80+deb9u6] (4.9+80+deb9u7 Debian:9.9/stable [amd64]) Conf firmware-linux-free (3.4 Debian:9.9/stable [all]) Conf google-cloud-sdk (246.0.0-0 cloud-sdk-stretch:cloud-sdk-stretch [all]) Conf linux-image-4.9.0-9-amd64 (4.9.168-1+deb9u2 Debian-Security:9/stable [amd64]) Conf linux-image-amd64 (4.9+80+deb9u7 Debian:9.9/stable [amd64]) */ lines := bytes.Split(bytes.TrimSpace(data), []byte("\n")) var pkgs []PkgInfo for _, ln := range lines { pkg := bytes.Fields(ln) if len(pkg) < 5 || string(pkg[0]) != "Inst" { continue } // Inst google-cloud-sdk [245.0.0-0] (246.0.0-0 cloud-sdk-stretch:cloud-sdk-stretch [all]) pkg = pkg[1:] // ==> google-cloud-sdk [245.0.0-0] (246.0.0-0 cloud-sdk-stretch:cloud-sdk-stretch [all]) if bytes.HasPrefix(pkg[1], []byte("[")) { pkg = append(pkg[:1], pkg[2:]...) // ==> google-cloud-sdk (246.0.0-0 cloud-sdk-stretch:cloud-sdk-stretch [all]) } else if !showNew { // This is a newly installed package and not an upgrade, ignore if showNew is false. continue } // Drop trailing "[]" if they exist. if bytes.Contains(pkg[len(pkg)-1], []byte("[]")) { pkg = pkg[:len(pkg)-1] } if !bytes.HasPrefix(pkg[1], []byte("(")) || !bytes.HasSuffix(pkg[len(pkg)-1], []byte(")")) { continue } ver := bytes.Trim(pkg[1], "(") // (246.0.0-0 => 246.0.0-0 arch := bytes.Trim(pkg[len(pkg)-1], "[])") // [all]) => all pkgs = append(pkgs, PkgInfo{Name: string(pkg[0]), Arch: osinfo.Architecture(string(arch)), Version: string(ver)}) } return pkgs } // AptUpdates returns all the packages that will be installed when running // apt-get [dist-|full-]upgrade. func AptUpdates(ctx context.Context, opts ...AptGetUpgradeOption) ([]PkgInfo, error) { aptOpts := &aptGetUpgradeOpts{ upgradeType: AptGetUpgrade, showNew: false, allowDowngrades: false, } for _, opt := range opts { opt(aptOpts) } args := aptGetUpgradableArgs switch aptOpts.upgradeType { case AptGetUpgrade: args = append(aptGetUpgradableArgs, aptGetUpgradeCmd) case AptGetDistUpgrade: args = append(aptGetUpgradableArgs, aptGetDistUpgradeCmd) case AptGetFullUpgrade: args = append(aptGetUpgradableArgs, aptGetFullUpgradeCmd) default: return nil, fmt.Errorf("unknown upgrade type: %q", aptOpts.upgradeType) } if _, err := AptUpdate(ctx); err != nil { return nil, err } out, err := run(ctx, aptGet, args) if err != nil { return nil, err } return parseAptUpdates(ctx, out, aptOpts.showNew), nil } // AptUpdate runs apt-get update. func AptUpdate(ctx context.Context) ([]byte, error) { return run(ctx, aptGet, aptGetUpdateArgs) } func parseInstalledDebpackages(data []byte) []PkgInfo { /* foo amd64 1.2.3-4 bar noarch 1.2.3-4 ... */ lines := strings.Split(strings.TrimSpace(string(data)), "\n") var pkgs []PkgInfo for _, ln := range lines { pkg := strings.Fields(ln) if len(pkg) != 3 { continue } pkgs = append(pkgs, PkgInfo{Name: pkg[0], Arch: osinfo.Architecture(pkg[1]), Version: pkg[2]}) } return pkgs } // InstalledDebPackages queries for all installed deb packages. func InstalledDebPackages(ctx context.Context) ([]PkgInfo, error) { out, err := run(ctx, dpkgQuery, dpkgQueryArgs) if err != nil { return nil, err } return parseInstalledDebpackages(out), nil } // DpkgInstall installs a deb package. func DpkgInstall(ctx context.Context, path string) error { _, err := run(ctx, dpkg, append(dpkgInstallArgs, path)) return err } osconfig-20210219.00/packages/apt_deb_test.go000066400000000000000000000217211401404514100205400ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "errors" "os" "os/exec" "reflect" "testing" utilmocks "github.com/GoogleCloudPlatform/osconfig/util/mocks" "github.com/golang/mock/gomock" ) func TestInstallAptPackages(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(aptGet, append(aptGetInstallArgs, pkgs...)...) expectedCmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive", ) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) if err := InstallAptPackages(testCtx, pkgs); err != nil { t.Errorf("unexpected error: %v", err) } first := mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), dpkgErr, errors.New("error")).Times(1) repair := mockCommandRunner.EXPECT().Run(testCtx, exec.Command(dpkg, dpkgRepairArgs...)).After(first).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).After(repair).Return([]byte("stdout"), []byte("stderr"), errors.New("error")).Times(1) if err := InstallAptPackages(testCtx, pkgs); err == nil { t.Errorf("did not get expected error") } } func TestRemoveAptPackages(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(aptGet, append(aptGetRemoveArgs, pkgs...)...) expectedCmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive", ) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) if err := RemoveAptPackages(testCtx, pkgs); err != nil { t.Errorf("unexpected error: %v", err) } first := mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), dpkgErr, errors.New("error")).Times(1) repair := mockCommandRunner.EXPECT().Run(testCtx, exec.Command(dpkg, dpkgRepairArgs...)).After(first).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).After(repair).Return([]byte("stdout"), []byte("stderr"), errors.New("error")).Times(1) if err := RemoveAptPackages(testCtx, pkgs); err == nil { t.Errorf("did not get expected error") } } func TestInstalledDebPackages(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(dpkgQuery, dpkgQueryArgs...) data := []byte("foo amd64 1.2.3-4") mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return(data, []byte("stderr"), nil).Times(1) ret, err := InstalledDebPackages(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } want := []PkgInfo{{"foo", "x86_64", "1.2.3-4"}} if !reflect.DeepEqual(ret, want) { t.Errorf("InstalledDebPackages() = %v, want %v", ret, want) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return(data, []byte("stderr"), errors.New("error")).Times(1) if _, err := InstalledDebPackages(testCtx); err == nil { t.Errorf("did not get expected error") } } func TestParseInstalledDebpackages(t *testing.T) { tests := []struct { name string data []byte want []PkgInfo }{ {"NormalCase", []byte("foo amd64 1.2.3-4\nbar noarch 1.2.3-4"), []PkgInfo{{"foo", "x86_64", "1.2.3-4"}, {"bar", "all", "1.2.3-4"}}}, {"NoPackages", []byte("nothing here"), nil}, {"nil", nil, nil}, {"UnrecognizedPackage", []byte("something we dont understand\n bar noarch 1.2.3-4"), []PkgInfo{{"bar", "all", "1.2.3-4"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parseInstalledDebpackages(tt.data); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseInstalledDebpackages() = %v, want %v", got, tt.want) } }) } } func TestParseAptUpdates(t *testing.T) { normalCase := ` Inst libldap-common [2.4.45+dfsg-1ubuntu1.2] (2.4.45+dfsg-1ubuntu1.3 Ubuntu:18.04/bionic-updates, Ubuntu:18.04/bionic-security [all]) Inst google-cloud-sdk [245.0.0-0] (246.0.0-0 cloud-sdk-stretch:cloud-sdk-stretch [amd64]) [] Inst firmware-linux-free (3.4 Debian:9.9/stable [all]) Conf firmware-linux-free (3.4 Debian:9.9/stable [all]) ` tests := []struct { name string data []byte showNew bool want []PkgInfo }{ {"NormalCase", []byte(normalCase), false, []PkgInfo{{"libldap-common", "all", "2.4.45+dfsg-1ubuntu1.3"}, {"google-cloud-sdk", "x86_64", "246.0.0-0"}}}, {"NormalCaseShowNew", []byte(normalCase), true, []PkgInfo{{"libldap-common", "all", "2.4.45+dfsg-1ubuntu1.3"}, {"google-cloud-sdk", "x86_64", "246.0.0-0"}, {"firmware-linux-free", "all", "3.4"}}}, {"NoPackages", []byte("nothing here"), false, nil}, {"nil", nil, false, nil}, {"UnrecognizedPackage", []byte("Inst something [we dont understand\n Inst google-cloud-sdk [245.0.0-0] (246.0.0-0 cloud-sdk-stretch:cloud-sdk-stretch [amd64])"), false, []PkgInfo{{"google-cloud-sdk", "x86_64", "246.0.0-0"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parseAptUpdates(testCtx, tt.data, tt.showNew); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseAptUpdates() = %v, want %v", got, tt.want) } }) } } func TestAptUpdates(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner updateCmd := exec.Command(aptGet, aptGetUpdateArgs...) expectedCmd := exec.Command(aptGet, append(aptGetUpgradableArgs, aptGetUpgradeCmd)...) data := []byte("Inst google-cloud-sdk [245.0.0-0] (246.0.0-0 cloud-sdk-stretch:cloud-sdk-stretch [amd64])") first := mockCommandRunner.EXPECT().Run(testCtx, updateCmd).Return(data, []byte("stderr"), nil).Times(1) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).After(first).Return(data, []byte("stderr"), nil).Times(1) ret, err := AptUpdates(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } want := []PkgInfo{{"google-cloud-sdk", "x86_64", "246.0.0-0"}} if !reflect.DeepEqual(ret, want) { t.Errorf("AptUpdates() = %v, want %v", ret, want) } first = mockCommandRunner.EXPECT().Run(testCtx, updateCmd).Return(data, []byte("stderr"), nil).Times(1) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).After(first).Return(data, []byte("stderr"), errors.New("error")).Times(1) if _, err := AptUpdates(testCtx); err == nil { t.Errorf("did not get expected error") } } func TestDebPkgInfo(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner testPkg := "test.deb" expectedCmd := exec.Command(dpkgDeb, "-I", testPkg) out := []byte(`new Debian package, version 2.0. size 6731954 bytes: control archive=2138 bytes. 498 bytes, 12 lines control 3465 bytes, 31 lines md5sums 2793 bytes, 65 lines * postinst #!/bin/sh 938 bytes, 28 lines * postrm #!/bin/sh 216 bytes, 7 lines * prerm #!/bin/sh Package: google-guest-agent Version: 1:1dummy-g1 Architecture: amd64 Maintainer: Google Cloud Team Installed-Size: 23279 Depends: init-system-helpers (>= 1.18~) Conflicts: python-google-compute-engine, python3-google-compute-engine Section: misc Priority: optional Description: Google Compute Engine Guest Agent Contains the guest agent and metadata script runner binaries. Git: https://github.com/GoogleCloudPlatform/guest-agent/tree/c3d526e650c4e45ae3258c07836fd72f85fd9fc8`) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return(out, []byte("stderr"), nil).Times(1) ret, err := DebPkgInfo(testCtx, testPkg) if err != nil { t.Errorf("unexpected error: %v", err) } want := &PkgInfo{"google-guest-agent", "x86_64", "1:1dummy-g1"} if !reflect.DeepEqual(ret, want) { t.Errorf("DebPkgInfo() = %+v, want %+v", ret, want) } // Error output. mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("bad error")).Times(1) if _, err := DebPkgInfo(testCtx, testPkg); err == nil { t.Errorf("did not get expected error") } // No package mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte(""), []byte("stderr"), nil).Times(1) if _, err := DebPkgInfo(testCtx, testPkg); err == nil { t.Errorf("did not get expected error") } } osconfig-20210219.00/packages/cos.go000066400000000000000000000037671401404514100167010ustar00rootroot00000000000000// Copyright 2020 Google 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. // Only build for linux but not on unsupported architectures. // +build linux // +build 386 amd64 package packages import ( "fmt" "strconv" "cos.googlesource.com/cos/tools.git/src/pkg/cos" "github.com/GoogleCloudPlatform/osconfig/osinfo" ) func init() { COSPkgInfoExists = cos.PackageInfoExists() } var readMachineArch = func() (string, error) { oi, err := osinfo.Get() if err != nil { return "", fmt.Errorf("error getting osinfo: %v", err) } return oi.Architecture, nil } func parseInstalledCOSPackages(cosPkgInfo cos.PackageInfo) ([]PkgInfo, error) { arch, err := readMachineArch() if err != nil { return nil, fmt.Errorf("error from readMachineArch: %v", err) } var pkgs []PkgInfo for _, pkg := range cosPkgInfo.InstalledPackages { name := pkg.Category + "/" + pkg.Name version := pkg.Version if pkg.Revision != 0 { version += "-r" + strconv.Itoa(pkg.Revision) } pkgs = append(pkgs, PkgInfo{Name: name, Arch: arch, Version: version}) } return pkgs, nil } var readCOSPackageInfo = func() (cos.PackageInfo, error) { return cos.GetPackageInfo() } // InstalledCOSPackages queries for all installed COS packages. func InstalledCOSPackages() ([]PkgInfo, error) { packageInfo, err := readCOSPackageInfo() if err != nil { return nil, fmt.Errorf("error reading COS package list with args: %v, contents: %v", err, packageInfo) } return parseInstalledCOSPackages(packageInfo) } osconfig-20210219.00/packages/cos_stub.go000066400000000000000000000014731401404514100177260ustar00rootroot00000000000000// Copyright 2021 Google 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. // Stub for linux builds. // +build linux,!386,!amd64 package packages // InstalledCOSPackages is a stub for unsupported architectures. func InstalledCOSPackages() ([]PkgInfo, error) { return nil, nil } osconfig-20210219.00/packages/cos_test.go000066400000000000000000000147001401404514100177250ustar00rootroot00000000000000// Copyright 2020 Google 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. // +build linux // +build 386 amd64 package packages import ( "errors" "io/ioutil" "os" "reflect" "testing" "cos.googlesource.com/cos/tools.git/src/pkg/cos" ) func TestParseInstalledCOSPackages(t *testing.T) { readMachineArch = func() (string, error) { return "", errors.New("failed to obtain machine architecture") } if _, err := parseInstalledCOSPackages(cos.PackageInfo{}); err == nil { t.Errorf("did not get expected error") } readMachineArch = func() (string, error) { return "x86_64", nil } pkg0 := cos.Package{Category: "dev-util", Name: "foo-x", Version: "1.2.3", Revision: 4} expect0 := PkgInfo{"dev-util/foo-x", "x86_64", "1.2.3-r4"} pkg1 := cos.Package{Category: "app-admin", Name: "bar", Version: "0.1", Revision: 0} expect1 := PkgInfo{"app-admin/bar", "x86_64", "0.1"} pkgInfo := cos.PackageInfo{InstalledPackages: []cos.Package{pkg0, pkg1}} parsed, err := parseInstalledCOSPackages(pkgInfo) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(parsed[0], expect0) { t.Errorf("parseInstalledCOSPackages pkg0: %v, want: %v", parsed[0], expect0) } if !reflect.DeepEqual(parsed[1], expect1) { t.Errorf("parseInstalledCOSPackages pkg1: %v, want: %v", parsed[1], expect1) } } func TestInstalledCOSPackages(t *testing.T) { testDataJSON := `{ "installedPackages": [ { "category": "app-arch", "name": "gzip", "version": "1.9" }, { "category": "dev-libs", "name": "popt", "version": "1.16", "revision": "2" }, { "category": "app-emulation", "name": "docker-credential-helpers", "version": "0.6.3", "revision": "1" }, { "category": "_not.real-category1+", "name": "_not-real_package1", "version": "12.34.56.78" }, { "category": "_not.real-category1+", "name": "_not-real_package2", "version": "12.34.56.78", "revision": "26" }, { "category": "_not.real-category1+", "name": "_not-real_package3", "version": "12.34.56.78_rc3" }, { "category": "_not.real-category1+", "name": "_not-real_package4", "version": "12.34.56.78_rc3", "revision": "26" }, { "category": "_not.real-category1+", "name": "_not-real_package5", "version": "12.34.56.78_pre2_rc3", "revision": "26" }, { "category": "_not.real-category2+", "name": "_not-real_package1", "version": "12.34.56.78q" }, { "category": "_not.real-category2+", "name": "_not-real_package2", "version": "12.34.56.78q", "revision": "26" }, { "category": "_not.real-category2+", "name": "_not-real_package3", "version": "12.34.56.78q_rc3" }, { "category": "_not.real-category2+", "name": "_not-real_package4", "version": "12.34.56.78q_rc3", "revision": "26" }, { "category": "_not.real-category2+", "name": "_not-real_package5", "version": "12.34.56.78q_pre2_rc3", "revision": "26" } ] }` testFile, err := ioutil.TempFile("", "cos_pkg_info_test") if err != nil { t.Fatalf("Failed to create tempfile: %v", err) } defer os.Remove(testFile.Name()) _, err = testFile.WriteString(testDataJSON) if err != nil { t.Fatalf("Failed to write test data: %v", err) } err = testFile.Close() if err != nil { t.Fatalf("Failed to close test file: %v", err) } expected := []PkgInfo{ {"app-arch/gzip", "x86_64", "1.9"}, {"dev-libs/popt", "x86_64", "1.16-r2"}, {"app-emulation/docker-credential-helpers", "x86_64", "0.6.3-r1"}, {"_not.real-category1+/_not-real_package1", "x86_64", "12.34.56.78"}, {"_not.real-category1+/_not-real_package2", "x86_64", "12.34.56.78-r26"}, {"_not.real-category1+/_not-real_package3", "x86_64", "12.34.56.78_rc3"}, {"_not.real-category1+/_not-real_package4", "x86_64", "12.34.56.78_rc3-r26"}, {"_not.real-category1+/_not-real_package5", "x86_64", "12.34.56.78_pre2_rc3-r26"}, {"_not.real-category2+/_not-real_package1", "x86_64", "12.34.56.78q"}, {"_not.real-category2+/_not-real_package2", "x86_64", "12.34.56.78q-r26"}, {"_not.real-category2+/_not-real_package3", "x86_64", "12.34.56.78q_rc3"}, {"_not.real-category2+/_not-real_package4", "x86_64", "12.34.56.78q_rc3-r26"}, {"_not.real-category2+/_not-real_package5", "x86_64", "12.34.56.78q_pre2_rc3-r26"}, } readMachineArch = func() (string, error) { return "", errors.New("failed to obtain machine architecture") } readCOSPackageInfo = func() (cos.PackageInfo, error) { return cos.GetPackageInfoFromFile(testFile.Name()) } if _, err := InstalledCOSPackages(); err == nil { t.Errorf("did not get expected error from readMachineArch") } readMachineArch = func() (string, error) { return "x86_64", nil } readCOSPackageInfo = func() (cos.PackageInfo, error) { return cos.GetPackageInfoFromFile("_" + testFile.Name()) } if _, err := InstalledCOSPackages(); err == nil { t.Errorf("did not get expected error fro readCOSPackageInfo") } readMachineArch = func() (string, error) { return "x86_64", nil } readCOSPackageInfo = func() (cos.PackageInfo, error) { return cos.GetPackageInfoFromFile(testFile.Name()) } ret, err := InstalledCOSPackages() if err != nil { t.Errorf("unexpected error: %v", err) } if len(ret) != len(expected) { t.Errorf("Length is wrong. want: %d, got: %d", len(expected), len(ret)) } if !reflect.DeepEqual(ret, expected) { t.Errorf("InstalledCOSPackages() returned: %v, want: %v", ret, expected) } } osconfig-20210219.00/packages/gem.go000066400000000000000000000047541401404514100166620ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "context" "os/exec" "runtime" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( gem string gemListArgs = []string{"list", "--local"} gemOutdatedArgs = []string{"outdated", "--local"} ) func init() { if runtime.GOOS != "windows" { gem = "/usr/bin/gem" } GemExists = util.Exists(gem) } // GemUpdates queries for all available gem updates. func GemUpdates(ctx context.Context) ([]PkgInfo, error) { stdout, _, err := runner.Run(ctx, exec.Command(gem, gemOutdatedArgs...)) if err != nil { return nil, err } /* foo (1.2.8 < 1.3.2) bar (1.0.0 < 1.1.2) ... */ lines := strings.Split(strings.TrimSpace(string(stdout)), "\n") if len(lines) == 0 { return nil, nil } var pkgs []PkgInfo for _, ln := range lines { pkg := strings.Fields(ln) if len(pkg) != 4 { clog.Debugf(ctx, "%q does not represent a gem update\n", ln) continue } ver := strings.Trim(pkg[3], ")") pkgs = append(pkgs, PkgInfo{Name: pkg[0], Arch: noarch, Version: ver}) } return pkgs, nil } // InstalledGemPackages queries for all installed gem packages. func InstalledGemPackages(ctx context.Context) ([]PkgInfo, error) { stdout, _, err := runner.Run(ctx, exec.Command(gem, gemListArgs...)) if err != nil { return nil, err } /* *** LOCAL GEMS *** foo (1.2.3, 1.2.4) bar (1.2.3) ... */ lines := strings.Split(strings.TrimSpace(string(stdout)), "\n") if len(lines) == 0 { clog.Debugf(ctx, "No gems installed.") return nil, nil } var pkgs []PkgInfo for _, ln := range lines[2:] { pkg := strings.Fields(ln) if len(pkg) < 2 { clog.Debugf(ctx, "'%s' does not represent a gem", ln) continue } for _, ver := range strings.Split(strings.Trim(pkg[1], "()"), ", ") { pkgs = append(pkgs, PkgInfo{Name: pkg[0], Arch: noarch, Version: ver}) } } return pkgs, nil } osconfig-20210219.00/packages/googet.go000066400000000000000000000060231401404514100173650ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "bytes" "context" "os" "path/filepath" "strings" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( googet string googetUpdateQueryArgs = []string{"update"} googetInstalledQueryArgs = []string{"installed"} googetInstallArgs = []string{"-noconfirm", "install"} googetRemoveArgs = []string{"-noconfirm", "remove"} ) func init() { googet = filepath.Join(os.Getenv("GooGetRoot"), "googet.exe") GooGetExists = util.Exists(googet) } func parseGooGetUpdates(data []byte) []PkgInfo { /* Searching for available updates... foo.noarch, 3.5.4@1 --> 3.6.7@1 from repo ... Perform update? (y/N): */ lines := strings.Split(strings.TrimSpace(string(data)), "\n") var pkgs []PkgInfo for _, ln := range lines { pkg := strings.Fields(ln) if len(pkg) < 4 { continue } p := strings.Split(pkg[0], ".") if len(p) != 2 { continue } pkgs = append(pkgs, PkgInfo{Name: p[0], Arch: strings.Trim(p[1], ","), Version: pkg[3]}) } return pkgs } // GooGetUpdates queries for all available googet updates. func GooGetUpdates(ctx context.Context) ([]PkgInfo, error) { out, err := run(ctx, googet, googetUpdateQueryArgs) if err != nil { return nil, err } return parseGooGetUpdates(out), nil } // InstallGooGetPackages installs GooGet packages. func InstallGooGetPackages(ctx context.Context, pkgs []string) error { _, err := run(ctx, googet, append(googetInstallArgs, pkgs...)) return err } // RemoveGooGetPackages installs GooGet packages. func RemoveGooGetPackages(ctx context.Context, pkgs []string) error { _, err := run(ctx, googet, append(googetRemoveArgs, pkgs...)) return err } func parseInstalledGooGetPackages(data []byte) []PkgInfo { /* Installed Packages: foo.x86_64 1.2.3@4 bar.noarch 1.2.3@4 ... */ lines := bytes.Split(bytes.TrimSpace(data), []byte("\n")) var pkgs []PkgInfo for _, ln := range lines { pkg := bytes.Fields(ln) if len(pkg) != 2 { continue } p := bytes.Split(pkg[0], []byte(".")) if len(p) != 2 { continue } pkgs = append(pkgs, PkgInfo{Name: string(p[0]), Arch: string(p[1]), Version: string(pkg[1])}) } return pkgs } // InstalledGooGetPackages queries for all installed googet packages. func InstalledGooGetPackages(ctx context.Context) ([]PkgInfo, error) { out, err := run(ctx, googet, googetInstalledQueryArgs) if err != nil { return nil, err } return parseInstalledGooGetPackages(out), nil } osconfig-20210219.00/packages/googet_test.go000066400000000000000000000126741401404514100204350ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "errors" "os/exec" "reflect" "testing" utilmocks "github.com/GoogleCloudPlatform/osconfig/util/mocks" "github.com/golang/mock/gomock" ) func TestInstallGooGetPackages(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(googet, append(googetInstallArgs, pkgs...)...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) if err := InstallGooGetPackages(testCtx, pkgs); err != nil { t.Errorf("unexpected error: %v", err) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("Could not install package")).Times(1) if err := InstallGooGetPackages(testCtx, pkgs); err == nil { t.Errorf("did not get expected error") } } func TestRemoveGooGet(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(googet, append(googetRemoveArgs, pkgs...)...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) if err := RemoveGooGetPackages(testCtx, pkgs); err != nil { t.Errorf("unexpected error: %v", err) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("Could not remove package")).Times(1) if err := RemoveGooGetPackages(testCtx, pkgs); err == nil { t.Errorf("did not get expected error") } } func TestParseInstalledGooGetPackages(t *testing.T) { tests := []struct { name string data []byte want []PkgInfo }{ {"NormalCase", []byte(" Installed Packages:\nfoo.x86_64 1.2.3@4\nbar.noarch 1.2.3@4"), []PkgInfo{{"foo", "x86_64", "1.2.3@4"}, {"bar", "noarch", "1.2.3@4"}}}, {"NoPackages", []byte("nothing here"), nil}, {"nil", nil, nil}, {"UnrecognizedPackage", []byte("Inst something we dont understand\n foo.x86_64 1.2.3@4"), []PkgInfo{{"foo", "x86_64", "1.2.3@4"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parseInstalledGooGetPackages(tt.data); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseInstalledGooGetPackages() = %v, want %v", got, tt.want) } }) } } func TestInstalledGooGetPackages(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(googet, googetInstalledQueryArgs...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("foo.x86_64 1.2.3@4"), []byte("stderr"), nil).Times(1) ret, err := InstalledGooGetPackages(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } want := []PkgInfo{{"foo", "x86_64", "1.2.3@4"}} if !reflect.DeepEqual(ret, want) { t.Errorf("InstalledGooGetPackages() = %v, want %v", ret, want) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return(nil, nil, errors.New("bad error")).Times(1) if _, err := InstalledGooGetPackages(testCtx); err == nil { t.Errorf("did not get expected error") } } func TestParseGooGetUpdates(t *testing.T) { tests := []struct { name string data []byte want []PkgInfo }{ {"NormalCase", []byte("Searching for available updates...\nfoo.noarch, 3.5.4@1 --> 3.6.7@1 from repo\nbar.x86_64, 1.0.0@1 --> 2.0.0@1 from repo\nPerform update? (y/N):"), []PkgInfo{{"foo", "noarch", "3.6.7@1"}, {"bar", "x86_64", "2.0.0@1"}}}, {"NoPackages", []byte("nothing here"), nil}, {"nil", nil, nil}, {"UnrecognizedPackage", []byte("Inst something we dont understand\n foo.noarch, 3.5.4@1 --> 3.6.7@1 from repo"), []PkgInfo{{"foo", "noarch", "3.6.7@1"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parseGooGetUpdates(tt.data); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseGooGetUpdates() = %v, want %v", got, tt.want) } }) } } func TestGooGetUpdates(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(googet, googetUpdateQueryArgs...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("foo.noarch, 3.5.4@1 --> 3.6.7@1 from repo"), []byte("stderr"), nil).Times(1) ret, err := GooGetUpdates(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } want := []PkgInfo{{"foo", "noarch", "3.6.7@1"}} if !reflect.DeepEqual(ret, want) { t.Errorf("GooGetUpdates() = %v, want %v", ret, want) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("bad error")).Times(1) if _, err := GooGetUpdates(testCtx); err == nil { t.Errorf("did not get expected error") } } osconfig-20210219.00/packages/msi_windows.go000066400000000000000000000144731401404514100204530ustar00rootroot00000000000000// Copyright 2020 Google 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 packages import ( "context" "fmt" "strings" "sync" "syscall" "unsafe" "github.com/GoogleCloudPlatform/osconfig/clog" ole "github.com/go-ole/go-ole" "golang.org/x/sys/windows" ) var ( msiInstallArgs = []string{"ACTION=INSTALL", "REBOOT=ReallySuppress"} msi = windows.NewLazySystemDLL("msi.dll") procMsiOpenPackageExW = msi.NewProc("MsiOpenPackageExW") procMsiGetProductPropertyW = msi.NewProc("MsiGetProductPropertyW") procMsiQueryProductStateW = msi.NewProc("MsiQueryProductStateW") procMsiCloseHandle = msi.NewProc("MsiCloseHandle") procMsiInstallProductW = msi.NewProc("MsiInstallProductW") procMsiSetInternalUI = msi.NewProc("MsiSetInternalUI") once sync.Once ) func init() { MSIExists = true } func setUIMode() { /* INSTALLUILEVEL MsiSetInternalUI( INSTALLUILEVEL dwUILevel, HWND *phWnd ); */ const INSTALLUILEVEL_NONE = 2 once.Do(func() { procMsiSetInternalUI.Call( uintptr(INSTALLUILEVEL_NONE), 0, ) }) } // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msiopenpackageexw func msiOpenPackageExW(szPackagePath string, dwOptions uint32) (uintptr, error) { /* UINT MsiOpenPackageExW( LPCWSTR szPackagePath, DWORD dwOptions, MSIHANDLE *hProduct ); */ var handle int32 pHandle := uintptr(unsafe.Pointer(&handle)) szPackagePathPtr, err := syscall.UTF16PtrFromString(szPackagePath) if err != nil { return 0, fmt.Errorf("error encoding szPackagePath to UTF16: %v", err) } ret, _, _ := procMsiOpenPackageExW.Call( uintptr(unsafe.Pointer(szPackagePathPtr)), uintptr(dwOptions), pHandle, ) if ret != 0 { return 0, fmt.Errorf("MsiOpenPackageExW error: %s", syscall.Errno(ret)) } return uintptr(handle), nil } // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msiclosehandle func msiCloseHandle(handle uintptr) { /* UINT MsiCloseHandle( MSIHANDLE hAny ); */ procMsiCloseHandle.Call(handle) } // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msigetproductpropertyw func msiGetProductPropertyW(handle uintptr, szProperty string) (string, error) { /* UINT MsiGetProductPropertyW( MSIHANDLE hProduct, LPCSTR szProperty, LPSTR lpValueBuf, LPDWORD pcchValueBuf ); */ szPropertyPtr, err := syscall.UTF16PtrFromString(szProperty) if err != nil { return "", fmt.Errorf("error encoding szProperty to UTF16: %v", err) } size := uint32(128) lpValueBuf := make([]uint16, size) ret, _, _ := procMsiGetProductPropertyW.Call( handle, uintptr(unsafe.Pointer(szPropertyPtr)), uintptr(unsafe.Pointer(&lpValueBuf[0])), uintptr(unsafe.Pointer(&size)), ) if ret != 0 { return "", fmt.Errorf("MsiGetProductPropertyW error: %s", syscall.Errno(ret)) } return syscall.UTF16ToString(lpValueBuf), nil } type msiInstallState int32 const ( INSTALLSTATE_UNKNOWN = msiInstallState(-1) INSTALLSTATE_ADVERTISED = msiInstallState(1) INSTALLSTATE_ABSENT = msiInstallState(2) INSTALLSTATE_DEFAULT = msiInstallState(5) ) // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msiqueryproductstatew func msiMsiQueryProductStateW(szProduct string) (msiInstallState, error) { /* INSTALLSTATE MsiQueryProductStateW( LPCWSTR szProduct ); */ szProductPtr, err := syscall.UTF16PtrFromString(szProduct) if err != nil { return -1, fmt.Errorf("error encoding szProduct to UTF16: %v", err) } ret, _, _ := procMsiQueryProductStateW.Call(uintptr(unsafe.Pointer(szProductPtr))) return msiInstallState(ret), nil } // https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msiinstallproductw func msiInstallProductW(szPackagePath string, szCommandLine []string) error { /* UINT MsiInstallProductW( LPCWSTR szPackagePath, LPCWSTR szCommandLine ); */ szPackagePathPtr, err := syscall.UTF16PtrFromString(szPackagePath) if err != nil { return fmt.Errorf("error encoding szPackagePath to UTF16: %v", err) } szCommandLinePtr, err := syscall.UTF16PtrFromString(strings.Join(szCommandLine, " ")) if err != nil { return fmt.Errorf("error encoding szCommandLine to UTF16: %v", err) } ret, _, _ := procMsiInstallProductW.Call( uintptr(unsafe.Pointer(szPackagePathPtr)), uintptr(unsafe.Pointer(szCommandLinePtr)), ) if ret != 0 { return fmt.Errorf("MsiInstallProductW error: %s", syscall.Errno(ret)) } return nil } // MSIInfo returns the ProductName and whether or not a specific msi (based on ProductCode) is installed. func MSIInfo(path string) (string, bool, error) { setUIMode() if err := coInitializeEx(); err != nil { return "", false, err } defer ole.CoUninitialize() const MSIOPENPACKAGEFLAGS_IGNOREMACHINESTATE = 1 handle, err := msiOpenPackageExW(path, MSIOPENPACKAGEFLAGS_IGNOREMACHINESTATE) if err != nil { return "", false, fmt.Errorf("error opening MSI package %q: %v", path, err) } defer msiCloseHandle(handle) productCode, err := msiGetProductPropertyW(handle, "ProductCode") if err != nil { return "", false, fmt.Errorf("error getting ProductCode property: %v", err) } productName, err := msiGetProductPropertyW(handle, "ProductName") if err != nil { return "", false, fmt.Errorf("error getting ProductName property: %v", err) } state, err := msiMsiQueryProductStateW(productCode) if err != nil { return "", false, err } return productName, state == INSTALLSTATE_DEFAULT, nil } // InstallMSIPackage installs an msi package. func InstallMSIPackage(ctx context.Context, path string, args []string) error { setUIMode() args = append(msiInstallArgs, args...) clog.Infof(ctx, "Installing msi package %q with command line %q.", path, args) if err := msiInstallProductW(path, args); err != nil { return fmt.Errorf("error installing MSI package %q: %v", path, err) } return nil } osconfig-20210219.00/packages/packages.go000066400000000000000000000105201401404514100176540ustar00rootroot00000000000000/* Copyright 2017 Google 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 packages provides package management functions for Windows and Linux // systems. package packages import ( "context" "fmt" "os/exec" "strings" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/osinfo" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( // AptExists indicates whether apt is installed. AptExists bool // DpkgExists indicates whether dpkg is installed. DpkgExists bool // DpkgQueryExists indicates whether dpkg-query is installed. DpkgQueryExists bool // YumExists indicates whether yum is installed. YumExists bool // ZypperExists indicates whether zypper is installed. ZypperExists bool // RPMExists indicates whether rpm is installed. RPMExists bool // RPMQueryExists indicates whether rpmquery is installed. RPMQueryExists bool // COSPkgInfoExists indicates whether COS package information is available. COSPkgInfoExists bool // GemExists indicates whether gem is installed. GemExists bool // PipExists indicates whether pip is installed. PipExists bool // GooGetExists indicates whether googet is installed. GooGetExists bool // MSIExists indicates whether MSIs can be installed. MSIExists bool noarch = osinfo.Architecture("noarch") runner = util.CommandRunner(&util.DefaultRunner{}) ptyrunner = util.CommandRunner(&ptyRunner{}) ) // Packages is a selection of packages based on their manager. type Packages struct { Yum []PkgInfo `json:"yum,omitempty"` Rpm []PkgInfo `json:"rpm,omitempty"` Apt []PkgInfo `json:"apt,omitempty"` Deb []PkgInfo `json:"deb,omitempty"` Zypper []PkgInfo `json:"zypper,omitempty"` ZypperPatches []ZypperPatch `json:"zypperPatches,omitempty"` COS []PkgInfo `json:"cos,omitempty"` Gem []PkgInfo `json:"gem,omitempty"` Pip []PkgInfo `json:"pip,omitempty"` GooGet []PkgInfo `json:"googet,omitempty"` WUA []WUAPackage `json:"wua,omitempty"` QFE []QFEPackage `json:"qfe,omitempty"` } // PkgInfo describes a package. type PkgInfo struct { Name, Arch, Version string } // ZypperPatch describes a Zypper patch. type ZypperPatch struct { Name, Category, Severity, Summary string } // WUAPackage describes a Windows Update Agent package. type WUAPackage struct { Title string Description string Categories []string CategoryIDs []string KBArticleIDs []string MoreInfoURLs []string SupportURL string UpdateID string RevisionNumber int32 LastDeploymentChangeTime time.Time } // QFEPackage describes a Windows Quick Fix Engineering package. type QFEPackage struct { Caption, Description, HotFixID, InstalledOn string } func run(ctx context.Context, cmd string, args []string) ([]byte, error) { stdout, stderr, err := runner.Run(ctx, exec.Command(cmd, args...)) if err != nil { return nil, fmt.Errorf("error running %s with args %q: %v, stdout: %q, stderr: %q", cmd, args, err, stdout, stderr) } return stdout, nil } type ptyRunner struct{} func (p *ptyRunner) Run(ctx context.Context, cmd *exec.Cmd) ([]byte, []byte, error) { clog.Debugf(ctx, "Running %q with args %q\n", cmd.Path, cmd.Args[1:]) stdout, stderr, err := runWithPty(cmd) clog.Debugf(ctx, "%s %q output:\n%s", cmd.Path, cmd.Args[1:], strings.ReplaceAll(string(stdout), "\n", "\n ")) return stdout, stderr, err } // SetCommandRunner allows external clients to set a custom commandRunner. func SetCommandRunner(commandRunner util.CommandRunner) { runner = commandRunner } // SetPtyCommandRunner allows external clients to set a custom // custom commandRunner. func SetPtyCommandRunner(commandRunner util.CommandRunner) { ptyrunner = commandRunner } osconfig-20210219.00/packages/packages_linux.go000066400000000000000000000103021401404514100210710ustar00rootroot00000000000000/* Copyright 2017 Google 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 packages import ( "context" "errors" "fmt" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/util" ) // GetPackageUpdates gets all available package updates from any known // installed package manager. func GetPackageUpdates(ctx context.Context) (*Packages, error) { pkgs := Packages{} var errs []string if AptExists { apt, err := AptUpdates(ctx, AptGetUpgradeType(AptGetFullUpgrade), AptGetUpgradeShowNew(false)) if err != nil { msg := fmt.Sprintf("error getting apt updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.Apt = apt } } if YumExists { yum, err := YumUpdates(ctx) if err != nil { msg := fmt.Sprintf("error getting yum updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.Yum = yum } } if ZypperExists { zypper, err := ZypperUpdates(ctx) if err != nil { msg := fmt.Sprintf("error getting zypper updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.Zypper = zypper } zypperPatches, err := ZypperPatches(ctx) if err != nil { msg := fmt.Sprintf("error getting zypper available patches: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.ZypperPatches = zypperPatches } } if GemExists { gem, err := GemUpdates(ctx) if err != nil { msg := fmt.Sprintf("error getting gem updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) } else { pkgs.Gem = gem } } if PipExists { pip, err := PipUpdates(ctx) if err != nil { msg := fmt.Sprintf("error getting pip updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) } else { pkgs.Pip = pip } } var err error if len(errs) != 0 { err = errors.New(strings.Join(errs, "\n")) } return &pkgs, err } // GetInstalledPackages gets all installed packages from any known installed // package manager. func GetInstalledPackages(ctx context.Context) (*Packages, error) { pkgs := Packages{} var errs []string if util.Exists(rpmquery) { rpm, err := InstalledRPMPackages(ctx) if err != nil { msg := fmt.Sprintf("error listing installed rpm packages: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.Rpm = rpm } } if util.Exists(zypper) { zypperPatches, err := ZypperInstalledPatches(ctx) if err != nil { msg := fmt.Sprintf("error getting zypper installed patches: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.ZypperPatches = zypperPatches } } if util.Exists(dpkgQuery) { deb, err := InstalledDebPackages(ctx) if err != nil { msg := fmt.Sprintf("error listing installed deb packages: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.Deb = deb } } if COSPkgInfoExists { cos, err := InstalledCOSPackages() if err != nil { msg := fmt.Sprintf("error listing installed COS packages: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.COS = cos } } if util.Exists(gem) { gem, err := InstalledGemPackages(ctx) if err != nil { msg := fmt.Sprintf("error listing installed gem packages: %v", err) clog.Debugf(ctx, "Error: %s", msg) } else { pkgs.Gem = gem } } if util.Exists(pip) { pip, err := InstalledPipPackages(ctx) if err != nil { msg := fmt.Sprintf("error listing installed pip packages: %v", err) clog.Debugf(ctx, "Error: %s", msg) } else { pkgs.Pip = pip } } var err error if len(errs) != 0 { err = errors.New(strings.Join(errs, "\n")) } return &pkgs, err } osconfig-20210219.00/packages/packages_test.go000066400000000000000000000023031401404514100207130ustar00rootroot00000000000000// Copyright 2017 Google 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 packages import ( "context" "io/ioutil" "os/exec" "path/filepath" ) var pkgs = []string{"pkg1", "pkg2"} var testCtx = context.Background() func getMockRun(content []byte, err error) func(_ context.Context, cmd *exec.Cmd) ([]byte, error) { return func(_ context.Context, cmd *exec.Cmd) ([]byte, error) { return content, err } } // TODO: move this to a common helper package func helperLoadBytes(name string) ([]byte, error) { path := filepath.Join("testdata", name) // relative path bytes, err := ioutil.ReadFile(path) if err != nil { return nil, err } return bytes, nil } osconfig-20210219.00/packages/packages_windows.go000066400000000000000000000074271401404514100214420ustar00rootroot00000000000000/* Copyright 2017 Google 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 packages import ( "context" "encoding/json" "errors" "fmt" "os" "os/exec" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/util" ole "github.com/go-ole/go-ole" ) func coInitializeEx() error { if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil { e, ok := err.(*ole.OleError) // S_OK and S_FALSE are both are Success codes. // https://docs.microsoft.com/en-us/windows/win32/learnwin32/error-handling-in-com if !ok || (e.Code() != S_OK && e.Code() != S_FALSE) { return fmt.Errorf(`ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED): %v`, err) } } return nil } // In order to work around memory issues with the WUA library we spawn a // new process for these inventory queries. func wuaUpdates(ctx context.Context, query string) ([]WUAPackage, error) { exe, err := os.Executable() if err != nil { return nil, err } var wua []WUAPackage stdout, stderr, err := runner.Run(ctx, exec.Command(exe, "wuaupdates", query)) if err != nil { return nil, fmt.Errorf("error running agent to query for WUA updates, err: %v, stderr: %q ", err, stderr) } if err := json.Unmarshal(stdout, &wua); err != nil { return nil, err } return wua, nil } // GetPackageUpdates gets available package updates GooGet as well as any // available updates from Windows Update Agent. func GetPackageUpdates(ctx context.Context) (*Packages, error) { var pkgs Packages var errs []string if GooGetExists { if googet, err := GooGetUpdates(ctx); err != nil { msg := fmt.Sprintf("error listing googet updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.GooGet = googet } } clog.Debugf(ctx, "Searching for available WUA updates.") if wua, err := wuaUpdates(ctx, "IsInstalled=0"); err != nil { msg := fmt.Sprintf("error listing installed Windows updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.WUA = wua } var err error if len(errs) != 0 { err = errors.New(strings.Join(errs, "\n")) } return &pkgs, err } // GetInstalledPackages gets all installed GooGet packages and Windows updates. // Windows updates are read from Windows Update Agent and Win32_QuickFixEngineering. func GetInstalledPackages(ctx context.Context) (*Packages, error) { var pkgs Packages var errs []string if util.Exists(googet) { if googet, err := InstalledGooGetPackages(ctx); err != nil { msg := fmt.Sprintf("error listing installed googet packages: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.GooGet = googet } } clog.Debugf(ctx, "Searching for installed WUA updates.") if wua, err := wuaUpdates(ctx, "IsInstalled=1"); err != nil { msg := fmt.Sprintf("error listing installed Windows updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.WUA = wua } if qfe, err := QuickFixEngineering(ctx); err != nil { msg := fmt.Sprintf("error listing installed QuickFixEngineering updates: %v", err) clog.Debugf(ctx, "Error: %s", msg) errs = append(errs, msg) } else { pkgs.QFE = qfe } var err error if len(errs) != 0 { err = errors.New(strings.Join(errs, "\n")) } return &pkgs, err } osconfig-20210219.00/packages/pip.go000066400000000000000000000041341401404514100166720ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "context" "encoding/json" "runtime" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( pip string pipListArgs = []string{"list", "--format=json"} pipOutdatedArgs = append(pipListArgs, "--outdated") ) func init() { if runtime.GOOS != "windows" { pip = "/usr/bin/pip" } PipExists = util.Exists(pip) } type pipUpdatesPkg struct { Name string `json:"name"` LatestVersion string `json:"latest_version"` } type pipInstalledPkg struct { Name string `json:"name"` Version string `json:"version"` } // PipUpdates queries for all available pip updates. func PipUpdates(ctx context.Context) ([]PkgInfo, error) { out, err := run(ctx, pip, pipOutdatedArgs) if err != nil { return nil, err } var pipUpdates []pipUpdatesPkg if err := json.Unmarshal(out, &pipUpdates); err != nil { return nil, err } var pkgs []PkgInfo for _, pkg := range pipUpdates { pkgs = append(pkgs, PkgInfo{Name: pkg.Name, Arch: noarch, Version: pkg.LatestVersion}) } return pkgs, nil } // InstalledPipPackages queries for all installed pip packages. func InstalledPipPackages(ctx context.Context) ([]PkgInfo, error) { out, err := run(ctx, pip, pipListArgs) if err != nil { return nil, err } var pipUpdates []pipInstalledPkg if err := json.Unmarshal(out, &pipUpdates); err != nil { return nil, err } var pkgs []PkgInfo for _, pkg := range pipUpdates { pkgs = append(pkgs, PkgInfo{Name: pkg.Name, Arch: noarch, Version: pkg.Version}) } return pkgs, nil } osconfig-20210219.00/packages/pty_linux.go000066400000000000000000000061401401404514100201340ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "bufio" "bytes" "fmt" "io" "os" "os/exec" "path/filepath" "strconv" "sync" "syscall" "unsafe" "golang.org/x/sys/unix" ) func ioctl(fd, req, arg uintptr) (err error) { _, _, e1 := unix.Syscall(unix.SYS_IOCTL, fd, req, arg) if e1 != 0 { err = syscall.Errno(e1) } return } // This is used for anytime we need to parse YUM output. // See https://bugzilla.redhat.com/show_bug.cgi?id=584525#c21 // TODO: We should probably look into a thin python shim we can // interact with that the utilizes the yum libraries. func runWithPty(cmd *exec.Cmd) ([]byte, []byte, error) { // Much of this logic was taken from, without the CGO stuff: // https://golang.org/src/os/signal/signal_cgo_test.go pty, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) if err != nil { return nil, nil, err } defer pty.Close() // grantpt doesn't appear to be required anymore. // unlockpt var i int if err := ioctl(pty.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&i))); err != nil { return nil, nil, fmt.Errorf("error from ioctl TIOCSPTLCK: %v", err) } // ptsname var u uint32 if err := ioctl(pty.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&u))); err != nil { return nil, nil, fmt.Errorf("error from ioctl TIOCGPTN: %v", err) } path := filepath.Join("/dev/pts", strconv.Itoa(int(u))) tty, err := os.OpenFile(path, os.O_RDWR|syscall.O_NOCTTY, 0) if err != nil { return nil, nil, err } defer tty.Close() if err := unix.IoctlSetWinsize(int(pty.Fd()), syscall.TIOCSWINSZ, &unix.Winsize{Row: 1, Col: 500}); err != nil { return nil, nil, fmt.Errorf("error from IoctlSetWinsize: %v", err) } var stderr bytes.Buffer cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = &stderr cmd.SysProcAttr = &syscall.SysProcAttr{ Setctty: true, Setsid: true, Ctty: int(tty.Fd()), } var stdout bytes.Buffer var retErr error var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() input := bufio.NewReader(pty) for { b, err := input.ReadBytes('\n') if err != nil { if perr, ok := err.(*os.PathError); ok { err = perr.Err } if err != io.EOF && err != syscall.EIO { retErr = err } return } if _, err := stdout.Write(b); err != nil { retErr = err return } } }() err = cmd.Run() if err := tty.Close(); err != nil { return nil, nil, err } wg.Wait() // Exit code 0 means no updates, 1 probably means there are but we just didn't install them. if err == nil { return nil, nil, err } return stdout.Bytes(), stderr.Bytes(), retErr } osconfig-20210219.00/packages/qfe_windows.go000066400000000000000000000030171401404514100204260ustar00rootroot00000000000000/* Copyright 2019 Google 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 packages import ( "context" "fmt" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/StackExchange/wmi" ) type win32QuickFixEngineering struct { Caption, Description, HotFixID, InstalledOn string } // QuickFixEngineering queries the wmi object win32_QuickFixEngineering for a list of installed updates. func QuickFixEngineering(ctx context.Context) ([]QFEPackage, error) { var updts []win32QuickFixEngineering clog.Debugf(ctx, "Querying WMI for installed QuickFixEngineering updates.") query := "SELECT Caption, Description, HotFixID, InstalledOn FROM Win32_QuickFixEngineering" if err := wmi.Query(query, &updts); err != nil { return nil, fmt.Errorf("wmi.Query(%q) error: %v", query, err) } var qfe []QFEPackage for _, update := range updts { qfe = append(qfe, QFEPackage{ Caption: update.Caption, Description: update.Description, HotFixID: update.HotFixID, InstalledOn: update.InstalledOn, }) } return qfe, nil } osconfig-20210219.00/packages/rpm.go000066400000000000000000000051161401404514100167010ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "bytes" "context" "fmt" "runtime" "github.com/GoogleCloudPlatform/osconfig/osinfo" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( rpmquery string rpm string rpmInstallArgs = []string{"--upgrade", "--replacepkgs", "-v"} // %|EPOCH?{%{EPOCH}:}:{}| == if EPOCH then prepend "%{EPOCH}:" to version. rpmqueryArgs = []string{"--queryformat", "%{NAME} %{ARCH} %|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\n"} rpmqueryInstalledArgs = append(rpmqueryArgs, "-a") rpmqueryRPMArgs = append(rpmqueryArgs, "-p") ) func init() { if runtime.GOOS != "windows" { rpmquery = "/usr/bin/rpmquery" rpm = "/bin/rpm" } RPMQueryExists = util.Exists(rpmquery) RPMExists = util.Exists(rpm) } func parseInstalledRPMPackages(data []byte) []PkgInfo { /* foo x86_64 1.2.3-4 bar noarch 2:1.2.3-4 ... */ lines := bytes.Split(bytes.TrimSpace(data), []byte("\n")) var pkgs []PkgInfo for _, ln := range lines { pkg := bytes.Fields(ln) if len(pkg) != 3 { continue } pkgs = append(pkgs, PkgInfo{Name: string(pkg[0]), Arch: osinfo.Architecture(string(pkg[1])), Version: string(pkg[2])}) } return pkgs } // InstalledRPMPackages queries for all installed rpm packages. func InstalledRPMPackages(ctx context.Context) ([]PkgInfo, error) { out, err := run(ctx, rpmquery, rpmqueryInstalledArgs) if err != nil { return nil, err } return parseInstalledRPMPackages(out), nil } // RPMInstall installs an rpm packages. func RPMInstall(ctx context.Context, path string) error { _, err := run(ctx, rpm, append(rpmInstallArgs, path)) return err } // RPMPkgInfo gets PkgInfo from a rpm package. func RPMPkgInfo(ctx context.Context, path string) (*PkgInfo, error) { out, err := run(ctx, rpmquery, append(rpmqueryRPMArgs, path)) if err != nil { return nil, err } pkgs := parseInstalledRPMPackages(out) if len(pkgs) != 1 { return nil, fmt.Errorf("unexpected number of parsed rpm packages %d: %q", len(pkgs), out) } return &pkgs[0], nil } osconfig-20210219.00/packages/rpm_test.go000066400000000000000000000072761401404514100177510ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "errors" "os/exec" "reflect" "testing" utilmocks "github.com/GoogleCloudPlatform/osconfig/util/mocks" "github.com/golang/mock/gomock" ) func TestParseInstalledRPMPackages(t *testing.T) { tests := []struct { name string data []byte want []PkgInfo }{ {"NormalCase", []byte("foo x86_64 1.2.3-4\nbar noarch 1.2.3-4"), []PkgInfo{{"foo", "x86_64", "1.2.3-4"}, {"bar", "all", "1.2.3-4"}}}, {"NoPackages", []byte("nothing here"), nil}, {"nil", nil, nil}, {"UnrecognizedPackage", []byte("foo.x86_64 1.2.3-4\nsomething we dont understand\n bar noarch 1.2.3-4 "), []PkgInfo{{"bar", "all", "1.2.3-4"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := parseInstalledRPMPackages(tt.data) if !reflect.DeepEqual(got, tt.want) { t.Errorf("installedRPMPackages() = %v, want %v", got, tt.want) } }) } } func TestInstalledRPMPackages(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(rpmquery, rpmqueryInstalledArgs...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("foo x86_64 1.2.3-4"), []byte("stderr"), nil).Times(1) ret, err := InstalledRPMPackages(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } want := []PkgInfo{{"foo", "x86_64", "1.2.3-4"}} if !reflect.DeepEqual(ret, want) { t.Errorf("InstalledRPMPackages() = %v, want %v", ret, want) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("bad error")).Times(1) if _, err := InstalledRPMPackages(testCtx); err == nil { t.Errorf("did not get expected error") } } func TestRPMPkgInfo(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner testPkg := "test.rpm" expectedCmd := exec.Command(rpmquery, append(rpmqueryRPMArgs, testPkg)...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("foo x86_64 1.2.3-4"), []byte("stderr"), nil).Times(1) ret, err := RPMPkgInfo(testCtx, testPkg) if err != nil { t.Errorf("unexpected error: %v", err) } want := &PkgInfo{"foo", "x86_64", "1.2.3-4"} if !reflect.DeepEqual(ret, want) { t.Errorf("RPMPkgInfo() = %v, want %v", ret, want) } // Error output. mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("bad error")).Times(1) if _, err := RPMPkgInfo(testCtx, testPkg); err == nil { t.Errorf("did not get expected error") } // More than 1 package mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("foo x86_64 1.2.3-4\nbar noarch 1.0.0"), []byte("stderr"), nil).Times(1) if _, err := RPMPkgInfo(testCtx, testPkg); err == nil { t.Errorf("did not get expected error") } // No package mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte(""), []byte("stderr"), nil).Times(1) if _, err := RPMPkgInfo(testCtx, testPkg); err == nil { t.Errorf("did not get expected error") } } osconfig-20210219.00/packages/stub_linux.go000066400000000000000000000016271401404514100203020ustar00rootroot00000000000000// Copyright 2019 Google 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. // +build !windows package packages import "context" // InstallMSIPackage is a linux stub function. func InstallMSIPackage(_ context.Context, _ string, _ []string) error { return nil } // MSIInfo is a linux stub function. func MSIInfo(_ string) (string, bool, error) { return "", false, nil } osconfig-20210219.00/packages/stub_windows.go000066400000000000000000000013701401404514100206300ustar00rootroot00000000000000// Copyright 2019 Google 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. // +build !linux package packages import ( "os/exec" ) func runWithPty(cmd *exec.Cmd) ([]byte, []byte, error) { return nil, nil, nil } osconfig-20210219.00/packages/wua_windows.go000066400000000000000000000312331401404514100204500ustar00rootroot00000000000000/* Copyright 2019 Google 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 packages import ( "context" "fmt" "sync" "github.com/GoogleCloudPlatform/osconfig/clog" ole "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" ) const ( S_OK = 0 S_FALSE = 1 ) var wuaSession sync.Mutex // IUpdateSession is a an IUpdateSession. type IUpdateSession struct { *ole.IDispatch } func NewUpdateSession() (*IUpdateSession, error) { wuaSession.Lock() if err := coInitializeEx(); err != nil { wuaSession.Unlock() return nil, err } s := &IUpdateSession{} unknown, err := oleutil.CreateObject("Microsoft.Update.Session") if err != nil { s.Close() return nil, fmt.Errorf(`oleutil.CreateObject("Microsoft.Update.Session"): %v`, err) } disp, err := unknown.QueryInterface(ole.IID_IDispatch) if err != nil { unknown.Release() s.Close() return nil, fmt.Errorf(`error creating Dispatch object from Microsoft.Update.Session connection: %v`, err) } s.IDispatch = disp return s, nil } func (s *IUpdateSession) Close() { if s.IDispatch != nil { s.IDispatch.Release() } ole.CoUninitialize() wuaSession.Unlock() } // InstallWUAUpdate install a WIndows update. func (s *IUpdateSession) InstallWUAUpdate(ctx context.Context, updt *IUpdate) error { title, err := updt.GetProperty("Title") if err != nil { return fmt.Errorf(`updt.GetProperty("Title"): %v`, err) } updts, err := NewUpdateCollection() if err != nil { return err } defer updts.Release() eula, err := updt.GetProperty("EulaAccepted") if err != nil { return fmt.Errorf(`updt.GetProperty("EulaAccepted"): %v`, err) } // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oaut/7b39eb24-9d39-498a-bcd8-75c38e5823d0 if eula.Val == 0 { clog.Debugf(ctx, "%s - Accepting EULA", title.Value()) if _, err := updt.CallMethod("AcceptEula"); err != nil { return fmt.Errorf(`updt.CallMethod("AcceptEula"): %v`, err) } } else { clog.Debugf(ctx, "%s - EulaAccepted: %v", title.Value(), eula.Value()) } if err := updts.Add(updt); err != nil { return err } clog.Debugf(ctx, "Downloading update %s", title.Value()) if err := s.DownloadWUAUpdateCollection(updts); err != nil { return fmt.Errorf("DownloadWUAUpdateCollection error: %v", err) } clog.Debugf(ctx, "Installing update %s", title.Value()) if err := s.InstallWUAUpdateCollection(updts); err != nil { return fmt.Errorf("InstallWUAUpdateCollection error: %v", err) } return nil } func NewUpdateCollection() (*IUpdateCollection, error) { updateCollObj, err := oleutil.CreateObject("Microsoft.Update.UpdateColl") if err != nil { return nil, fmt.Errorf(`oleutil.CreateObject("Microsoft.Update.UpdateColl"): %v`, err) } defer updateCollObj.Release() updateColl, err := updateCollObj.IDispatch(ole.IID_IDispatch) if err != nil { return nil, err } return &IUpdateCollection{IDispatch: updateColl}, nil } type IUpdateCollection struct { *ole.IDispatch } type IUpdate struct { *ole.IDispatch } func (c *IUpdateCollection) Add(updt *IUpdate) error { if _, err := c.CallMethod("Add", updt.IDispatch); err != nil { return fmt.Errorf(`IUpdateCollection.CallMethod("Add", updt): %v`, err) } return nil } func (c *IUpdateCollection) RemoveAt(i int) error { if _, err := c.CallMethod("RemoveAt", i); err != nil { return fmt.Errorf(`IUpdateCollection.CallMethod("RemoveAt", %d): %v`, i, err) } return nil } func (c *IUpdateCollection) Count() (int32, error) { return GetCount(c.IDispatch) } func (c *IUpdateCollection) Item(i int) (*IUpdate, error) { updtRaw, err := c.GetProperty("Item", i) if err != nil { return nil, fmt.Errorf(`IUpdateCollection.GetProperty("Item", %d): %v`, i, err) } return &IUpdate{IDispatch: updtRaw.ToIDispatch()}, nil } // GetCount returns the Count property. func GetCount(dis *ole.IDispatch) (int32, error) { countRaw, err := dis.GetProperty("Count") if err != nil { return 0, fmt.Errorf(`IDispatch.GetProperty("Count"): %v`, err) } count, _ := countRaw.Value().(int32) return count, nil } func (u *IUpdate) kbaIDs() ([]string, error) { kbArticleIDsRaw, err := u.GetProperty("KBArticleIDs") if err != nil { return nil, fmt.Errorf(`IUpdate.GetProperty("KBArticleIDs"): %v`, err) } kbArticleIDs := kbArticleIDsRaw.ToIDispatch() defer kbArticleIDs.Release() count, err := GetCount(kbArticleIDs) if err != nil { return nil, err } if count == 0 { return nil, nil } var ss []string for i := 0; i < int(count); i++ { item, err := kbArticleIDs.GetProperty("Item", i) if err != nil { return nil, fmt.Errorf(`kbArticleIDs.GetProperty("Item", %d): %v`, i, err) } ss = append(ss, item.ToString()) } return ss, nil } func (u *IUpdate) categories() ([]string, []string, error) { catRaw, err := u.GetProperty("Categories") if err != nil { return nil, nil, fmt.Errorf(`IUpdate.GetProperty("Categories"): %v`, err) } cat := catRaw.ToIDispatch() defer cat.Release() count, err := GetCount(cat) if err != nil { return nil, nil, err } if count == 0 { return nil, nil, nil } var cns, cids []string for i := 0; i < int(count); i++ { itemRaw, err := cat.GetProperty("Item", i) if err != nil { return nil, nil, fmt.Errorf(`cat.GetProperty("Item", %d): %v`, i, err) } item := itemRaw.ToIDispatch() defer item.Release() name, err := item.GetProperty("Name") if err != nil { return nil, nil, fmt.Errorf(`item.GetProperty("Name"): %v`, err) } categoryID, err := item.GetProperty("CategoryID") if err != nil { return nil, nil, fmt.Errorf(`item.GetProperty("CategoryID"): %v`, err) } cns = append(cns, name.ToString()) cids = append(cids, categoryID.ToString()) } return cns, cids, nil } func (u *IUpdate) moreInfoURLs() ([]string, error) { moreInfoURLsRaw, err := u.GetProperty("MoreInfoURLs") if err != nil { return nil, fmt.Errorf(`IUpdate.GetProperty("MoreInfoURLs"): %v`, err) } moreInfoURLs := moreInfoURLsRaw.ToIDispatch() defer moreInfoURLs.Release() count, err := GetCount(moreInfoURLs) if err != nil { return nil, err } if count == 0 { return nil, nil } var ss []string for i := 0; i < int(count); i++ { item, err := moreInfoURLs.GetProperty("Item", i) if err != nil { return nil, fmt.Errorf(`moreInfoURLs.GetProperty("Item", %d): %v`, i, err) } ss = append(ss, item.ToString()) } return ss, nil } func (c *IUpdateCollection) extractPkg(item int) (*WUAPackage, error) { updt, err := c.Item(item) if err != nil { return nil, err } defer updt.Release() title, err := updt.GetProperty("Title") if err != nil { return nil, fmt.Errorf(`updt.GetProperty("Title"): %v`, err) } description, err := updt.GetProperty("Description") if err != nil { return nil, fmt.Errorf(`updt.GetProperty("Description"): %v`, err) } kbArticleIDs, err := updt.kbaIDs() if err != nil { return nil, err } categories, categoryIDs, err := updt.categories() if err != nil { return nil, err } moreInfoURLs, err := updt.moreInfoURLs() if err != nil { return nil, err } supportURL, err := updt.GetProperty("SupportURL") if err != nil { return nil, fmt.Errorf(`updt.GetProperty("SupportURL"): %v`, err) } lastDeploymentChangeTimeRaw, err := updt.GetProperty("LastDeploymentChangeTime") if err != nil { return nil, fmt.Errorf(`updt.GetProperty("LastDeploymentChangeTime"): %v`, err) } lastDeploymentChangeTime, err := ole.GetVariantDate(uint64(lastDeploymentChangeTimeRaw.Val)) if err != nil { return nil, fmt.Errorf(`ole.GetVariantDate(uint64(lastDeploymentChangeTimeRaw.Val)): %v`, err) } identityRaw, err := updt.GetProperty("Identity") if err != nil { return nil, fmt.Errorf(`updt.GetProperty("Identity"): %v`, err) } identity := identityRaw.ToIDispatch() defer identity.Release() revisionNumber, err := identity.GetProperty("RevisionNumber") if err != nil { return nil, fmt.Errorf(`identity.GetProperty("RevisionNumber"): %v`, err) } updateID, err := identity.GetProperty("UpdateID") if err != nil { return nil, fmt.Errorf(`identity.GetProperty("UpdateID"): %v`, err) } return &WUAPackage{ Title: title.ToString(), Description: description.ToString(), SupportURL: supportURL.ToString(), KBArticleIDs: kbArticleIDs, UpdateID: updateID.ToString(), Categories: categories, CategoryIDs: categoryIDs, MoreInfoURLs: moreInfoURLs, RevisionNumber: int32(revisionNumber.Val), LastDeploymentChangeTime: lastDeploymentChangeTime, }, nil } // WUAUpdates queries the Windows Update Agent API searcher with the provided query. func WUAUpdates(query string) ([]WUAPackage, error) { session, err := NewUpdateSession() if err != nil { return nil, fmt.Errorf("error creating NewUpdateSession: %v", err) } defer session.Close() updts, err := session.GetWUAUpdateCollection(query) if err != nil { return nil, fmt.Errorf("error calling GetWUAUpdateCollection with query %q: %v", query, err) } defer updts.Release() updtCnt, err := updts.Count() if err != nil { return nil, err } if updtCnt == 0 { return nil, nil } var packages []WUAPackage for i := 0; i < int(updtCnt); i++ { pkg, err := updts.extractPkg(i) if err != nil { return nil, err } packages = append(packages, *pkg) } return packages, nil } // DownloadWUAUpdateCollection downloads all updates in a IUpdateCollection func (s *IUpdateSession) DownloadWUAUpdateCollection(updates *IUpdateCollection) error { // returns IUpdateDownloader // https://docs.microsoft.com/en-us/windows/desktop/api/wuapi/nn-wuapi-iupdatedownloader downloaderRaw, err := s.CallMethod("CreateUpdateDownloader") if err != nil { return fmt.Errorf("error calling method CreateUpdateDownloader on IUpdateSession: %v", err) } downloader := downloaderRaw.ToIDispatch() defer downloader.Release() if _, err := downloader.PutProperty("Updates", updates.IDispatch); err != nil { return fmt.Errorf("error calling PutProperty Updates on IUpdateDownloader: %v", err) } if _, err := downloader.CallMethod("Download"); err != nil { return fmt.Errorf("error calling method Download on IUpdateDownloader: %v", err) } return nil } // InstallWUAUpdateCollection installs all updates in a IUpdateCollection func (s *IUpdateSession) InstallWUAUpdateCollection(updates *IUpdateCollection) error { // returns IUpdateInstallersession *ole.IDispatch, // https://docs.microsoft.com/en-us/windows/desktop/api/wuapi/nf-wuapi-iupdatesession-createupdateinstaller installerRaw, err := s.CallMethod("CreateUpdateInstaller") if err != nil { return fmt.Errorf("error calling method CreateUpdateInstaller on IUpdateSession: %v", err) } installer := installerRaw.ToIDispatch() defer installer.Release() if _, err := installer.PutProperty("Updates", updates.IDispatch); err != nil { return fmt.Errorf("error calling PutProperty Updates on IUpdateInstaller: %v", err) } // TODO: Look into using the async methods and attempt to track/log progress. if _, err := installer.CallMethod("Install"); err != nil { return fmt.Errorf("error calling method Install on IUpdateInstaller: %v", err) } return nil } // GetWUAUpdateCollection queries the Windows Update Agent API searcher with the provided query // and returns a IUpdateCollection. func (s *IUpdateSession) GetWUAUpdateCollection(query string) (*IUpdateCollection, error) { // returns IUpdateSearcher // https://msdn.microsoft.com/en-us/library/windows/desktop/aa386515(v=vs.85).aspx searcherRaw, err := s.CallMethod("CreateUpdateSearcher") if err != nil { return nil, fmt.Errorf("error calling CreateUpdateSearcher: %v", err) } searcher := searcherRaw.ToIDispatch() defer searcher.Release() // returns ISearchResult // https://msdn.microsoft.com/en-us/library/windows/desktop/aa386077(v=vs.85).aspx resultRaw, err := searcher.CallMethod("Search", query) if err != nil { return nil, fmt.Errorf("error calling method Search on IUpdateSearcher: %v", err) } result := resultRaw.ToIDispatch() defer result.Release() // returns IUpdateCollection // https://msdn.microsoft.com/en-us/library/windows/desktop/aa386107(v=vs.85).aspx updtsRaw, err := result.GetProperty("Updates") if err != nil { return nil, fmt.Errorf("error calling GetProperty Updates on ISearchResult: %v", err) } return &IUpdateCollection{IDispatch: updtsRaw.ToIDispatch()}, nil } osconfig-20210219.00/packages/yum.go000066400000000000000000000162301401404514100167140ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "bytes" "context" "fmt" "os/exec" "runtime" "github.com/GoogleCloudPlatform/osconfig/osinfo" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( yum string yumInstallArgs = []string{"install", "--assumeyes"} yumRemoveArgs = []string{"remove", "--assumeyes"} yumCheckUpdateArgs = []string{"check-update", "--assumeyes"} yumListUpdatesArgs = []string{"update", "--assumeno", "--cacheonly", "--color=never"} yumListUpdateMinimalArgs = []string{"update-minimal", "--assumeno", "--cacheonly", "--color=never"} ) func init() { if runtime.GOOS != "windows" { yum = "/usr/bin/yum" } YumExists = util.Exists(yum) } type yumUpdateOpts struct { security bool minimal bool excludes []string } // YumUpdateOption is an option for yum update. type YumUpdateOption func(*yumUpdateOpts) // YumUpdateSecurity returns a YumUpdateOption that specifies the --security flag should // be used. func YumUpdateSecurity(security bool) YumUpdateOption { return func(args *yumUpdateOpts) { args.security = security } } // YumUpdateMinimal returns a YumUpdateOption that specifies the update-minimal // command should be used. func YumUpdateMinimal(minimal bool) YumUpdateOption { return func(args *yumUpdateOpts) { args.minimal = minimal } } // YumExcludes returns a YumUpdateOption that specifies the excludes // command should be used. func YumExcludes(excludes []string) YumUpdateOption { return func(args *yumUpdateOpts) { args.excludes = excludes } } // InstallYumPackages installs yum packages. func InstallYumPackages(ctx context.Context, pkgs []string) error { _, err := run(ctx, yum, append(yumInstallArgs, pkgs...)) return err } // RemoveYumPackages removes yum packages. func RemoveYumPackages(ctx context.Context, pkgs []string) error { _, err := run(ctx, yum, append(yumRemoveArgs, pkgs...)) return err } func parseYumUpdates(data []byte) []PkgInfo { /* Last metadata expiration check: 0:11:22 ago on Tue 12 Nov 2019 12:13:38 AM UTC. Dependencies resolved. ================================================================================================================================================================================= Package Arch Version Repository Size ================================================================================================================================================================================= Installing: kernel x86_64 2.6.32-754.24.3.el6 updates 32 M Updating: google-compute-engine noarch 1:20190916.00-g2.el6 google-compute-engine 18 k kernel-firmware noarch 2.6.32-754.24.3.el6 updates 29 M libudev x86_64 147-2.74.el6_10 updates 78 k nspr x86_64 4.21.0-1.el6_10 updates 114 k google-cloud-sdk noarch 270.0.0-1 google-cloud-sdk 36 M Transaction Summary ================================================================================================================================================================================= Upgrade 5 Packages Total download size: 36 M Operation aborted. */ lines := bytes.Split(bytes.TrimSpace(data), []byte("\n")) var pkgs []PkgInfo var upgrading bool for _, ln := range lines { pkg := bytes.Fields(ln) if len(pkg) == 0 { continue } // Continue until we see the installing/upgrading section. // Yum has this as Updating, dnf is Upgrading. if string(pkg[0]) == "Upgrading:" || string(pkg[0]) == "Updating:" || string(pkg[0]) == "Installing:" { upgrading = true continue } else if !upgrading { continue } // A package line should have 6 fields, break unless this is a 'replacing' entry. if len(pkg) < 6 { if string(pkg[0]) == "replacing" { continue } break } pkgs = append(pkgs, PkgInfo{Name: string(pkg[0]), Arch: osinfo.Architecture(string(pkg[1])), Version: string(pkg[2])}) } return pkgs } // YumUpdates queries for all available yum updates. func YumUpdates(ctx context.Context, opts ...YumUpdateOption) ([]PkgInfo, error) { // We just use check-update to ensure all repo keys are synced as we run // update with --assumeno. stdout, stderr, err := runner.Run(ctx, exec.Command(yum, yumCheckUpdateArgs...)) // Exit code 0 means no updates, 100 means there are updates. if err == nil { return nil, nil } if exitErr, ok := err.(*exec.ExitError); ok { if exitErr.ExitCode() == 100 { err = nil } } // Since we don't get good error codes from 'yum update' exit now if there is an issue. if err != nil { return nil, fmt.Errorf("error running %s with args %q: %v, stdout: %q, stderr: %q", yum, yumCheckUpdateArgs, err, stdout, stderr) } return listAndParseYumPackages(ctx, opts...) } func listAndParseYumPackages(ctx context.Context, opts ...YumUpdateOption) ([]PkgInfo, error) { yumOpts := &yumUpdateOpts{ security: false, minimal: false, excludes: []string{}, } for _, opt := range opts { opt(yumOpts) } args := yumListUpdatesArgs if yumOpts.minimal { args = yumListUpdateMinimalArgs } if yumOpts.security { args = append(args, "--security") } if len(yumOpts.excludes) > 0 { for _, pkg := range yumOpts.excludes { args = append(args, []string{"--exclude", pkg}...) } } stdout, stderr, err := ptyrunner.Run(ctx, exec.Command(yum, args...)) if err != nil { return nil, fmt.Errorf("error running %s with args %q: %v, stdout: %q, stderr: %q", yum, args, err, stdout, stderr) } if stdout == nil { return nil, nil } pkgs := parseYumUpdates(stdout) if len(pkgs) == 0 { // This means we could not parse any packages and instead got an error from yum. return nil, fmt.Errorf("error checking for yum updates, non-zero error code from 'yum update' but no packages parsed, stdout: %q", stdout) } return pkgs, nil } osconfig-20210219.00/packages/yum_test.go000066400000000000000000000221221401404514100177500ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "context" "errors" "os" "os/exec" "reflect" "testing" utilmocks "github.com/GoogleCloudPlatform/osconfig/util/mocks" "github.com/golang/mock/gomock" ) func TestInstallYumPackages(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(yum, append(yumInstallArgs, pkgs...)...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) if err := InstallYumPackages(testCtx, pkgs); err != nil { t.Errorf("unexpected error: %v", err) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("could not update")).Times(1) if err := InstallYumPackages(testCtx, pkgs); err == nil { t.Errorf("did not get expected error") } } func TestRemoveYum(t *testing.T) { ctx := context.Background() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(yum, append(yumRemoveArgs, pkgs...)...) mockCommandRunner.EXPECT().Run(ctx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) if err := RemoveYumPackages(ctx, pkgs); err != nil { t.Errorf("unexpected error: %v", err) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("removal error")).Times(1) if err := RemoveYumPackages(testCtx, pkgs); err == nil { t.Errorf("did not get expected error") } } func TestYumUpdates(t *testing.T) { data := []byte(` ================================================================================================================================================================================= Package Arch Version Repository Size ================================================================================================================================================================================= Installing: kernel x86_64 2.6.32-754.24.3.el6 updates 32 M replacing kernel.x86_64 1.0.0-4 Upgrading: foo noarch 2.0.0-1 BaseOS 361 k bar x86_64 2.0.0-1 repo 10 M Obsoleting: baz noarch 2.0.0-1 repo 10 M `) if os.Getenv("EXIT100") == "1" { os.Exit(100) } cmd := exec.Command(os.Args[0], "-test.run=TestYumUpdates") cmd.Env = append(os.Environ(), "EXIT100=1") errExit100 := cmd.Run() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner ptyrunner = mockCommandRunner expectedCheckUpdate := exec.Command(yum, yumCheckUpdateArgs...) // Test Error t.Run("Error", func(t *testing.T) { mockCommandRunner.EXPECT().Run(testCtx, expectedCheckUpdate).Return(data, []byte("stderr"), errors.New("Bad error")).Times(1) if _, err := YumUpdates(testCtx); err == nil { t.Errorf("did not get expected error") } }) // yum check-updates exit code 0 t.Run("ExitCode0", func(t *testing.T) { mockCommandRunner.EXPECT().Run(testCtx, expectedCheckUpdate).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) ret, err := YumUpdates(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } if ret != nil { t.Errorf("unexpected return: %v", ret) } }) // Test no options t.Run("NoOptions", func(t *testing.T) { expectedCmd := exec.Command(yum, yumListUpdatesArgs...) first := mockCommandRunner.EXPECT().Run(testCtx, expectedCheckUpdate).Return(data, []byte("stderr"), errExit100).Times(1) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).After(first).Return(data, []byte("stderr"), nil).Times(1) ret, err := YumUpdates(testCtx) if err != nil { t.Errorf("did not expect error: %v", err) } allPackageNames := []string{"kernel", "foo", "bar"} for _, pkg := range ret { if !contains(allPackageNames, pkg.Name) { t.Errorf("package %s expected to be present.", pkg.Name) } } }) // Test MinimalWithSecurity t.Run("MinimalWithSecurity", func(t *testing.T) { expectedCmd := exec.Command(yum, append(yumListUpdateMinimalArgs, "--security")...) first := mockCommandRunner.EXPECT().Run(testCtx, expectedCheckUpdate).Return(data, []byte("stderr"), errExit100).Times(1) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).After(first).Return(data, []byte("stderr"), nil).Times(1) ret, err := YumUpdates(testCtx, YumUpdateMinimal(true), YumUpdateSecurity(true)) if err != nil { t.Errorf("did not expect error: %v", err) } allPackageNames := []string{"kernel", "foo", "bar"} for _, pkg := range ret { if !contains(allPackageNames, pkg.Name) { t.Errorf("package %s expected to be present.", pkg.Name) } } }) // Test WithSecurityWithExcludes t.Run("WithSecurityWithExcludes", func(t *testing.T) { // the mock data returned by mockcommandrunner will not include this // package anyways. The purpose of this test is to make sure that // when customer specifies excluded packages, we set the --exclude flag // in the yum command. excludedPackages := []string{"ex-pkg1", "ex-pkg2"} expectedCmd := exec.Command(yum, append(yumListUpdatesArgs, "--security", "--exclude", excludedPackages[0], "--exclude", excludedPackages[1])...) first := mockCommandRunner.EXPECT().Run(testCtx, expectedCheckUpdate).Return(data, []byte("stderr"), errExit100).Times(1) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).After(first).Return(data, []byte("stderr"), nil).Times(1) ret, err := YumUpdates(testCtx, YumUpdateMinimal(false), YumUpdateSecurity(true), YumExcludes(excludedPackages)) if err != nil { t.Errorf("did not expect error: %v", err) } allPackageNames := []string{"kernel", "foo", "bar"} for _, pkg := range ret { if !contains(allPackageNames, pkg.Name) { t.Errorf("package %s expected to be present.", pkg.Name) } } }) } func contains(names []string, name string) bool { for _, n := range names { if n == name { return true } } return false } func TestParseYumUpdates(t *testing.T) { data := []byte(` ================================================================================================================================================================================= Package Arch Version Repository Size ================================================================================================================================================================================= Installing: kernel x86_64 2.6.32-754.24.3.el6 updates 32 M replacing kernel.x86_64 1.0.0-4 Upgrading: foo noarch 2.0.0-1 BaseOS 361 k bar x86_64 2.0.0-1 repo 10 M Obsoleting: baz noarch 2.0.0-1 repo 10 M `) tests := []struct { name string data []byte want []PkgInfo }{ {"NormalCase", data, []PkgInfo{{"kernel", "x86_64", "2.6.32-754.24.3.el6"}, {"foo", "all", "2.0.0-1"}, {"bar", "x86_64", "2.0.0-1"}}}, {"NoPackages", []byte("nothing here"), nil}, {"nil", nil, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parseYumUpdates(tt.data); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseYumUpdates() = %v, want %v", got, tt.want) } }) } } osconfig-20210219.00/packages/zypper.go000066400000000000000000000301111401404514100174250ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "bytes" "context" "fmt" "os/exec" "regexp" "runtime" "strconv" "strings" "github.com/GoogleCloudPlatform/osconfig/osinfo" "github.com/GoogleCloudPlatform/osconfig/util" ) var ( zypper string // zypperInstallArgs is zypper command to install patches, packages zypperInstallArgs = []string{"--gpg-auto-import-keys", "--non-interactive", "install", "--auto-agree-with-licenses"} zypperRemoveArgs = []string{"--non-interactive", "remove"} zypperListUpdatesArgs = []string{"--gpg-auto-import-keys", "-q", "list-updates"} zypperListPatchesArgs = []string{"--gpg-auto-import-keys", "-q", "list-patches"} zypperPatchInfoArgs = []string{"info", "-t", "patch"} ) func init() { if runtime.GOOS != "windows" { zypper = "/usr/bin/zypper" } ZypperExists = util.Exists(zypper) } type zypperListPatchOpts struct { categories []string severities []string withOptional bool all bool } // ZypperListOption is patch list options type ZypperListOption func(opts *zypperListPatchOpts) // ZypperListPatchCategories is zypper list option to provide category filter func ZypperListPatchCategories(categories []string) ZypperListOption { return func(args *zypperListPatchOpts) { args.categories = categories } } // ZypperListPatchSeverities is zypper list option to provide severity filter func ZypperListPatchSeverities(severities []string) ZypperListOption { return func(args *zypperListPatchOpts) { args.severities = severities } } // ZypperListPatchWithOptional is zypper list option to also list optional patches func ZypperListPatchWithOptional(withOptional bool) ZypperListOption { return func(args *zypperListPatchOpts) { args.withOptional = withOptional } } // ZypperListPatchAll is zypper list option to all all patches func ZypperListPatchAll(all bool) ZypperListOption { return func(args *zypperListPatchOpts) { args.all = all } } // InstallZypperPackages Installs zypper packages func InstallZypperPackages(ctx context.Context, pkgs []string) error { _, err := run(ctx, zypper, append(zypperInstallArgs, pkgs...)) return err } // ZypperInstall installs zypper patches and packages func ZypperInstall(ctx context.Context, patches []ZypperPatch, pkgs []PkgInfo) error { args := zypperInstallArgs // https://www.mankier.com/8/zypper#Concepts-Package_Types use patch install // for single patch and package installs for _, patch := range patches { args = append(args, "patch:"+patch.Name) } for _, pkg := range pkgs { args = append(args, "package:"+pkg.Name) } stdout, stderr, err := runner.Run(ctx, exec.Command(zypper, args...)) // https://en.opensuse.org/SDB:Zypper_manual#EXIT_CODES if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { // ZYPPER_EXIT_INF_REBOOT_NEEDED if exitErr.ExitCode() == 102 { err = nil } } else { err = fmt.Errorf("error running %s with args %q: %v, stdout: %q, stderr: %q", zypper, args, err, stdout, stderr) } } return err } // RemoveZypperPackages installed Zypper packages. func RemoveZypperPackages(ctx context.Context, pkgs []string) error { _, err := run(ctx, zypper, append(zypperRemoveArgs, pkgs...)) return err } func parseZypperUpdates(data []byte) []PkgInfo { /* S | Repository | Name | Current Version | Available Version | Arch --+---------------------+------------------------+-----------------+-------------------+------- v | SLES12-SP3-Updates | at | 3.1.14-7.3 | 3.1.14-8.3.1 | x86_64 v | SLES12-SP3-Updates | autoyast2-installation | 3.2.17-1.3 | 3.2.22-2.9.2 | noarch ... */ lines := bytes.Split(bytes.TrimSpace(data), []byte("\n")) var pkgs []PkgInfo for _, ln := range lines { pkg := bytes.Split(ln, []byte("|")) if len(pkg) != 6 || string(bytes.TrimSpace(pkg[0])) != "v" { continue } name := string(bytes.TrimSpace(pkg[2])) arch := string(bytes.TrimSpace(pkg[5])) ver := string(bytes.TrimSpace(pkg[4])) pkgs = append(pkgs, PkgInfo{Name: name, Arch: osinfo.Architecture(arch), Version: ver}) } return pkgs } // ZypperUpdates queries for all available zypper updates. func ZypperUpdates(ctx context.Context) ([]PkgInfo, error) { out, err := run(ctx, zypper, zypperListUpdatesArgs) if err != nil { return nil, err } return parseZypperUpdates(out), nil } func parseZypperPatches(data []byte) ([]ZypperPatch, []ZypperPatch) { /* Repository | Name | Category | Severity | Interactive | Status | Summary ------------------------------------+---------------------------------------------+-------------+-----------+-------------+------------+------------------------------------------------------------ SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1206 | security | low | --- | applied | Security update for bzip2 SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1221 | security | moderate | --- | applied | Security update for libxslt SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1229 | recommended | moderate | --- | not needed | Recommended update for sensors SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1258 | recommended | moderate | --- | needed | Recommended update for postfix */ lines := bytes.Split(bytes.TrimSpace(data), []byte("\n")) var ins []ZypperPatch var avail []ZypperPatch for _, ln := range lines { patch := bytes.Split(ln, []byte("|")) if len(patch) != 7 { continue } name := string(bytes.TrimSpace(patch[1])) cat := string(bytes.TrimSpace(patch[2])) sev := string(bytes.TrimSpace(patch[3])) status := string(bytes.TrimSpace(patch[5])) sum := string(bytes.TrimSpace(patch[6])) switch status { case "needed": avail = append(avail, ZypperPatch{Name: name, Category: cat, Severity: sev, Summary: sum}) case "applied": ins = append(ins, ZypperPatch{Name: name, Category: cat, Severity: sev, Summary: sum}) default: continue } } return ins, avail } func zypperPatches(ctx context.Context, opts ...ZypperListOption) ([]byte, error) { zOpts := &zypperListPatchOpts{ categories: nil, severities: nil, withOptional: false, all: false, } for _, opt := range opts { opt(zOpts) } args := zypperListPatchesArgs for _, c := range zOpts.categories { args = append(args, "--category="+c) } for _, s := range zOpts.severities { args = append(args, "--severity="+s) } if zOpts.withOptional { args = append(args, "--with-optional") } // As per zypper's current implementation, // --all is ignored if we have any filters on any other // field. if zOpts.all || (len(zOpts.severities)+len(zOpts.categories)) <= 0 { args = append(args, "--all") } return run(ctx, zypper, args) } // ZypperPatches queries for all available zypper patches. func ZypperPatches(ctx context.Context, opts ...ZypperListOption) ([]ZypperPatch, error) { out, err := zypperPatches(ctx, opts...) if err != nil { return nil, err } _, patches := parseZypperPatches(out) return patches, nil } // ZypperInstalledPatches queries for all installed zypper patches. func ZypperInstalledPatches(ctx context.Context, opts ...ZypperListOption) ([]ZypperPatch, error) { out, err := zypperPatches(ctx, opts...) if err != nil { return nil, err } patches, _ := parseZypperPatches(out) return patches, nil } func zypperPatchInfo(ctx context.Context, patches []string) ([]byte, error) { args := zypperPatchInfoArgs for _, name := range patches { args = append(args, name) } return run(ctx, zypper, args) } func parseZypperPatchInfo(out []byte) (map[string][]string, error) { /* Loading repository data... Reading installed packages... Information for patch SUSE-SLE-SERVER-12-SP4-2019-2974: ------------------------------------------------------- Repository : SLES12-SP4-Updates Name : SUSE-SLE-SERVER-12-SP4-2019-2974 Version : 1 Arch : noarch Vendor : maint-coord@suse.de Status : needed Category : recommended Severity : important Created On : Thu Nov 14 13:17:48 2019 Interactive : --- Summary : Recommended update for irqbalance Description : This update for irqbalance fixes the following issues: - Irqbalanced spreads the IRQs between the available virtual machines. (bsc#1119465, bsc#1154905) Provides : patch:SUSE-SLE-SERVER-12-SP4-2019-2974 = 1 Conflicts : [2] irqbalance.src < 1.1.0-9.3.1 irqbalance.x86_64 < 1.1.0-9.3.1 */ patchInfo := make(map[string][]string) var validConflictLine = regexp.MustCompile(`\s*Conflicts\s*:\s*\[\d*\]\s*`) var conflictLineExtract = regexp.MustCompile(`\[[0-9]+\]`) var nameLine = regexp.MustCompile(`\s*Name\s*:\s*`) lines := bytes.Split(bytes.TrimSpace(out), []byte("\n")) i := 0 for { // find the name line for ; i < len(lines); i++ { b := nameLine.Find([]byte(lines[i])) if b != nil { break } } if i >= len(lines) { // we do not have any more patch info blobs break } parts := strings.Split(string(lines[i]), ":") i++ if len(parts) != 2 { return nil, fmt.Errorf("invalid name output") } patchName := strings.Trim(parts[1], " ") for ; i < len(lines); i++ { b := validConflictLine.Find([]byte(lines[i])) if b != nil { // Conflicts : [2] break } } if i >= len(lines) { // did not find conflicting packages // this should not happen return nil, nil } matches := conflictLineExtract.FindAllString(string(lines[i]), -1) if len(matches) != 1 { return nil, fmt.Errorf("invalid patch info") } // get the number of package lines to parse conflicts := strings.Trim(matches[0], "[") conflicts = strings.Trim(conflicts, "]") conflictLines, err := strconv.Atoi(conflicts) if err != nil { return nil, fmt.Errorf("invalid patch info: invalid conflict info") } ctr := i + 1 ctrEnd := ctr + conflictLines for ; ctr < ctrEnd; ctr++ { //libsolv.src < 0.6.36-2.27.19.8 //libsolv-tools.x86_64 < 0.6.36-2.27.19.8 //libzypp.src < 16.20.2-27.60.4 //libzypp.x86_64 < 16.20.2-27.60.4 //perl-solv.x86_64 < 0.6.36-2.27.19.8 //python-solv.x86_64 < 0.6.36-2.27.19.8 //zypper.src < 1.13.54-18.40.2 //zypper.x86_64 < 1.13.54-18.40.2 //zypper-log.noarch < 1.13.54-18.40.2 parts := strings.Split(string(lines[ctr]), "<") if len(parts) != 2 { return nil, fmt.Errorf("invalid package info") } nameArch := strings.Split(parts[0], ".") if len(nameArch) != 2 { return nil, fmt.Errorf("invalid package info") } pkgName := strings.Trim(nameArch[0], " ") patches, ok := patchInfo[pkgName] if !ok { patches = make([]string, 0) } patches = append(patches, patchName) patchInfo[pkgName] = patches } // set i for next patch information blob i = ctrEnd } // TODO: instead of returning a map of // make it more concrete type returns with more information // about the package and patch if len(patchInfo) == 0 { return nil, fmt.Errorf("invalid patch information, did not find patch blobs") } return patchInfo, nil } // ZypperPackagesInPatch returns the list of patches, a package upgrade belongs to func ZypperPackagesInPatch(ctx context.Context, patches []ZypperPatch) (map[string][]string, error) { var patchNames []string for _, patch := range patches { patchNames = append(patchNames, patch.Name) } out, err := zypperPatchInfo(ctx, patchNames) if err != nil { return nil, err } return parseZypperPatchInfo(out) } osconfig-20210219.00/packages/zypper_test.go000066400000000000000000000303231401404514100204710ustar00rootroot00000000000000// Copyright 2019 Google 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 packages import ( "errors" "os/exec" "reflect" "strings" "testing" utilmocks "github.com/GoogleCloudPlatform/osconfig/util/mocks" "github.com/golang/mock/gomock" ) func TestZypperInstalls(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(zypper, append(zypperInstallArgs, pkgs...)...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) if err := InstallZypperPackages(testCtx, pkgs); err != nil { t.Errorf("unexpected error: %v", err) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("error")).Times(1) if err := InstallZypperPackages(testCtx, pkgs); err == nil { t.Errorf("did not get expected error") } } func TestRemoveZypper(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(zypper, append(zypperRemoveArgs, pkgs...)...) mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), nil).Times(1) if err := RemoveZypperPackages(testCtx, pkgs); err != nil { t.Errorf("unexpected error: %v", err) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("error")).Times(1) if err := RemoveZypperPackages(testCtx, pkgs); err == nil { t.Errorf("unexpected error: %v", err) } } func TestParseZypperUpdates(t *testing.T) { normalCase := `S | Repository | Name | Current Version | Available Version | Arch --+---------------------+------------------------+-----------------+-------------------+------- v | SLES12-SP3-Updates | at | 3.1.14-7.3 | 3.1.14-8.3.1 | x86_64 v | SLES12-SP3-Updates | autoyast2-installation | 3.2.17-1.3 | 3.2.22-2.9.2 | noarch this is junk data` tests := []struct { name string data []byte want []PkgInfo }{ {"NormalCase", []byte(normalCase), []PkgInfo{{"at", "x86_64", "3.1.14-8.3.1"}, {"autoyast2-installation", "all", "3.2.22-2.9.2"}}}, {"NoPackages", []byte("nothing here"), nil}, {"nil", nil, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parseZypperUpdates(tt.data); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseZypperUpdates() = %v, want %v", got, tt.want) } }) } } func TestZypperUpdates(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(zypper, zypperListUpdatesArgs...) data := []byte("v | SLES12-SP3-Updates | at | 3.1.14-7.3 | 3.1.14-8.3.1 | x86_64") mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return(data, []byte("stderr"), nil).Times(1) ret, err := ZypperUpdates(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } want := []PkgInfo{{"at", "x86_64", "3.1.14-8.3.1"}} if !reflect.DeepEqual(ret, want) { t.Errorf("ZypperUpdates() = %v, want %v", ret, want) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("error")).Times(1) if _, err := ZypperUpdates(testCtx); err == nil { t.Errorf("did not get expected error") } } func TestParseZypperPatches(t *testing.T) { normalCase := `Repository | Name | Category | Severity | Interactive | Status | Summary ------------------------------------+---------------------------------------------+-------------+-----------+-------------+------------+------------------------------------------------------------ SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1206 | security | low | --- | applied | Security update for bzip2 SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1221 | security | moderate | --- | needed | Security update for libxslt SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1229 | recommended | moderate | --- | not needed | Recommended update for sensors SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1258 | recommended | moderate | --- | needed | Recommended update for postfix some junk data` tests := []struct { name string data []byte wantIns []ZypperPatch wantAvail []ZypperPatch }{ { "NormalCase", []byte(normalCase), []ZypperPatch{{"SUSE-SLE-Module-Basesystem-15-SP1-2019-1206", "security", "low", "Security update for bzip2"}}, []ZypperPatch{{"SUSE-SLE-Module-Basesystem-15-SP1-2019-1221", "security", "moderate", "Security update for libxslt"}, {"SUSE-SLE-Module-Basesystem-15-SP1-2019-1258", "recommended", "moderate", "Recommended update for postfix"}}, }, {"NoPackages", []byte("nothing here"), nil, nil}, {"nil", nil, nil, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotIns, gotAvail := parseZypperPatches(tt.data) if !reflect.DeepEqual(gotIns, tt.wantIns) { t.Errorf("parseZypperPatches() = %v, want %v", gotIns, tt.wantIns) } if !reflect.DeepEqual(gotAvail, tt.wantAvail) { t.Errorf("parseZypperPatches() = %v, want %v", gotAvail, tt.wantAvail) } }) } } func TestZypperPatches(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(zypper, append(zypperListPatchesArgs, "--all")...) data := []byte("SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1258 | recommended | moderate | --- | needed | Recommended update for postfix") mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return(data, []byte("stderr"), nil).Times(1) ret, err := ZypperPatches(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } want := []ZypperPatch{{"SUSE-SLE-Module-Basesystem-15-SP1-2019-1258", "recommended", "moderate", "Recommended update for postfix"}} if !reflect.DeepEqual(ret, want) { t.Errorf("ZypperPatches() = %v, want %v", ret, want) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("error")).Times(1) if _, err := ZypperPatches(testCtx); err == nil { t.Errorf("did not get expected error") } } func TestZypperInstalledPatches(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommandRunner := utilmocks.NewMockCommandRunner(mockCtrl) runner = mockCommandRunner expectedCmd := exec.Command(zypper, append(zypperListPatchesArgs, "--all")...) data := []byte("SLE-Module-Basesystem15-SP1-Updates | SUSE-SLE-Module-Basesystem-15-SP1-2019-1258 | recommended | moderate | --- | applied | Recommended update for postfix") mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return(data, []byte("stderr"), nil).Times(1) ret, err := ZypperInstalledPatches(testCtx) if err != nil { t.Errorf("unexpected error: %v", err) } want := []ZypperPatch{{"SUSE-SLE-Module-Basesystem-15-SP1-2019-1258", "recommended", "moderate", "Recommended update for postfix"}} if !reflect.DeepEqual(ret, want) { t.Errorf("ZypperInstalledPatches() = %v, want %v", ret, want) } mockCommandRunner.EXPECT().Run(testCtx, expectedCmd).Return([]byte("stdout"), []byte("stderr"), errors.New("error")).Times(1) if _, err := ZypperInstalledPatches(testCtx); err == nil { t.Errorf("did not get expected error") } } func TestParsePatchInfo(t *testing.T) { patchInfo := ` Loading repository data... Reading installed packages... Information for patch SUSE-SLE-SERVER-12-SP4-2019-2974: ------------------------------------------------------- Repository : SLES12-SP4-Updates Name : SUSE-SLE-SERVER-12-SP4-2019-2974 Version : 1 Arch : noarch Vendor : maint-coord@suse.de Status : needed Category : recommended Severity : important Created On : Thu Nov 14 13:17:48 2019 Interactive : --- Summary : Recommended update for irqbalance Description : This update for irqbalance fixes the following issues: - Irqbalanced spreads the IRQs between the available virtual machines. (bsc#1119465, bsc#1154905) Provides : patch:SUSE-SLE-SERVER-12-SP4-2019-2974 = 1 Conflicts : [4] irqbalance.src < 1.1.0-9.3.1 irqbalance.x86_64 < 1.1.0-9.3.1 common-package.src < 1.1.0-9.3.1 common-package.x86_64 < 1.1.0-9.3.1 Information for patch SUSE-SLE-Module-Public-Cloud-12-2019-2026: ---------------------------------------------------------------- Repository : SLE-Module-Public-Cloud12-Updates Name : SUSE-SLE-Module-Public-Cloud-12-2019-2026 Version : 1 Arch : noarch Vendor : maint-coord@suse.de Status : needed Category : recommended Severity : moderate Created On : Tue Jul 30 17:20:02 2019 Interactive : --- Summary : Recommended update for Azure Python SDK Description : This update brings the following python modules for the Azure Python SDK: - python-Flask - python-Werkzeug - python-click - python-decorator - python-httpbin - python-idna - python-itsdangerous - python-py - python-pytest-httpbin - python-pytest-mock - python-requests Provides : patch:SUSE-SLE-Module-Public-Cloud-12-2019-2026 = 1 Conflicts : [32] python-Flask.noarch < 0.12.1-7.4.2 python-Flask.src < 0.12.1-7.4.2 python-Werkzeug.noarch < 0.12.2-10.4.2 python-Werkzeug.src < 0.12.2-10.4.2 python-click.noarch < 6.7-2.4.2 python-click.src < 6.7-2.4.2 python-decorator.noarch < 4.1.2-4.4.2 python-decorator.src < 4.1.2-4.4.2 python-httpbin.noarch < 0.5.0-2.4.2 python-httpbin.src < 0.5.0-2.4.2 python-idna.noarch < 2.5-3.10.2 python-idna.src < 2.5-3.10.2 python-itsdangerous.noarch < 0.24-7.4.2 python-itsdangerous.src < 0.24-7.4.2 python-py.noarch < 1.5.2-8.8.2 python-py.src < 1.5.2-8.8.2 python-requests.noarch < 2.18.2-8.4.2 python-requests.src < 2.18.2-8.4.2 python-six.noarch < 1.11.0-9.21.2 python-six.src < 1.11.0-9.21.2 python3-Flask.noarch < 0.12.1-7.4.2 python3-Werkzeug.noarch < 0.12.2-10.4.2 python3-click.noarch < 6.7-2.4.2 python3-decorator.noarch < 4.1.2-4.4.2 python3-httpbin.noarch < 0.5.0-2.4.2 python3-idna.noarch < 2.5-3.10.2 python3-itsdangerous.noarch < 0.24-7.4.2 python3-py.noarch < 1.5.2-8.8.2 python3-requests.noarch < 2.18.2-8.4.2 python3-six.noarch < 1.11.0-9.21.2 common-package.src < 1.1.0-9.3.1 common-package.x86_64 < 1.1.0-9.3.1 ` ppMap, err := parseZypperPatchInfo([]byte(patchInfo)) if err != nil { t.Errorf("unexpected error: %+v", err) } if _, ok := ppMap["python3-requests"]; !ok { t.Errorf("Unexpected result: expected a patch for python3-requests") } if _, ok := ppMap["random-package"]; ok { t.Errorf("Unexpected result: did not expect patch for random-package") } if _, ok := ppMap["random-package"]; ok { t.Errorf("Unexpected result: did not expect patch for random-package") } if patches, ok := ppMap["common-package"]; !ok { t.Errorf("Unexpected result: did not expect patch for common-package") for _, patch := range patches { if (strings.Compare(patch, "SUSE-SLE-Module-Public-Cloud-12-2019-2026") != 0) || (strings.Compare(patch, "SUSE-SLE-SERVER-12-SP4-2019-2974") != 0) { t.Errorf("Unexptected result: patch name should be one of SUSE-SLE-SERVER-12-SP4-2019-2974 or SUSE-SLE-Module-Public-Cloud-12-2019-2026") } } } } osconfig-20210219.00/packaging/000077500000000000000000000000001401404514100157175ustar00rootroot00000000000000osconfig-20210219.00/packaging/debian/000077500000000000000000000000001401404514100171415ustar00rootroot00000000000000osconfig-20210219.00/packaging/debian/changelog000066400000000000000000000002441401404514100210130ustar00rootroot00000000000000google-osconfig-agent (1:20191216.00-g1) stable; urgency=medium * Move to Beta API. -- Google Cloud Team Tue, 16 Dec 2019 12:00:00 +0000 osconfig-20210219.00/packaging/debian/compat000066400000000000000000000000031401404514100203400ustar00rootroot0000000000000010 osconfig-20210219.00/packaging/debian/control000066400000000000000000000007461401404514100205530ustar00rootroot00000000000000Source: google-osconfig-agent Maintainer: Google Cloud Team Section: misc Priority: optional Standards-Version: 3.9.8 Build-Depends: debhelper (>= 10), dh-golang (>= 1.1), golang-go Package: google-osconfig-agent Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: Google Compute Engine OSConfig Agent Contains the OSConfig agent service binary as well as systemd startup scripts XS-Go-Import-Path: github.com/GoogleCloudPlatform/osconfig osconfig-20210219.00/packaging/debian/copyright000066400000000000000000000017261401404514100211020ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: google-compute-engine Upstream-Contact: gc-team@google.com Files: * Copyright: Copyright 2017 Google Inc. License: Apache-2.0 Files: debian/* Copyright: Copyright 2017 Google Inc. License: Apache-2.0 License: Apache-2.0 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. . On Debian systems, the complete text of the Apache version 2.0 license can be found in "/usr/share/common-licenses/Apache-2.0". osconfig-20210219.00/packaging/debian/google-osconfig-agent.docs000066400000000000000000000000261401404514100241660ustar00rootroot00000000000000THIRD_PARTY_LICENSES/ osconfig-20210219.00/packaging/debian/postrm000066400000000000000000000001561401404514100204120ustar00rootroot00000000000000#!/bin/sh set -e if [ $1 = upgrade ]; then touch /etc/osconfig/osconfig_agent_restart_required fi exit 0 osconfig-20210219.00/packaging/debian/rules000077500000000000000000000023031401404514100202170ustar00rootroot00000000000000#!/usr/bin/make -f export PATH := /tmp/go/bin:$(PATH) export SHELL := env PATH=$(PATH) /bin/bash export DH_OPTIONS export DH_GOPKG := github.com/GoogleCloudPlatform/osconfig export DH_GOLANG_EXCLUDES := e2e_tests/ export CGO_ENABLED := 0 export GO111MODULE := on export GOPROXY := https://proxy.golang.org export GOCACHE := /tmp/.cache %: dh $@ --buildsystem=golang --with=golang,systemd override_dh_auto_install: # Binary-only package. dh_auto_install -- --no-source mv debian/google-osconfig-agent/usr/bin/osconfig debian/google-osconfig-agent/usr/bin/google_osconfig_agent install -d debian/google-osconfig-agent/etc/osconfig install -d debian/google-osconfig-agent/lib/systemd/system install -p -m 0644 *.service debian/google-osconfig-agent/lib/systemd/system/ override_dh_golang: # We don't use any packaged dependencies, so skip dh_golang step. override_dh_auto_test: # Skip tests as they are already setup as part of the commit process. override_dh_auto_build: dh_auto_build -O--buildsystem=golang -- -ldflags="-s -w -X main.version=$(VERSION)-$(RELEASE)" -mod=readonly override_dh_installinit: override_dh_systemd_start: dh_systemd_start --no-restart-after-upgrade --no-restart-on-upgrade osconfig-20210219.00/packaging/debian/source/000077500000000000000000000000001401404514100204415ustar00rootroot00000000000000osconfig-20210219.00/packaging/debian/source/format000066400000000000000000000000141401404514100216470ustar00rootroot000000000000003.0 (quilt) osconfig-20210219.00/packaging/googet/000077500000000000000000000000001401404514100172035ustar00rootroot00000000000000osconfig-20210219.00/packaging/googet/google-osconfig-agent.goospec000066400000000000000000000021651401404514100247450ustar00rootroot00000000000000{ "name": "google-osconfig-agent", {{$package_version := printf "%s.0+win@1" .version -}} "version": "{{$package_version}}", "arch": "x86_64", "authors": "Google Inc.", "license": "http://www.apache.org/licenses/LICENSE-2.0", "description": "Google OSConfig agent", "source": "https://github.com/GoogleCloudPlatform/osconfig", "files": { "google_osconfig_agent.exe": "/Google/OSConfig/google_osconfig_agent.exe", "THIRD_PARTY_LICENSES": "/Google/OSConfig/THIRD_PARTY_LICENSES/", "LICENSE": "/Google/OSConfig/LICENSE.txt" }, "install": { "path": "packaging/googet/install.ps1" }, "uninstall": { "path": "packaging/googet/uninstall.ps1" }, "sources": [{ "include": [ "google_osconfig_agent.exe", "packaging/googet/install.ps1", "packaging/googet/uninstall.ps1", "THIRD_PARTY_LICENSES/**", "LICENSE" ] }], "build": { "linux": "/bin/bash", "linuxArgs": ["-c", "GOOS=windows $GO build -ldflags='-s -w -X main.version={{$package_version}}' -mod=readonly -o google_osconfig_agent.exe"] } } osconfig-20210219.00/packaging/googet/install.ps1000066400000000000000000000034721401404514100213040ustar00rootroot00000000000000# Copyright 2018 Google 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. $ErrorActionPreference = 'Stop' function Set-ServiceConfig { # Restart service after 1s, then 2s. Reset error counter after 60s. sc.exe failure google_osconfig_agent reset= 60 actions= restart/1000/restart/2000 # Set dependency and delayed start sc.exe config google_osconfig_agent depend= "rpcss" start= delayed-auto # Create trigger to start the service on first IP address sc.exe triggerinfo google_osconfig_agent start/networkon } try { if (-not (Get-Service 'google_osconfig_agent' -ErrorAction SilentlyContinue)) { New-Service -DisplayName 'Google OSConfig Agent' ` -Name 'google_osconfig_agent' ` -BinaryPathName '"C:\Program Files\Google\OSConfig\google_osconfig_agent.exe"' ` -StartupType Automatic ` -Description 'Google OSConfig service agent' Set-ServiceConfig Start-Service google_osconfig_agent -Verbose -ErrorAction Stop } else { Set-ServiceConfig New-Item -Path 'C:\Program Files\Google\OSConfig\osconfig_agent_restart_required' -Force -Type File -ErrorAction SilentlyContinue | Out-Null } } catch { Write-Output $_.InvocationInfo.PositionMessage Write-Output "Install failed: $($_.Exception.Message)" exit 1 } osconfig-20210219.00/packaging/googet/uninstall.ps1000066400000000000000000000012611401404514100216410ustar00rootroot00000000000000# Copyright 2018 Google 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. Stop-Service google_osconfig_agent -Verbose & sc.exe delete google_osconfig_agent osconfig-20210219.00/packaging/google-osconfig-agent.spec000066400000000000000000000046621401404514100227600ustar00rootroot00000000000000# Copyright 2018 Google 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. # Don't build debuginfo packages. %global debug_package %{nil} Name: google-osconfig-agent Epoch: 1 Version: %{_version} Release: g1%{?dist} Summary: Google Compute Engine guest environment. License: ASL 2.0 Url: https://github.com/GoogleCloudPlatform/osconfig Source0: %{name}_%{version}.orig.tar.gz BuildArch: %{_arch} %if ! 0%{?el6} BuildRequires: systemd %endif %description Contains the OSConfig agent binary and startup scripts %prep %autosetup %build GOPATH=%{_gopath} CGO_ENABLED=0 %{_go} build -ldflags="-s -w -X main.version=%{version}-%{release}" -mod=readonly -o google_osconfig_agent %install install -d "%{buildroot}/%{_docdir}/%{name}" cp -r THIRD_PARTY_LICENSES "%buildroot/%_docdir/%name/THIRD_PARTY_LICENSES" install -d %{buildroot}%{_bindir} install -d %{buildroot}/etc/osconfig install -p -m 0755 google_osconfig_agent %{buildroot}%{_bindir}/google_osconfig_agent %if 0%{?el6} install -d %{buildroot}/etc/init install -p -m 0644 %{name}.conf %{buildroot}/etc/init %else install -d %{buildroot}%{_unitdir} install -d %{buildroot}%{_presetdir} install -p -m 0644 %{name}.service %{buildroot}%{_unitdir} install -p -m 0644 90-%{name}.preset %{buildroot}%{_presetdir}/90-%{name}.preset %endif %files %{_docdir}/%{name} %defattr(-,root,root,-) %{_bindir}/google_osconfig_agent %if 0%{?el6} /etc/init/%{name}.conf %else %{_unitdir}/%{name}.service %{_presetdir}/90-%{name}.preset %endif %post %if 0%{?el6} if [ $1 -eq 1 ]; then # Start the service on first install start -q -n google-osconfig-agent fi %else %systemd_post google-osconfig-agent.service if [ $1 -eq 1 ]; then # Start the service on first install systemctl start google-osconfig-agent.service fi if [ $1 -eq 2 ]; then touch /etc/osconfig/osconfig_agent_restart_required fi %preun %systemd_preun google-osconfig-agent.service %postun %systemd_postun google-osconfig-agent.service %endif osconfig-20210219.00/policies/000077500000000000000000000000001401404514100156025ustar00rootroot00000000000000osconfig-20210219.00/policies/apt.go000066400000000000000000000151101401404514100167130ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "bytes" "context" "errors" "fmt" "io" "net/http" "sort" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) var debArchiveTypeMap = map[agentendpointpb.AptRepository_ArchiveType]string{ agentendpointpb.AptRepository_DEB: "deb", agentendpointpb.AptRepository_DEB_SRC: "deb-src", } const aptGPGFile = "/etc/apt/trusted.gpg.d/osconfig_agent_managed.gpg" func getAptGPGKey(key string) (*openpgp.Entity, error) { resp, err := http.Get(key) if err != nil { return nil, err } defer resp.Body.Close() if resp.ContentLength > 1024*1024 { return nil, fmt.Errorf("key size of %d too large", resp.ContentLength) } var buf bytes.Buffer tee := io.TeeReader(resp.Body, &buf) b, err := armor.Decode(tee) if err != nil && err != io.EOF { return nil, err } if b == nil { return openpgp.ReadEntity(packet.NewReader(&buf)) } return openpgp.ReadEntity(packet.NewReader(b.Body)) } func containsEntity(es []*openpgp.Entity, e *openpgp.Entity) bool { for _, entity := range es { if entity.PrimaryKey.Fingerprint == e.PrimaryKey.Fingerprint { return true } } return false } func aptRepositories(ctx context.Context, repos []*agentendpointpb.AptRepository, repoFile string) error { var es []*openpgp.Entity var keys []string for _, repo := range repos { key := repo.GetGpgKey() if key == "" { continue } keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { e, err := getAptGPGKey(key) if err != nil { clog.Errorf(ctx, "Error fetching gpg key %q: %v", key, err) continue } if !containsEntity(es, e) { es = append(es, e) } } if len(es) > 0 { var buf bytes.Buffer for _, e := range es { if err := e.Serialize(&buf); err != nil { clog.Errorf(ctx, "Error serializing gpg key: %v", err) } } if err := writeIfChanged(ctx, buf.Bytes(), aptGPGFile); err != nil { clog.Errorf(ctx, "Error writing gpg key: %v", err) } } /* # Repo file managed by Google OSConfig agent deb http://repo1-url/ repo1 main deb http://repo1-url/ repo2 main contrib non-free */ var buf bytes.Buffer buf.WriteString("# Repo file managed by Google OSConfig agent\n") for _, repo := range repos { archiveType, ok := debArchiveTypeMap[repo.ArchiveType] if !ok { archiveType = "deb" } line := fmt.Sprintf("\n%s %s %s", archiveType, repo.Uri, repo.Distribution) for _, c := range repo.Components { line = fmt.Sprintf("%s %s", line, c) } buf.WriteString(line + "\n") } return writeIfChanged(ctx, buf.Bytes(), repoFile) } func aptChanges(ctx context.Context, aptInstalled, aptRemoved, aptUpdated []*agentendpointpb.Package) error { var err error var errs []string var installed []packages.PkgInfo if len(aptInstalled) > 0 || len(aptUpdated) > 0 || len(aptRemoved) > 0 { installed, err = packages.InstalledDebPackages(ctx) if err != nil { return err } } var updates []packages.PkgInfo if len(aptUpdated) > 0 { updates, err = packages.AptUpdates(ctx, packages.AptGetUpgradeType(packages.AptGetDistUpgrade), packages.AptGetUpgradeShowNew(false)) if err != nil { return err } } changes := getNecessaryChanges(installed, updates, aptInstalled, aptRemoved, aptUpdated) if changes.packagesToInstall != nil { // run apt-get update to update to latest changes. if _, err := packages.AptUpdate(ctx); err != nil { clog.Errorf(ctx, "Error running apt-get update") } clog.Infof(ctx, "Installing packages %s", changes.packagesToInstall) if err := packages.InstallAptPackages(ctx, changes.packagesToInstall); err != nil { clog.Errorf(ctx, "Error installing apt packages: %v", err) // Try fallback logic to install the packages individually. clog.Infof(ctx, "Trying to install packages individually") var installPkgErrs []string for _, pkg := range changes.packagesToInstall { if err = packages.InstallAptPackages(ctx, []string{pkg}); err != nil { installPkgErrs = append(installPkgErrs, fmt.Sprintf("Error installing apt package: %v. Error details: %v", pkg, err)) } } if len(installPkgErrs) > 0 { errorString := strings.Join(installPkgErrs, "\n") clog.Errorf(ctx, "Error installing apt packages individually: %v", errorString) errs = append(errs, fmt.Sprintf("error installing apt packages: %v", errorString)) } } } else { clog.Debugf(ctx, "No packages to install.") } if changes.packagesToUpgrade != nil { clog.Infof(ctx, "Upgrading packages %s", changes.packagesToUpgrade) if err := packages.InstallAptPackages(ctx, changes.packagesToUpgrade); err != nil { clog.Errorf(ctx, "Error upgrading apt packages: %v", err) errs = append(errs, fmt.Sprintf("error upgrading apt packages: %v", err)) } } else { clog.Debugf(ctx, "No packages to upgrade.") } if changes.packagesToRemove != nil { clog.Infof(ctx, "Removing packages %s", changes.packagesToRemove) if err := packages.RemoveAptPackages(ctx, changes.packagesToRemove); err != nil { clog.Errorf(ctx, "Error removing apt packages: %v", err) // Try fallback logic to remove the packages individually. clog.Infof(ctx, "Trying to remove packages individually") var removePkgErrs []string for _, pkg := range changes.packagesToRemove { if err = packages.RemoveAptPackages(ctx, []string{pkg}); err != nil { removePkgErrs = append(removePkgErrs, fmt.Sprintf("Error removing apt package: %v. Error details: %v", pkg, err)) } } if len(removePkgErrs) > 0 { errorString := strings.Join(removePkgErrs, "\n") clog.Errorf(ctx, "Error removing apt packages individually: %v", errorString) errs = append(errs, fmt.Sprintf("error removing apt packages: %v", errorString)) } } } else { clog.Debugf(ctx, "No packages to remove.") } if errs == nil { return nil } return errors.New(strings.Join(errs, ",\n")) } osconfig-20210219.00/policies/apt_test.go000066400000000000000000000051461401404514100177620ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "testing" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) func runAptRepositories(ctx context.Context, repos []*agentendpointpb.AptRepository) (string, error) { td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { return "", fmt.Errorf("error creating temp dir: %v", err) } defer os.RemoveAll(td) testRepo := filepath.Join(td, "testRepo") if err := aptRepositories(ctx, repos, testRepo); err != nil { return "", fmt.Errorf("error running aptRepositories: %v", err) } data, err := ioutil.ReadFile(testRepo) if err != nil { return "", fmt.Errorf("error reading testRepo: %v", err) } return string(data), nil } func TestAptRepositories(t *testing.T) { tests := []struct { desc string repos []*agentendpointpb.AptRepository want string }{ {"no repos", []*agentendpointpb.AptRepository{}, "# Repo file managed by Google OSConfig agent\n"}, { "1 repo", []*agentendpointpb.AptRepository{ {Uri: "http://repo1-url/", Distribution: "distribution", Components: []string{"component1"}}, }, "# Repo file managed by Google OSConfig agent\n\ndeb http://repo1-url/ distribution component1\n", }, { "2 repos", []*agentendpointpb.AptRepository{ {Uri: "http://repo1-url/", Distribution: "distribution", Components: []string{"component1"}, ArchiveType: agentendpointpb.AptRepository_DEB_SRC}, {Uri: "http://repo2-url/", Distribution: "distribution", Components: []string{"component1", "component2"}, ArchiveType: agentendpointpb.AptRepository_DEB}, }, "# Repo file managed by Google OSConfig agent\n\ndeb-src http://repo1-url/ distribution component1\n\ndeb http://repo2-url/ distribution component1 component2\n", }, } for _, tt := range tests { got, err := runAptRepositories(context.Background(), tt.repos) if err != nil { t.Fatal(err) } if got != tt.want { t.Errorf("%s: got:\n%q\nwant:\n%q", tt.desc, got, tt.want) } } } osconfig-20210219.00/policies/changes.go000066400000000000000000000044531401404514100175470ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "github.com/GoogleCloudPlatform/osconfig/packages" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) // changes represents the delta between the actual and the desired package installation state. type changes struct { packagesToInstall []string packagesToUpgrade []string packagesToRemove []string } // getNecessaryChanges compares the current state and the desired state to determine which packages // need to be installed, upgraded, or removed. func getNecessaryChanges(installedPkgs []packages.PkgInfo, upgradablePkgs []packages.PkgInfo, installPkgs, removePkgs, updatePkgs []*agentendpointpb.Package) changes { installedPkgMap := make(map[string]bool) for _, pkg := range installedPkgs { installedPkgMap[pkg.Name] = true } upgradeablePkgMap := make(map[string]bool) for _, pkg := range upgradablePkgs { upgradeablePkgMap[pkg.Name] = true } var pkgsToInstall []string var pkgsToRemove []string var pkgsToUpgrade []string for _, pkg := range installPkgs { if _, ok := installedPkgMap[pkg.Name]; !ok { pkgsToInstall = append(pkgsToInstall, pkg.Name) } } for _, pkg := range removePkgs { if _, ok := installedPkgMap[pkg.Name]; ok { pkgsToRemove = append(pkgsToRemove, pkg.Name) } } for _, pkg := range updatePkgs { if _, ok := upgradeablePkgMap[pkg.Name]; ok { pkgsToUpgrade = append(pkgsToUpgrade, pkg.Name) continue } // If not installed we need to install it. if _, ok := installedPkgMap[pkg.Name]; !ok { pkgsToInstall = append(pkgsToInstall, pkg.Name) } } return changes{ packagesToInstall: pkgsToInstall, packagesToUpgrade: pkgsToUpgrade, packagesToRemove: pkgsToRemove, } } osconfig-20210219.00/policies/changes_test.go000066400000000000000000000100321401404514100205740ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "reflect" "testing" "github.com/GoogleCloudPlatform/osconfig/packages" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) func TestGetNecessaryChanges(t *testing.T) { tests := [...]struct { name string installedPkgs []packages.PkgInfo upgradablePkgs []packages.PkgInfo installPkgs []*agentendpointpb.Package removePkgs []*agentendpointpb.Package updatePkgs []*agentendpointpb.Package want changes }{ { name: "install from empty", installedPkgs: createPkgInfos(), upgradablePkgs: createPkgInfos(), installPkgs: createPackages("foo"), removePkgs: createPackages(), updatePkgs: createPackages(), want: changes{ packagesToInstall: []string{"foo"}, packagesToUpgrade: []string{}, packagesToRemove: []string{}, }, }, { name: "upgrade from empty", installedPkgs: createPkgInfos(), upgradablePkgs: createPkgInfos(), installPkgs: createPackages(), removePkgs: createPackages(), updatePkgs: createPackages("foo"), want: changes{ packagesToInstall: []string{"foo"}, packagesToUpgrade: []string{}, packagesToRemove: []string{}, }, }, { name: "single upgrade", installedPkgs: createPkgInfos("foo"), upgradablePkgs: createPkgInfos("foo"), installPkgs: createPackages(), removePkgs: createPackages(), updatePkgs: createPackages("foo"), want: changes{ packagesToInstall: []string{}, packagesToUpgrade: []string{"foo"}, packagesToRemove: []string{}, }, }, { name: "remove", installedPkgs: createPkgInfos("foo"), upgradablePkgs: createPkgInfos("foo"), installPkgs: createPackages(), removePkgs: createPackages("foo"), updatePkgs: createPackages(), want: changes{ packagesToInstall: []string{}, packagesToUpgrade: []string{}, packagesToRemove: []string{"foo"}, }, }, { name: "mixed", installedPkgs: createPkgInfos("foo", "bar", "buz"), upgradablePkgs: createPkgInfos("bar", "boo"), installPkgs: createPackages("foo", "baz"), removePkgs: createPackages("buz"), updatePkgs: createPackages("bar"), want: changes{ packagesToInstall: []string{"baz"}, packagesToUpgrade: []string{"bar"}, packagesToRemove: []string{"buz"}, }, }, } for _, tt := range tests { got := getNecessaryChanges(tt.installedPkgs, tt.upgradablePkgs, tt.installPkgs, tt.removePkgs, tt.updatePkgs) if !equalChanges(&got, &tt.want) { t.Errorf("Did not get expected changes for '%s', got: %v, want: %v", tt.name, got, tt.want) } } } func equalChanges(got *changes, want *changes) bool { return equalSlices(got.packagesToInstall, want.packagesToInstall) && equalSlices(got.packagesToRemove, want.packagesToRemove) && equalSlices(got.packagesToUpgrade, want.packagesToUpgrade) } func equalSlices(got []string, want []string) bool { if len(got) == 0 && len(want) == 0 { return true } return reflect.DeepEqual(got, want) } func createPkgInfos(names ...string) []packages.PkgInfo { var res []packages.PkgInfo for _, n := range names { res = append(res, packages.PkgInfo{Name: n}) } return res } func createPackages(names ...string) []*agentendpointpb.Package { var res []*agentendpointpb.Package for _, n := range names { res = append(res, &agentendpointpb.Package{Name: n}) } return res } osconfig-20210219.00/policies/googet.go000066400000000000000000000056331401404514100174240ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "bytes" "context" "errors" "fmt" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) func googetRepositories(ctx context.Context, repos []*agentendpointpb.GooRepository, repoFile string) error { /* # Repo file managed by Google OSConfig agent - name: repo1-name url: https://repo1-url - name: repo1-name url: https://repo2-url */ var buf bytes.Buffer buf.WriteString("# Repo file managed by Google OSConfig agent\n") for _, repo := range repos { buf.WriteString(fmt.Sprintf("\n- name: %s\n", repo.Name)) buf.WriteString(fmt.Sprintf(" url: %s\n", repo.Url)) } return writeIfChanged(ctx, buf.Bytes(), repoFile) } func googetChanges(ctx context.Context, gooInstalled, gooRemoved, gooUpdated []*agentendpointpb.Package) error { var err error var errs []string var installed []packages.PkgInfo if len(gooInstalled) > 0 || len(gooUpdated) > 0 || len(gooRemoved) > 0 { installed, err = packages.InstalledGooGetPackages(ctx) if err != nil { return err } } var updates []packages.PkgInfo if len(gooUpdated) > 0 { updates, err = packages.GooGetUpdates(ctx) if err != nil { return err } } changes := getNecessaryChanges(installed, updates, gooInstalled, gooRemoved, gooUpdated) if changes.packagesToInstall != nil { clog.Infof(ctx, "Installing packages %s", changes.packagesToInstall) if err := packages.InstallGooGetPackages(ctx, changes.packagesToInstall); err != nil { errs = append(errs, fmt.Sprintf("error installing googet packages: %v", err)) } } if changes.packagesToUpgrade != nil { clog.Infof(ctx, "Upgrading packages %s", changes.packagesToUpgrade) if err := packages.InstallGooGetPackages(ctx, changes.packagesToUpgrade); err != nil { errs = append(errs, fmt.Sprintf("error upgrading googet packages: %v", err)) } } if changes.packagesToRemove != nil { clog.Infof(ctx, "Removing packages %s", changes.packagesToRemove) if err := packages.RemoveGooGetPackages(ctx, changes.packagesToRemove); err != nil { errs = append(errs, fmt.Sprintf("error removing googet packages: %v", err)) } } if errs == nil { return nil } return errors.New(strings.Join(errs, ",\n")) } osconfig-20210219.00/policies/googet_test.go000066400000000000000000000045071401404514100204620ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "testing" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) func runGooGetRepositories(ctx context.Context, repos []*agentendpointpb.GooRepository) (string, error) { td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { return "", fmt.Errorf("error creating temp dir: %v", err) } defer os.RemoveAll(td) testRepo := filepath.Join(td, "testRepo") if err := googetRepositories(ctx, repos, testRepo); err != nil { return "", fmt.Errorf("error running googetRepositories: %v", err) } data, err := ioutil.ReadFile(testRepo) if err != nil { return "", fmt.Errorf("error reading testRepo: %v", err) } return string(data), nil } func TestGooGetRepositories(t *testing.T) { tests := []struct { desc string repos []*agentendpointpb.GooRepository want string }{ {"no repos", []*agentendpointpb.GooRepository{}, "# Repo file managed by Google OSConfig agent\n"}, { "1 repo", []*agentendpointpb.GooRepository{ {Url: "http://repo1-url/", Name: "name"}, }, "# Repo file managed by Google OSConfig agent\n\n- name: name\n url: http://repo1-url/\n", }, { "2 repos", []*agentendpointpb.GooRepository{ {Url: "http://repo1-url/", Name: "name1"}, {Url: "http://repo2-url/", Name: "name2"}, }, "# Repo file managed by Google OSConfig agent\n\n- name: name1\n url: http://repo1-url/\n\n- name: name2\n url: http://repo2-url/\n", }, } for _, tt := range tests { got, err := runGooGetRepositories(context.Background(), tt.repos) if err != nil { t.Fatal(err) } if got != tt.want { t.Errorf("%s: got:\n%q\nwant:\n%q", tt.desc, got, tt.want) } } } osconfig-20210219.00/policies/local.go000066400000000000000000000106641401404514100172320ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "context" "encoding/json" "cloud.google.com/go/compute/metadata" "github.com/GoogleCloudPlatform/osconfig/clog" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) // localConfig represents the structure of the config to the JSON parser. // // The types of members of the struct are wrappers for protobufs and delegate // the parsing to protojson lib via their UnmarshalJSON implementations. type localConfig struct { Packages []*pkg PackageRepositories []*packageRepository SoftwareRecipes []*softwareRecipe } type pkg struct { agentendpointpb.Package } func (r *pkg) UnmarshalJSON(b []byte) error { un := &protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true} return un.Unmarshal(b, &r.Package) } type packageRepository struct { agentendpointpb.PackageRepository } func (r *packageRepository) UnmarshalJSON(b []byte) error { un := &protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true} return un.Unmarshal(b, &r.PackageRepository) } type softwareRecipe struct { agentendpointpb.SoftwareRecipe } func (r *softwareRecipe) UnmarshalJSON(b []byte) error { un := &protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true} return un.Unmarshal(b, &r.SoftwareRecipe) } func readLocalConfig(ctx context.Context) (*localConfig, error) { s, err := metadata.Get("/instance/attributes/gce-software-declaration") if err != nil { clog.Debugf(ctx, "No local config: %v", err) return nil, nil } var lc localConfig return &lc, json.Unmarshal([]byte(s), &lc) } // GetId returns a repository Id that is used to group repositories for // override by higher priotiry policy(-ies). // For repositories that have no such Id, GetId returns "", in which // case the repository is never overridden. func getID(repo *agentendpointpb.PackageRepository) string { switch repo.Repository.(type) { case *agentendpointpb.PackageRepository_Yum: return "yum-" + repo.GetYum().GetId() case *agentendpointpb.PackageRepository_Zypper: return "zypper-" + repo.GetZypper().GetId() default: return "" } } // mergeConfigs merges the local config with the lookup response, giving priority to the lookup // result. If both arguments are nil, returns an empty policy. func mergeConfigs(local *localConfig, egp *agentendpointpb.EffectiveGuestPolicy) *agentendpointpb.EffectiveGuestPolicy { if egp == nil { egp = &agentendpointpb.EffectiveGuestPolicy{} } if local == nil { return egp } // Ids that are in the maps below repos := make(map[string]bool) pkgs := make(map[string]bool) recipes := make(map[string]bool) for _, v := range egp.GetPackages() { pkgs[v.Package.Name] = true } for _, v := range egp.GetPackageRepositories() { if id := getID(v.GetPackageRepository()); id != "" { repos[id] = true } } for _, v := range egp.GetSoftwareRecipes() { recipes[v.SoftwareRecipe.Name] = true } for _, v := range local.Packages { if _, ok := pkgs[v.Name]; !ok { sp := new(agentendpointpb.EffectiveGuestPolicy_SourcedPackage) sp.Package = &v.Package egp.Packages = append(egp.Packages, sp) } } for _, v := range local.PackageRepositories { id := getID(&v.PackageRepository) if id != "" { if _, ok := repos[id]; ok { continue } } sr := new(agentendpointpb.EffectiveGuestPolicy_SourcedPackageRepository) sr.PackageRepository = &v.PackageRepository egp.PackageRepositories = append(egp.PackageRepositories, sr) } for _, v := range local.SoftwareRecipes { if _, ok := recipes[v.Name]; !ok { sp := new(agentendpointpb.EffectiveGuestPolicy_SourcedSoftwareRecipe) sp.SoftwareRecipe = proto.Clone(&v.SoftwareRecipe).(*agentendpointpb.SoftwareRecipe) egp.SoftwareRecipes = append(egp.SoftwareRecipes, sp) } } return egp } osconfig-20210219.00/policies/local_test.go000066400000000000000000000063321401404514100202660ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "encoding/json" "testing" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" "google.golang.org/protobuf/encoding/prototext" ) const sampleConfig = ` { "packages": [ { "name": "my-package", "desiredState": "INSTALLED", "manager": "APT" }, { "name": "my-other-package", "desired_state": "INSTALLED", "manager": "APT" } ], "packageRepositories": [ { "apt": { "uri": "http://packages.cloud.google.com/apt", "archiveType": "DEB", "distribution": "google-cloud-monitoring-stretch", "components": [ "main" ], "gpgKey": "https://packages.cloud.google.com/apt/doc/apt-key.gpg" } }, { "yum": { "id": "my-yum", "display_name": "my-yum-name", "base_url": "http://my-base-url", "gpg_keys": [ "https://packages.cloud.google.com/apt/doc/apt-key.gpg" ] } } ], "softwareRecipes": [ { "name": "install-envoy", "desired_state": "INSTALLED", "installSteps": [ { "scriptRun": { "script": "" } } ] }, { "name": "install-something", "desired_state": "INSTALLED", "installSteps": [ { "scriptRun": { "script": "" } } ] } ] }` func TestMerging(t *testing.T) { s := []byte(sampleConfig) var lc localConfig if err := json.Unmarshal([]byte(s), &lc); err != nil { t.Errorf("Got error: %v", err) return } var pr agentendpointpb.EffectiveGuestPolicy var sr agentendpointpb.EffectiveGuestPolicy_SourcedSoftwareRecipe sr.Source = "policy1" sr.SoftwareRecipe = new(agentendpointpb.SoftwareRecipe) sr.SoftwareRecipe.Name = "install-something" sr.SoftwareRecipe.DesiredState = agentendpointpb.DesiredState_REMOVED pr.SoftwareRecipes = append(pr.SoftwareRecipes, &sr) pr2 := mergeConfigs(&lc, &pr) var wantmap = map[string]agentendpointpb.DesiredState{ "install-something": agentendpointpb.DesiredState_REMOVED, "install-envoy": agentendpointpb.DesiredState_INSTALLED, } for _, ssr := range pr2.SoftwareRecipes { gotState := ssr.SoftwareRecipe.DesiredState wantState, ok := wantmap[ssr.SoftwareRecipe.Name] if !ok { t.Errorf("Recipe ame: %s unexpected.", ssr.SoftwareRecipe.Name) continue } if gotState != wantState { t.Errorf("Recipe: %s got state: %d want state: %d", ssr.SoftwareRecipe.Name, gotState, wantState) } } rs := pr2.SoftwareRecipes[0].SoftwareRecipe.DesiredState want := agentendpointpb.DesiredState_REMOVED if rs != want { txt, _ := prototext.Marshal(pr2) t.Logf("Merged: %s", txt) t.Errorf("Wrong recipe state. Got: %d(%s), want: %d(%s).", rs, rs.String(), want, want.String()) } } osconfig-20210219.00/policies/policies.go000066400000000000000000000214671401404514100177520ustar00rootroot00000000000000// Copyright 2018 Google 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 policies configures OS Guest Policies based on osconfig API response. package policies import ( "bytes" "context" "crypto/sha256" "hash" "io" "os" "time" "github.com/GoogleCloudPlatform/osconfig/agentconfig" "github.com/GoogleCloudPlatform/osconfig/agentendpoint" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/GoogleCloudPlatform/osconfig/policies/recipes" "github.com/GoogleCloudPlatform/osconfig/retryutil" "github.com/GoogleCloudPlatform/osconfig/tasker" "github.com/GoogleCloudPlatform/osconfig/util" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) func run(ctx context.Context) { var resp *agentendpointpb.EffectiveGuestPolicy client, err := agentendpoint.NewBetaClient(ctx) if err != nil { clog.Errorf(ctx, "agentendpoint.NewBetaClient Error: %v", err) } else { defer client.Close() resp, err = client.LookupEffectiveGuestPolicies(ctx) if err != nil { clog.Errorf(ctx, "Error running LookupEffectiveGuestPolicies: %v", err) } } local, err := readLocalConfig(ctx) if err != nil { clog.Errorf(ctx, "Error reading local software config: %v", err) } effective := mergeConfigs(local, resp) // We don't check the error from setConfig or installRecipes as all errors are already logged. setConfig(ctx, effective) installRecipes(ctx, effective) } // Run looks up osconfigs and applies them using tasker.Enqueue. func Run(ctx context.Context) { tasker.Enqueue(ctx, "Run GuestPolicies", func() { run(ctx) }) } func installRecipes(ctx context.Context, egp *agentendpointpb.EffectiveGuestPolicy) error { for _, recipe := range egp.GetSoftwareRecipes() { if r := recipe.GetSoftwareRecipe(); r != nil { if err := recipes.InstallRecipe(ctx, r); err != nil { clog.Errorf(ctx, "Error installing recipe: %v", err) } } } return nil } func setConfig(ctx context.Context, egp *agentendpointpb.EffectiveGuestPolicy) { var aptRepos []*agentendpointpb.AptRepository var yumRepos []*agentendpointpb.YumRepository var zypperRepos []*agentendpointpb.ZypperRepository var gooRepos []*agentendpointpb.GooRepository for _, repo := range egp.GetPackageRepositories() { if r := repo.GetPackageRepository().GetGoo(); r != nil { gooRepos = append(gooRepos, r) continue } if r := repo.GetPackageRepository().GetApt(); r != nil { aptRepos = append(aptRepos, r) continue } if r := repo.GetPackageRepository().GetYum(); r != nil { yumRepos = append(yumRepos, r) continue } if r := repo.GetPackageRepository().GetZypper(); r != nil { zypperRepos = append(zypperRepos, r) continue } } var gooInstallPkgs, gooRemovePkgs, gooUpdatePkgs []*agentendpointpb.Package var aptInstallPkgs, aptRemovePkgs, aptUpdatePkgs []*agentendpointpb.Package var yumInstallPkgs, yumRemovePkgs, yumUpdatePkgs []*agentendpointpb.Package var zypperInstallPkgs, zypperRemovePkgs, zypperUpdatePkgs []*agentendpointpb.Package for _, pkg := range egp.GetPackages() { switch pkg.GetPackage().GetManager() { case agentendpointpb.Package_ANY, agentendpointpb.Package_MANAGER_UNSPECIFIED: switch pkg.GetPackage().GetDesiredState() { case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED: gooInstallPkgs = append(gooInstallPkgs, pkg.GetPackage()) aptInstallPkgs = append(aptInstallPkgs, pkg.GetPackage()) yumInstallPkgs = append(yumInstallPkgs, pkg.GetPackage()) zypperInstallPkgs = append(zypperInstallPkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_REMOVED: gooRemovePkgs = append(gooRemovePkgs, pkg.GetPackage()) aptRemovePkgs = append(aptRemovePkgs, pkg.GetPackage()) yumRemovePkgs = append(yumRemovePkgs, pkg.GetPackage()) zypperRemovePkgs = append(zypperRemovePkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_UPDATED: gooUpdatePkgs = append(gooUpdatePkgs, pkg.GetPackage()) aptUpdatePkgs = append(aptUpdatePkgs, pkg.GetPackage()) yumUpdatePkgs = append(yumUpdatePkgs, pkg.GetPackage()) zypperUpdatePkgs = append(zypperUpdatePkgs, pkg.GetPackage()) } case agentendpointpb.Package_GOO: switch pkg.GetPackage().GetDesiredState() { case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED: gooInstallPkgs = append(gooInstallPkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_REMOVED: gooRemovePkgs = append(gooRemovePkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_UPDATED: gooUpdatePkgs = append(gooUpdatePkgs, pkg.GetPackage()) } case agentendpointpb.Package_APT: switch pkg.GetPackage().GetDesiredState() { case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED: aptInstallPkgs = append(aptInstallPkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_REMOVED: aptRemovePkgs = append(aptRemovePkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_UPDATED: aptUpdatePkgs = append(aptUpdatePkgs, pkg.GetPackage()) } case agentendpointpb.Package_YUM: switch pkg.GetPackage().GetDesiredState() { case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED: yumInstallPkgs = append(yumInstallPkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_REMOVED: yumRemovePkgs = append(yumRemovePkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_UPDATED: yumUpdatePkgs = append(yumUpdatePkgs, pkg.GetPackage()) } case agentendpointpb.Package_ZYPPER: switch pkg.GetPackage().GetDesiredState() { case agentendpointpb.DesiredState_INSTALLED, agentendpointpb.DesiredState_DESIRED_STATE_UNSPECIFIED: zypperInstallPkgs = append(zypperInstallPkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_REMOVED: zypperRemovePkgs = append(zypperRemovePkgs, pkg.GetPackage()) case agentendpointpb.DesiredState_UPDATED: zypperUpdatePkgs = append(zypperUpdatePkgs, pkg.GetPackage()) } } } if packages.GooGetExists { if err := googetRepositories(ctx, gooRepos, agentconfig.GooGetRepoFilePath()); err != nil { clog.Errorf(ctx, "Error writing googet repo file: %v", err) } if err := retryutil.RetryFunc(ctx, 1*time.Minute, "Applying googet changes", func() error { return googetChanges(ctx, gooInstallPkgs, gooRemovePkgs, gooUpdatePkgs) }); err != nil { clog.Errorf(ctx, "Error performing googet changes: %v", err) } } if packages.AptExists { if err := aptRepositories(ctx, aptRepos, agentconfig.AptRepoFilePath()); err != nil { clog.Errorf(ctx, "Error writing apt repo file: %v", err) } if err := retryutil.RetryFunc(ctx, 1*time.Minute, "Applying apt changes", func() error { return aptChanges(ctx, aptInstallPkgs, aptRemovePkgs, aptUpdatePkgs) }); err != nil { clog.Errorf(ctx, "Error performing apt changes: %v", err) } } if packages.YumExists { if err := yumRepositories(ctx, yumRepos, agentconfig.YumRepoFilePath()); err != nil { clog.Errorf(ctx, "Error writing yum repo file: %v", err) } if err := retryutil.RetryFunc(ctx, 1*time.Minute, "Applying yum changes", func() error { return yumChanges(ctx, yumInstallPkgs, yumRemovePkgs, yumUpdatePkgs) }); err != nil { clog.Errorf(ctx, "Error performing yum changes: %v", err) } } if packages.ZypperExists { if err := zypperRepositories(ctx, zypperRepos, agentconfig.ZypperRepoFilePath()); err != nil { clog.Errorf(ctx, "Error writing zypper repo file: %v", err) } if err := retryutil.RetryFunc(ctx, 1*time.Minute, "Applying zypper changes.", func() error { return zypperChanges(ctx, zypperInstallPkgs, zypperRemovePkgs, zypperUpdatePkgs) }); err != nil { clog.Errorf(ctx, "Error performing zypper changes: %v", err) } } } func checksum(r io.Reader) hash.Hash { hash := sha256.New() io.Copy(hash, r) return hash } func writeIfChanged(ctx context.Context, content []byte, path string) error { file, err := os.Open(path) if err != nil && !os.IsNotExist(err) { return err } if !os.IsNotExist(err) { reader := bytes.NewReader(content) h1 := checksum(reader) h2 := checksum(file) file.Close() if bytes.Equal(h1.Sum(nil), h2.Sum(nil)) { return nil } } clog.Infof(ctx, "Writing repo file %s with updated contents", path) return util.AtomicWrite(path, content, 0644) } osconfig-20210219.00/policies/recipes/000077500000000000000000000000001401404514100172345ustar00rootroot00000000000000osconfig-20210219.00/policies/recipes/artifacts.go000066400000000000000000000070501401404514100215450ustar00rootroot00000000000000// Copyright 2019 Google 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 recipes import ( "context" "fmt" "io" "net/http" "net/url" "path" "path/filepath" "cloud.google.com/go/storage" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/external" "github.com/GoogleCloudPlatform/osconfig/util" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) // fetchArtifacts takes in a slice of artifacts and downloads them into the specified directory, // Returns a map of artifact names to their new locations on the local disk. func fetchArtifacts(ctx context.Context, artifacts []*agentendpointpb.SoftwareRecipe_Artifact, directory string) (map[string]string, error) { localNames := make(map[string]string) for _, a := range artifacts { clog.Debugf(ctx, "Downloading artifact: %q", a) path, err := fetchArtifact(ctx, a, directory) if err != nil { return nil, err } localNames[a.Id] = path } return localNames, nil } func fetchArtifact(ctx context.Context, artifact *agentendpointpb.SoftwareRecipe_Artifact, directory string) (string, error) { var checksum, extension string var reader io.ReadCloser switch { case artifact.GetGcs() != nil: gcs := artifact.GetGcs() extension = path.Ext(gcs.Object) cl, err := storage.NewClient(ctx) if err != nil { return "", fmt.Errorf("error creating gcs client: %v", err) } reader, err = external.FetchGCSObject(ctx, cl, gcs.Bucket, gcs.Object, gcs.Generation) if err != nil { return "", fmt.Errorf("error fetching artifact %q from GCS: %v", artifact.Id, err) } defer reader.Close() case artifact.GetRemote() != nil: remote := artifact.GetRemote() uri, err := url.Parse(remote.Uri) if err != nil { return "", fmt.Errorf("Could not parse url %q for artifact %q", remote.Uri, artifact.Id) } extension = path.Ext(uri.Path) checksum = remote.Checksum cl := &http.Client{} reader, err = getHTTPArtifact(cl, *uri) if err != nil { return "", fmt.Errorf("error fetching artifact %q: %v", artifact.Id, err) } defer reader.Close() default: return "", fmt.Errorf("unknown artifact type for artifact %v", artifact.Id) } localPath := getStoragePath(directory, artifact.Id, extension) if _, err := util.AtomicWriteFileStream(reader, checksum, localPath, 0600); err != nil { return "", fmt.Errorf("Error downloading stream: %v", err) } return localPath, nil } func getHTTPArtifact(client *http.Client, uri url.URL) (io.ReadCloser, error) { if !isSupportedURL(uri) { return nil, fmt.Errorf("error, unsupported protocol scheme %s", uri.Scheme) } reader, err := external.FetchRemoteObjectHTTP(client, uri.String()) if err != nil { return nil, err } return reader, nil } func isSupportedURL(uri url.URL) bool { return (uri.Scheme == "http") || (uri.Scheme == "https") } func getStoragePath(directory, fname, extension string) string { localpath := filepath.Join(directory, fname) if extension != "" { localpath = localpath + extension } return localpath } osconfig-20210219.00/policies/recipes/artifacts_test.go000066400000000000000000000027021401404514100226030ustar00rootroot00000000000000// Copyright 2019 Google 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 recipes import ( "net/url" "strings" "testing" ) func TestFetchArtifacts_http_InvalidURL(t *testing.T) { uri := "ftp://google.com/agent.deb" u, err := url.Parse(uri) _, err = getHTTPArtifact(nil, *u) if err == nil || !strings.Contains(err.Error(), "unsupported protocol scheme") { t.Errorf("expected error (unsupported protocol); got(%v)", err) } } func TestRecipesgetStoragePathWithExtension(t *testing.T) { expect := "/tmp/artifact-id-1.txt" localpath := getStoragePath("/tmp", "artifact-id-1", ".txt") if localpath != expect { t.Errorf("Expected(%s); got(%s)", expect, localpath) } } func TestRecipesgetStoragePathWitouthExtension(t *testing.T) { expect := "/tmp/artifact-id-1" localpath := getStoragePath("/tmp", "artifact-id-1", "") if localpath != expect { t.Errorf("Expected(%s); got(%s)", expect, localpath) } } osconfig-20210219.00/policies/recipes/installrecipe.go000066400000000000000000000113011401404514100224150ustar00rootroot00000000000000// Copyright 2019 Google 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 recipes import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "time" "github.com/GoogleCloudPlatform/osconfig/clog" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) // InstallRecipe installs a recipe. func InstallRecipe(ctx context.Context, recipe *agentendpointpb.SoftwareRecipe) error { ctx = clog.WithLabels(ctx, map[string]string{"recipe_name": recipe.GetName()}) steps := recipe.InstallSteps recipeDB, err := newRecipeDB() if err != nil { return err } installedRecipe, ok := recipeDB.getRecipe(recipe.Name) if ok { clog.Debugf(ctx, "Currently installed version of software recipe %s with version %s.", recipe.GetName(), installedRecipe.Version) if (installedRecipe.compare(recipe.Version)) && (recipe.DesiredState == agentendpointpb.DesiredState_UPDATED) { clog.Infof(ctx, "Upgrading software recipe %s from version %s to %s.", recipe.Name, installedRecipe.Version, recipe.GetVersion()) steps = recipe.UpdateSteps } else { clog.Debugf(ctx, "Skipping software recipe %s.", recipe.GetName()) return nil } } else { clog.Infof(ctx, "Installing software recipe %s.", recipe.GetName()) } clog.Debugf(ctx, "Creating working directory for recipe %s.", recipe.GetName()) runID := fmt.Sprintf("run_%d", time.Now().UnixNano()) runDir, err := createBaseDir(recipe, runID) if err != nil { return fmt.Errorf("failed to create base directory: %v", err) } defer func() { if err := os.RemoveAll(runDir); err != nil { clog.Warningf(ctx, "Failed to remove recipe working directory at %q: %v", runDir, err) } }() artifacts, err := fetchArtifacts(ctx, recipe.Artifacts, runDir) if err != nil { return fmt.Errorf("failed to obtain artifacts: %v", err) } runEnvs := []string{ fmt.Sprintf("RECIPE_NAME=%s", recipe.Name), fmt.Sprintf("RECIPE_VERSION=%s", recipe.Version), fmt.Sprintf("RUNID=%s", runID), } for artifactID, artifactPath := range artifacts { runEnvs = append(runEnvs, fmt.Sprintf("%s=%s", artifactID, artifactPath)) } for i, step := range steps { clog.Debugf(ctx, "Running step %d: %q", i, step) stepDir := filepath.Join(runDir, fmt.Sprintf("step%02d", i)) if err := os.MkdirAll(stepDir, 0755); err != nil { return fmt.Errorf("failed to create recipe step dir %q: %s", stepDir, err) } var err error var stepType string switch { case step.GetFileCopy() != nil: stepType = "CopyFile" err = stepCopyFile(step.GetFileCopy(), artifacts, runEnvs, stepDir) case step.GetArchiveExtraction() != nil: stepType = "ExtractArchive" err = stepExtractArchive(ctx, step.GetArchiveExtraction(), artifacts, runEnvs, stepDir) case step.GetMsiInstallation() != nil: stepType = "InstallMsi" err = stepInstallMsi(ctx, step.GetMsiInstallation(), artifacts, runEnvs, stepDir) case step.GetFileExec() != nil: stepType = "ExecFile" err = stepExecFile(ctx, step.GetFileExec(), artifacts, runEnvs, stepDir) case step.GetScriptRun() != nil: stepType = "RunScript" err = stepRunScript(ctx, step.GetScriptRun(), artifacts, runEnvs, stepDir) case step.GetDpkgInstallation() != nil: stepType = "InstallDpkg" err = stepInstallDpkg(ctx, step.GetDpkgInstallation(), artifacts) case step.GetRpmInstallation() != nil: stepType = "InstallRpm" err = stepInstallRpm(ctx, step.GetRpmInstallation(), artifacts) } if err != nil { recipeDB.addRecipe(recipe.Name, recipe.Version, false) if stepType == "" { return fmt.Errorf("unknown step type for step %d", i) } return fmt.Errorf("error running step %d (%s): %v", i, stepType, err) } } clog.Infof(ctx, "All steps completed successfully, marking recipe %s as installed.", recipe.Name) return recipeDB.addRecipe(recipe.Name, recipe.Version, true) } func createBaseDir(recipe *agentendpointpb.SoftwareRecipe, runID string) (string, error) { name := recipe.Name if recipe.Version != "" { name = fmt.Sprintf("%s_%s", name, recipe.Version) } dir, err := ioutil.TempDir("", fmt.Sprintf("%s_%s_", name, runID)) if err != nil { return "", fmt.Errorf("failed to create working dir for recipe: %q %s", recipe.Name, err) } return dir, nil } osconfig-20210219.00/policies/recipes/recipe.go000066400000000000000000000045321401404514100210360ustar00rootroot00000000000000// Copyright 2019 Google 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 recipes import ( "fmt" "strconv" "strings" ) type recipeVersion []int func (v recipeVersion) String() string { res := fmt.Sprintf("%d", v[0]) for _, val := range v[1:] { res = fmt.Sprintf("%s.%d", res, val) } return res } // Recipe represents an installed recipe. type Recipe struct { Name string Version recipeVersion InstallTime int64 Success bool } func (r *Recipe) setVersion(version string) error { var err error r.Version, err = convertVersion(version) return err } // compare returns true if the provided Version is greater than the recipe's // Version, false otherwise. func (r *Recipe) compare(version string) bool { if version == "" { return false } cVersion, err := convertVersion(version) if err != nil { return false } if len(r.Version) > len(cVersion) { topad := len(r.Version) - len(cVersion) for i := 0; i < topad; i++ { cVersion = append(cVersion, 0) } } else { topad := len(cVersion) - len(r.Version) for i := 0; i < topad; i++ { r.Version = append(r.Version, 0) } } for i := 0; i < len(r.Version); i++ { if r.Version[i] != cVersion[i] { return cVersion[i] > r.Version[i] } } return false } func convertVersion(version string) ([]int, error) { // ${ROOT}/recipe[_ver]/runId/recipe.yaml // recipe at time of application // ${ROOT}/recipe[_ver]/runId/artifacts/* // ${ROOT}/recipe[_ver]/runId/stepN_type/ if version == "" { return []int{0}, nil } var ret []int for idx, element := range strings.Split(version, ".") { if idx > 3 { return nil, fmt.Errorf("invalid Version string") } val, err := strconv.ParseUint(element, 10, 0) if err != nil { return nil, fmt.Errorf("invalid Version string") } ret = append(ret, int(val)) } return ret, nil } osconfig-20210219.00/policies/recipes/recipe_test.go000066400000000000000000000052501401404514100220730ustar00rootroot00000000000000// Copyright 2019 Google 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 recipes import ( "strings" "testing" ) func TestRecipe_SetVersion_ValidVersion(t *testing.T) { rec := &Recipe{} rec.setVersion("1.2.3.23") if rec.Version[0] != 1 || rec.Version[1] != 2 || rec.Version[2] != 3 || rec.Version[3] != 23 { t.Errorf("invalid Version set for the recipe") } } func TestRecipe_SetVersion_EmptyVersion(t *testing.T) { rec := &Recipe{} rec.setVersion("") if len(rec.Version) != 1 || rec.Version[0] != 0 { t.Errorf("invalid Version set for the recipe") } } func TestRecipe_SetVersion_InvalidVersion(t *testing.T) { rec := &Recipe{} err := rec.setVersion("12.32.23.23.23.23") if !strings.Contains(err.Error(), "invalid") { t.Errorf("setVersion should return error") } } func TestRecipe_SetVersion_InvalidCharacter(t *testing.T) { rec := &Recipe{} err := rec.setVersion("12.32.dsf.23") if !strings.Contains(err.Error(), "invalid") { t.Errorf("setVersion should return error") } } func TestRecipe_Compare_EmptyVersion(t *testing.T) { rec := &Recipe{Version: []int{1, 2, 3, 4}} if rec.compare("") { t.Errorf("should return false") } } func TestRecipe_Compare_InvalidInput(t *testing.T) { rec := &Recipe{Version: []int{1, 2, 3, 4}} if rec.compare("1.2.3.4.56.7") { t.Errorf("should return false") } } func TestRecipe_Compare_PaddingNeededAndCompare(t *testing.T) { rec := &Recipe{Version: []int{1, 2, 3, 4}} if !rec.compare("1.3") { t.Errorf("should return true") } } func TestRecipe_Compare_PaddingNeededToRecipe(t *testing.T) { rec := &Recipe{Version: []int{1, 3}} if rec.compare("1.2.3.4") { t.Errorf("should return false") } } func TestRecipe_Compare_IsGreater(t *testing.T) { rec := &Recipe{Version: []int{1, 2, 3, 4}} if !rec.compare("1.3.4.2") { t.Errorf("should return true") } } func TestRecipe_Compare_IsNotGreater(t *testing.T) { rec := &Recipe{Version: []int{1, 6, 3, 4}} if rec.compare("1.3.4.2") { t.Errorf("should return false") } } func TestRecipe_Compare_IsEqualVersion(t *testing.T) { rec := &Recipe{Version: []int{1, 6, 3, 4}} if rec.compare("1.6.3.4") { t.Errorf("should return false") } } osconfig-20210219.00/policies/recipes/recipedb.go000066400000000000000000000047711401404514100213510ustar00rootroot00000000000000// Copyright 2019 Google 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 recipes import ( "encoding/json" "io/ioutil" "os" "path/filepath" "runtime" "time" ) var ( dbDirWindows = "C:\\ProgramData\\Google" dbDirUnix = "/var/lib/google" dbFileName = "osconfig_recipedb" ) // RecipeDB represents local state of installed recipes. type RecipeDB map[string]Recipe // newRecipeDB instantiates a recipeDB. func newRecipeDB() (RecipeDB, error) { db := make(RecipeDB) f, err := os.Open(filepath.Join(getDbDir(), dbFileName)) if err != nil { if os.IsNotExist(err) { return db, nil } return nil, err } defer f.Close() bytes, err := ioutil.ReadAll(f) if err != nil { return nil, err } var recipelist []Recipe if err := json.Unmarshal(bytes, &recipelist); err != nil { return nil, err } for _, recipe := range recipelist { db[recipe.Name] = recipe } return db, nil } // getRecipe returns the Recipe object for the given recipe name. func (db RecipeDB) getRecipe(name string) (Recipe, bool) { r, ok := db[name] return r, ok } // addRecipe marks a recipe as installed. func (db RecipeDB) addRecipe(name, version string, success bool) error { versionNum, err := convertVersion(version) if err != nil { return err } db[name] = Recipe{Name: name, Version: versionNum, InstallTime: time.Now().Unix(), Success: success} var recipelist []Recipe for _, recipe := range db { recipelist = append(recipelist, recipe) } dbBytes, err := json.Marshal(recipelist) if err != nil { return err } dbDir := getDbDir() if err := os.MkdirAll(dbDir, 0755); err != nil { return err } f, err := ioutil.TempFile(dbDir, dbFileName+"_*") if err != nil { return err } if _, err := f.Write(dbBytes); err != nil { f.Close() return err } if err := f.Close(); err != nil { return err } return os.Rename(f.Name(), filepath.Join(dbDir, dbFileName)) } func getDbDir() string { if runtime.GOOS == "windows" { return dbDirWindows } return dbDirUnix } osconfig-20210219.00/policies/recipes/steps.go000066400000000000000000000331571401404514100207320ustar00rootroot00000000000000// Copyright 2019 Google 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 recipes import ( "archive/tar" "archive/zip" "compress/bzip2" "compress/gzip" "context" "errors" "fmt" "io" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" "github.com/GoogleCloudPlatform/osconfig/util" "github.com/ulikunitz/xz" "github.com/ulikunitz/xz/lzma" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) var extensionMap = map[agentendpointpb.SoftwareRecipe_Step_RunScript_Interpreter]string{ agentendpointpb.SoftwareRecipe_Step_RunScript_INTERPRETER_UNSPECIFIED: ".bat", agentendpointpb.SoftwareRecipe_Step_RunScript_SHELL: ".bat", agentendpointpb.SoftwareRecipe_Step_RunScript_POWERSHELL: ".ps1", } func stepCopyFile(step *agentendpointpb.SoftwareRecipe_Step_CopyFile, artifacts map[string]string, runEnvs []string, stepDir string) error { dest, err := util.NormPath(step.Destination) if err != nil { return err } permissions, err := parsePermissions(step.Permissions) if err != nil { return err } if _, err := os.Stat(dest); err != nil { if !os.IsNotExist(err) { return err } } else { // file exists if !step.Overwrite { return fmt.Errorf("file already exists at path %q and Overwrite = false", step.Destination) } if err := os.Chmod(dest, permissions); err != nil { return err } } artifact := step.GetArtifactId() src, ok := artifacts[artifact] if !ok { return fmt.Errorf("could not find location for artifact %q", artifact) } reader, err := os.Open(src) if err != nil { return err } defer reader.Close() _, err = util.AtomicWriteFileStream(reader, "", dest, permissions) return err } func parsePermissions(s string) (os.FileMode, error) { if s == "" { return 0755, nil } i, err := strconv.ParseUint(s, 8, 32) if err != nil { return 0, err } return os.FileMode(i), nil } func stepExtractArchive(ctx context.Context, step *agentendpointpb.SoftwareRecipe_Step_ExtractArchive, artifacts map[string]string, runEnvs []string, stepDir string) error { artifact := step.GetArtifactId() filename, ok := artifacts[artifact] if !ok { return fmt.Errorf("%q not found in artifact map", artifact) } switch typ := step.GetType(); typ { case agentendpointpb.SoftwareRecipe_Step_ExtractArchive_ZIP: return extractZip(filename, step.Destination) case agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_GZIP, agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_BZIP, agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_LZMA, agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_XZ, agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR: return extractTar(ctx, filename, step.Destination, typ) default: return fmt.Errorf("Unrecognized archive type %q", typ) } } func zipIsDir(name string) bool { if os.PathSeparator == '\\' { return strings.HasSuffix(name, `\`) || strings.HasSuffix(name, "/") } return strings.HasSuffix(name, "/") } func extractZip(zipPath string, dst string) error { zr, err := zip.OpenReader(zipPath) if err != nil { return err } defer zr.Close() // Check for conflicts for _, f := range zr.File { filen, err := util.NormPath(filepath.Join(dst, f.Name)) if err != nil { return err } stat, err := os.Stat(filen) if os.IsNotExist(err) { continue } if err != nil { return err } if zipIsDir(f.Name) && stat.IsDir() { // it's ok if directories already exist continue } return fmt.Errorf("file exists: %s", filen) } // Create files. for _, f := range zr.File { filen, err := util.NormPath(filepath.Join(dst, f.Name)) if err != nil { return err } if zipIsDir(f.Name) { mode := f.Mode() if mode == 0 { mode = 0755 } if err := os.MkdirAll(filen, mode); err != nil { return err } // Setting to correct permissions in case the directory has already been created if err := os.Chmod(filen, mode); err != nil { return err } continue } filedir := filepath.Dir(filen) if err = os.MkdirAll(filedir, 0755); err != nil { return err } reader, err := f.Open() if err != nil { return err } mode := f.Mode() if mode == 0 { mode = 0755 } dst, err := os.OpenFile(filen, os.O_RDWR|os.O_CREATE, mode) if err != nil { return err } if _, err = io.Copy(dst, reader); err != nil { dst.Close() return err } reader.Close() if err := dst.Close(); err != nil { return err } if err := os.Chtimes(filen, time.Now(), f.ModTime()); err != nil { return err } } return nil } func decompress(reader io.Reader, archiveType agentendpointpb.SoftwareRecipe_Step_ExtractArchive_ArchiveType) (io.Reader, error) { switch archiveType { case agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_GZIP: // *gzip.Reader is a io.ReadCloser but it isn't necesary to call Close() on it. return gzip.NewReader(reader) case agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_BZIP: return bzip2.NewReader(reader), nil case agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_LZMA: return lzma.NewReader2(reader) case agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR_XZ: return xz.NewReader(reader) case agentendpointpb.SoftwareRecipe_Step_ExtractArchive_TAR: return reader, nil default: return nil, fmt.Errorf("Unrecognized archive type %q when trying to decompress tar", archiveType) } } func checkForConflicts(tr *tar.Reader, dst string) error { for { header, err := tr.Next() if err == io.EOF { break } if err != nil { return err } filen, err := util.NormPath(filepath.Join(dst, header.Name)) if err != nil { return err } stat, err := os.Stat(filen) if os.IsNotExist(err) { continue } if err != nil { return err } if header.Typeflag == tar.TypeDir && stat.IsDir() { // it's ok if directories already exist continue } return fmt.Errorf("file exists: %s", filen) } return nil } func extractTar(ctx context.Context, tarName string, dst string, archiveType agentendpointpb.SoftwareRecipe_Step_ExtractArchive_ArchiveType) error { file, err := os.Open(tarName) if err != nil { return err } defer file.Close() decompressed, err := decompress(file, archiveType) if err != nil { return err } tr := tar.NewReader(decompressed) if err := checkForConflicts(tr, dst); err != nil { return err } file.Seek(0, 0) decompressed, err = decompress(file, archiveType) if err != nil { return err } tr = tar.NewReader(decompressed) for { var err error header, err := tr.Next() if err == io.EOF { break } if err != nil { return err } filen, err := util.NormPath(filepath.Join(dst, header.Name)) if err != nil { return err } filedir := filepath.Dir(filen) if err := os.MkdirAll(filedir, 0700); err != nil { return err } switch header.Typeflag { case tar.TypeDir: if err := os.MkdirAll(filen, os.FileMode(header.Mode)); err != nil { return err } // Setting to correct permissions in case the directory has already been created if err := os.Chmod(filen, os.FileMode(header.Mode)); err != nil { return err } if err := chown(filen, header.Uid, header.Gid); err != nil { return err } case tar.TypeReg, tar.TypeRegA: dst, err := os.OpenFile(filen, os.O_RDWR|os.O_CREATE, os.FileMode(header.Mode)) if err != nil { return err } if _, err := io.Copy(dst, tr); err != nil { dst.Close() return err } if err := dst.Close(); err != nil { return err } case tar.TypeLink: if err := os.Link(header.Linkname, filen); err != nil { return err } continue case tar.TypeSymlink: if err := os.Symlink(header.Linkname, filen); err != nil { return err } continue case tar.TypeChar: if err := mkCharDevice(filen, uint32(header.Devmajor), uint32(header.Devminor)); err != nil { return err } if runtime.GOOS != "windows" { if err := os.Chmod(filen, os.FileMode(header.Mode)); err != nil { return err } } case tar.TypeBlock: if err := mkBlockDevice(filen, uint32(header.Devmajor), uint32(header.Devminor)); err != nil { return err } if runtime.GOOS != "windows" { if err := os.Chmod(filen, os.FileMode(header.Mode)); err != nil { return err } } case tar.TypeFifo: if err := mkFifo(filen, uint32(header.Mode)); err != nil { return err } default: clog.Infof(ctx, "Unknown file type for tar entry %s\n", filen) continue } if err := chown(filen, header.Uid, header.Gid); err != nil { return err } if err := os.Chtimes(filen, header.AccessTime, header.ModTime); err != nil { return err } } return nil } func stepInstallMsi(ctx context.Context, step *agentendpointpb.SoftwareRecipe_Step_InstallMsi, artifacts map[string]string, runEnvs []string, stepDir string) error { if runtime.GOOS != "windows" { return errors.New("SoftwareRecipe_Step_InstallMsi only applicable on Windows") } artifact := step.GetArtifactId() path, ok := artifacts[artifact] if !ok { return fmt.Errorf("%q not found in artifact map", artifact) } args := step.Flags if len(args) == 0 { args = []string{"/i", "/qn", "/norestart"} } args = append(args, path) exitCodes := step.AllowedExitCodes if len(exitCodes) == 0 { exitCodes = []int32{0, 1641, 3010} } return executeCommand(ctx, "C:\\Windows\\System32\\msiexec.exe", args, stepDir, runEnvs, exitCodes) } func stepInstallDpkg(ctx context.Context, step *agentendpointpb.SoftwareRecipe_Step_InstallDpkg, artifacts map[string]string) error { if !packages.DpkgExists { return fmt.Errorf("dpkg does not exist on system") } artifact := step.GetArtifactId() path, ok := artifacts[artifact] if !ok { return fmt.Errorf("%q not found in artifact map", artifact) } return packages.DpkgInstall(ctx, path) } func stepInstallRpm(ctx context.Context, step *agentendpointpb.SoftwareRecipe_Step_InstallRpm, artifacts map[string]string) error { if !packages.RPMExists { return fmt.Errorf("rpm does not exist on system") } artifact := step.GetArtifactId() path, ok := artifacts[artifact] if !ok { return fmt.Errorf("%q not found in artifact map", artifact) } return packages.RPMInstall(ctx, path) } func stepExecFile(ctx context.Context, step *agentendpointpb.SoftwareRecipe_Step_ExecFile, artifacts map[string]string, runEnvs []string, stepDir string) error { var path string switch { case step.GetArtifactId() != "": var ok bool artifact := step.GetArtifactId() path, ok = artifacts[artifact] if !ok { return fmt.Errorf("%q not found in artifact map", artifact) } // By default artifacts are created with 0644 if err := os.Chmod(path, 0755); err != nil { return fmt.Errorf("error setting execute permissions on artifact %s: %v", step.GetArtifactId(), err) } case step.GetLocalPath() != "": path = step.GetLocalPath() default: return fmt.Errorf("can't determine location type") } return executeCommand(ctx, path, step.Args, stepDir, runEnvs, []int32{0}) } func stepRunScript(ctx context.Context, step *agentendpointpb.SoftwareRecipe_Step_RunScript, artifacts map[string]string, runEnvs []string, stepDir string) error { var extension string if runtime.GOOS == "windows" { extension = extensionMap[step.Interpreter] } scriptPath := filepath.Join(stepDir, "recipe_script_source"+extension) if err := util.AtomicWrite(scriptPath, []byte(step.Script), 0755); err != nil { return err } var cmd string var args []string switch step.Interpreter { case agentendpointpb.SoftwareRecipe_Step_RunScript_INTERPRETER_UNSPECIFIED: cmd = scriptPath case agentendpointpb.SoftwareRecipe_Step_RunScript_SHELL: if runtime.GOOS == "windows" { cmd = scriptPath } else { args = append([]string{scriptPath}) cmd = "/bin/sh" } case agentendpointpb.SoftwareRecipe_Step_RunScript_POWERSHELL: if runtime.GOOS != "windows" { return fmt.Errorf("interpreter %q can only be used on Windows systems", step.Interpreter) } args = append([]string{"-File", scriptPath}) cmd = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\PowerShell.exe" default: return fmt.Errorf("unsupported interpreter %q", step.Interpreter) } return executeCommand(ctx, cmd, args, stepDir, runEnvs, step.AllowedExitCodes) } func executeCommand(ctx context.Context, cmd string, args []string, workDir string, runEnvs []string, allowedExitCodes []int32) error { cmdObj := exec.Command(cmd, args...) cmdObj.Dir = workDir defaultEnv, err := createDefaultEnvironment() if err != nil { return fmt.Errorf("error creating default environment: %v", err) } cmdObj.Env = append(cmdObj.Env, defaultEnv...) cmdObj.Env = append(cmdObj.Env, runEnvs...) o, err := cmdObj.CombinedOutput() clog.Infof(ctx, "Combined output for %q command:\n%s", cmd, o) if err == nil { return nil } if v, ok := err.(*exec.ExitError); ok && len(allowedExitCodes) != 0 { result := int32(v.ExitCode()) for _, code := range allowedExitCodes { if result == code { return nil } } } return err } func chown(file string, uid, gid int) error { // os.Chown unsupported on windows if runtime.GOOS == "windows" { return nil } return os.Chown(file, uid, gid) } osconfig-20210219.00/policies/recipes/steps_linux.go000066400000000000000000000021651401404514100221440ustar00rootroot00000000000000// Copyright 2019 Google 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. // +build linux package recipes import ( "golang.org/x/sys/unix" ) func mkCharDevice(path string, devMajor, devMinor uint32) error { return unix.Mknod(path, unix.S_IFCHR, int(unix.Mkdev(devMajor, devMinor))) } func mkBlockDevice(path string, devMajor, devMinor uint32) error { return unix.Mknod(path, unix.S_IFBLK, int(unix.Mkdev(devMajor, devMinor))) } func mkFifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) } func createDefaultEnvironment() ([]string, error) { return []string{}, nil } osconfig-20210219.00/policies/recipes/steps_windows.go000066400000000000000000000022771401404514100225030ustar00rootroot00000000000000// Copyright 2019 Google 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. // +build windows package recipes import ( "fmt" "golang.org/x/sys/windows" ) func mkCharDevice(path string, devMajor, devMinor uint32) error { return fmt.Errorf("Creating a char device is not supported on windows") } func mkBlockDevice(path string, devMajor, devMinor uint32) error { return fmt.Errorf("Creating a block device is not supported on windows") } func mkFifo(path string, mode uint32) error { return fmt.Errorf("Creating a fifo is not supported on windows") } func createDefaultEnvironment() ([]string, error) { return windows.GetCurrentProcessToken().Environ(false) } osconfig-20210219.00/policies/yum.go000066400000000000000000000067321401404514100167530ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "bytes" "context" "errors" "fmt" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) func yumRepositories(ctx context.Context, repos []*agentendpointpb.YumRepository, repoFile string) error { // TODO: Would it be easier to just use templates? /* # Repo file managed by Google OSConfig agent [repo1] name=repo1-name baseurl=https://repo1-url enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=http://repo1-url/gpg [repo2] display_name=repo2-name baseurl=https://repo2-url enabled=1 gpgcheck=1 repo_gpgcheck=1 */ var buf bytes.Buffer buf.WriteString("# Repo file managed by Google OSConfig agent\n") for _, repo := range repos { buf.WriteString(fmt.Sprintf("\n[%s]\n", repo.Id)) if repo.DisplayName == "" { buf.WriteString(fmt.Sprintf("name=%s\n", repo.Id)) } else { buf.WriteString(fmt.Sprintf("name=%s\n", repo.DisplayName)) } buf.WriteString(fmt.Sprintf("baseurl=%s\n", repo.BaseUrl)) buf.WriteString("enabled=1\ngpgcheck=1\nrepo_gpgcheck=1\n") if len(repo.GpgKeys) > 0 { buf.WriteString(fmt.Sprintf("gpgkey=%s\n", repo.GpgKeys[0])) for _, k := range repo.GpgKeys[1:] { buf.WriteString(fmt.Sprintf(" %s\n", k)) } } } return writeIfChanged(ctx, buf.Bytes(), repoFile) } func yumChanges(ctx context.Context, yumInstalled, yumRemoved, yumUpdated []*agentendpointpb.Package) error { var err error var errs []string var installed []packages.PkgInfo if len(yumInstalled) > 0 || len(yumUpdated) > 0 || len(yumRemoved) > 0 { installed, err = packages.InstalledRPMPackages(ctx) if err != nil { return err } } var updates []packages.PkgInfo if len(yumUpdated) > 0 { updates, err = packages.YumUpdates(ctx) if err != nil { return err } } changes := getNecessaryChanges(installed, updates, yumInstalled, yumRemoved, yumUpdated) if changes.packagesToInstall != nil { clog.Infof(ctx, "Installing packages %s", changes.packagesToInstall) if err := packages.InstallYumPackages(ctx, changes.packagesToInstall); err != nil { errs = append(errs, fmt.Sprintf("error installing yum packages: %v", err)) } } if changes.packagesToUpgrade != nil { clog.Infof(ctx, "Upgrading packages %s", changes.packagesToUpgrade) if err := packages.InstallYumPackages(ctx, changes.packagesToUpgrade); err != nil { errs = append(errs, fmt.Sprintf("error upgrading yum packages: %v", err)) } } if changes.packagesToRemove != nil { clog.Infof(ctx, "Removing packages %s", changes.packagesToRemove) if err := packages.RemoveYumPackages(ctx, changes.packagesToRemove); err != nil { errs = append(errs, fmt.Sprintf("error removing yum packages: %v", err)) } } if errs == nil { return nil } return errors.New(strings.Join(errs, ",\n")) } osconfig-20210219.00/policies/yum_test.go000066400000000000000000000052571401404514100200130ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "testing" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) func runYumRepositories(ctx context.Context, repos []*agentendpointpb.YumRepository) (string, error) { td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { return "", fmt.Errorf("error creating temp dir: %v", err) } defer os.RemoveAll(td) testRepo := filepath.Join(td, "testRepo") if err := yumRepositories(ctx, repos, testRepo); err != nil { return "", fmt.Errorf("error running yumRepositories: %v", err) } data, err := ioutil.ReadFile(testRepo) if err != nil { return "", fmt.Errorf("error reading testRepo: %v", err) } return string(data), nil } func TestYumRepositories(t *testing.T) { tests := []struct { desc string repos []*agentendpointpb.YumRepository want string }{ {"no repos", []*agentendpointpb.YumRepository{}, "# Repo file managed by Google OSConfig agent\n"}, { "1 repo", []*agentendpointpb.YumRepository{ {BaseUrl: "http://repo1-url/", Id: "id"}, }, "# Repo file managed by Google OSConfig agent\n\n[id]\nname=id\nbaseurl=http://repo1-url/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\n", }, { "2 repos", []*agentendpointpb.YumRepository{ {BaseUrl: "http://repo1-url/", Id: "id1", DisplayName: "displayName1", GpgKeys: []string{"https://url/key"}}, {BaseUrl: "http://repo1-url/", Id: "id2", DisplayName: "displayName2", GpgKeys: []string{"https://url/key1", "https://url/key2"}}, }, "# Repo file managed by Google OSConfig agent\n\n[id1]\nname=displayName1\nbaseurl=http://repo1-url/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://url/key\n\n[id2]\nname=displayName2\nbaseurl=http://repo1-url/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://url/key1\n https://url/key2\n", }, } for _, tt := range tests { got, err := runYumRepositories(context.Background(), tt.repos) if err != nil { t.Fatal(err) } if got != tt.want { t.Errorf("%s: got:\n%q\nwant:\n%q", tt.desc, got, tt.want) } } } osconfig-20210219.00/policies/zypper.go000066400000000000000000000070231401404514100174640ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "bytes" "context" "errors" "fmt" "strings" "github.com/GoogleCloudPlatform/osconfig/clog" "github.com/GoogleCloudPlatform/osconfig/packages" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) // TODO: Write repo_gpgcheck, pkg_gpgcheck, type func zypperRepositories(ctx context.Context, repos []*agentendpointpb.ZypperRepository, repoFile string) error { /* # Repo file managed by Google OSConfig agent [repo1] name=repo1-name baseurl=https://repo1-url enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=http://repo1-url/gpg [repo2] display_name=repo2-name baseurl=https://repo2-url enabled=1 gpgcheck=1 repo_gpgcheck=1 */ var buf bytes.Buffer buf.WriteString("# Repo file managed by Google OSConfig agent\n") for _, repo := range repos { buf.WriteString(fmt.Sprintf("\n[%s]\n", repo.Id)) if repo.DisplayName == "" { buf.WriteString(fmt.Sprintf("name=%s\n", repo.Id)) } else { buf.WriteString(fmt.Sprintf("name=%s\n", repo.DisplayName)) } buf.WriteString(fmt.Sprintf("baseurl=%s\n", repo.BaseUrl)) buf.WriteString("enabled=1\ngpgcheck=1\nrepo_gpgcheck=1\n") if len(repo.GpgKeys) > 0 { buf.WriteString(fmt.Sprintf("gpgkey=%s\n", repo.GpgKeys[0])) for _, k := range repo.GpgKeys[1:] { buf.WriteString(fmt.Sprintf(" %s\n", k)) } } } return writeIfChanged(ctx, buf.Bytes(), repoFile) } func zypperChanges(ctx context.Context, zypperInstalled, zypperRemoved, zypperUpdated []*agentendpointpb.Package) error { var err error var errs []string var installed []packages.PkgInfo if len(zypperInstalled) > 0 || len(zypperUpdated) > 0 || len(zypperRemoved) > 0 { installed, err = packages.InstalledRPMPackages(ctx) if err != nil { return err } } var updates []packages.PkgInfo if len(zypperUpdated) > 0 { updates, err = packages.ZypperUpdates(ctx) if err != nil { return err } } changes := getNecessaryChanges(installed, updates, zypperInstalled, zypperRemoved, zypperUpdated) if changes.packagesToInstall != nil { clog.Infof(ctx, "Installing packages %s", changes.packagesToInstall) if err := packages.InstallZypperPackages(ctx, changes.packagesToInstall); err != nil { errs = append(errs, fmt.Sprintf("error installing zypper packages: %v", err)) } } if changes.packagesToUpgrade != nil { clog.Infof(ctx, "Upgrading packages %s", changes.packagesToUpgrade) if err := packages.InstallZypperPackages(ctx, changes.packagesToUpgrade); err != nil { errs = append(errs, fmt.Sprintf("error upgrading zypper packages: %v", err)) } } if changes.packagesToRemove != nil { clog.Infof(ctx, "Removing packages %s", changes.packagesToRemove) if err := packages.RemoveZypperPackages(ctx, changes.packagesToRemove); err != nil { errs = append(errs, fmt.Sprintf("error removing zypper packages: %v", err)) } } if errs == nil { return nil } return errors.New(strings.Join(errs, ",\n")) } osconfig-20210219.00/policies/zypper_test.go000066400000000000000000000053151401404514100205250ustar00rootroot00000000000000// Copyright 2019 Google 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 policies import ( "context" "fmt" "io/ioutil" "os" "path/filepath" "testing" agentendpointpb "google.golang.org/genproto/googleapis/cloud/osconfig/agentendpoint/v1beta" ) func runZypperRepositories(ctx context.Context, repos []*agentendpointpb.ZypperRepository) (string, error) { td, err := ioutil.TempDir(os.TempDir(), "") if err != nil { return "", fmt.Errorf("error creating temp dir: %v", err) } defer os.RemoveAll(td) testRepo := filepath.Join(td, "testRepo") if err := zypperRepositories(ctx, repos, testRepo); err != nil { return "", fmt.Errorf("error running zypperRepositories: %v", err) } data, err := ioutil.ReadFile(testRepo) if err != nil { return "", fmt.Errorf("error reading testRepo: %v", err) } return string(data), nil } func TestZypperRepositories(t *testing.T) { tests := []struct { desc string repos []*agentendpointpb.ZypperRepository want string }{ {"no repos", []*agentendpointpb.ZypperRepository{}, "# Repo file managed by Google OSConfig agent\n"}, { "1 repo", []*agentendpointpb.ZypperRepository{ {BaseUrl: "http://repo1-url/", Id: "id"}, }, "# Repo file managed by Google OSConfig agent\n\n[id]\nname=id\nbaseurl=http://repo1-url/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\n", }, { "2 repos", []*agentendpointpb.ZypperRepository{ {BaseUrl: "http://repo1-url/", Id: "id1", DisplayName: "displayName1", GpgKeys: []string{"https://url/key"}}, {BaseUrl: "http://repo1-url/", Id: "id2", DisplayName: "displayName2", GpgKeys: []string{"https://url/key1", "https://url/key2"}}, }, "# Repo file managed by Google OSConfig agent\n\n[id1]\nname=displayName1\nbaseurl=http://repo1-url/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://url/key\n\n[id2]\nname=displayName2\nbaseurl=http://repo1-url/\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=https://url/key1\n https://url/key2\n", }, } for _, tt := range tests { got, err := runZypperRepositories(context.Background(), tt.repos) if err != nil { t.Fatal(err) } if got != tt.want { t.Errorf("%s: got:\n%q\nwant:\n%q", tt.desc, got, tt.want) } } } osconfig-20210219.00/retryutil/000077500000000000000000000000001401404514100160365ustar00rootroot00000000000000osconfig-20210219.00/retryutil/retry.go000066400000000000000000000055241401404514100175400ustar00rootroot00000000000000// Copyright 2019 Google 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 retryutil provides utility functions for retrying. package retryutil import ( "context" "fmt" "math" "math/rand" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // RetrySleep returns a pseudo-random sleep duration. func RetrySleep(base int, extra int) time.Duration { // base=1 and extra=0 => 1*1+[0,1] => 1-2s // base=2 and extra=0 => 2*2+[0,2] => 4-6s // base=3 and extra=0 => 3*3+[0,3] => 9-12s // base=1 and extra=5 => 6*1+[0,6] => 6-12s // base=2 and extra=5 => 7*2+[0,7] => 14-21s // base=3 and extra=5 => 8*3+[0,8] => 24-32s // base=1 and extra=10 => 11*1+[0,11] => 11-22s // base=2 and extra=10 => 12*2+[0,12] => 24-36s // base=3 and extra=10 => 13*3+[0,13] => 39-52s rnd := rand.New(rand.NewSource(time.Now().UnixNano())) nf := math.Min(float64((base+extra)*base+rnd.Intn(base+extra)), 300) return time.Duration(int(nf)) * time.Second } // RetryFunc retries a function provided as a parameter for maxRetryTime. func RetryFunc(ctx context.Context, maxRetryTime time.Duration, desc string, f func() error) error { var tot time.Duration for i := 1; ; i++ { err := f() if err == nil { return nil } ns := RetrySleep(i, 0) tot += ns if tot > maxRetryTime { return err } clog.Errorf(ctx, "Error %s, attempt %d, retrying in %s: %v", desc, i, ns, err) time.Sleep(ns) } } // RetryAPICall retries an API call for maxRetryTime. func RetryAPICall(ctx context.Context, maxRetryTime time.Duration, name string, f func() error) error { var tot time.Duration for i := 1; ; i++ { extra := 1 err := f() if err == nil { return nil } if s, ok := status.FromError(err); ok { err := fmt.Errorf("code: %q, message: %q, details: %q", s.Code(), s.Message(), s.Details()) switch s.Code() { // Errors we should retry. case codes.DeadlineExceeded, codes.Unavailable, codes.Aborted, codes.Internal: // Add additional sleep. case codes.ResourceExhausted: extra = 10 default: return err } } else { return err } ns := RetrySleep(i, extra) tot += ns if tot > maxRetryTime { return err } clog.Warningf(ctx, "Error calling %s, attempt %d, retrying in %s: %v", name, i, ns, err) time.Sleep(ns) } } osconfig-20210219.00/tasker/000077500000000000000000000000001401404514100152645ustar00rootroot00000000000000osconfig-20210219.00/tasker/tasker.go000066400000000000000000000033041401404514100171040ustar00rootroot00000000000000// Copyright 2018 Google 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 tasker is a task queue for the osconfig_agent. package tasker import ( "context" "sync" "github.com/GoogleCloudPlatform/osconfig/clog" ) var ( tc chan *task wg sync.WaitGroup mx sync.Mutex ) func initTasker(ctx context.Context) { tc = make(chan *task) go tasker(ctx) } type task struct { name string run func() } // Enqueue adds a task to the task queue. // Calls to Enqueue after a Close will block. func Enqueue(ctx context.Context, name string, f func()) { mx.Lock() if tc == nil { initTasker(ctx) } tc <- &task{name: name, run: f} mx.Unlock() } // Close prevents any further tasks from being enqueued and waits for the queue to empty. // Subsequent calls to Close() will block. func Close() { mx.Lock() close(tc) wg.Wait() } func tasker(ctx context.Context) { wg.Add(1) defer wg.Done() for { clog.Debugf(ctx, "Waiting for tasks to run.") select { case t, ok := <-tc: // Indicates an empty and closed channel. if !ok { return } clog.Debugf(ctx, "Tasker running %q.", t.name) t.run() clog.Debugf(ctx, "Finished task %q.", t.name) } } } osconfig-20210219.00/tasker/tasker_test.go000066400000000000000000000023241401404514100201440ustar00rootroot00000000000000// Copyright 2019 Google 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 tasker import ( "context" "strconv" "testing" ) var notes []int // TestEnqueueTaskRunSequentially to set sequential // execution of tasks in tasker func TestEnqueueTaskRunSequentially(t *testing.T) { times := 10000 for i := 0; i < times; i++ { addToQueue(i) } Close() if len(notes) != times { t.Fatalf("len(notes) != times, %d != %d", len(notes), times) } for i := 1; i < times; i++ { if notes[i] < notes[i-1] { t.Errorf("task(%d) expected to run earlier", i) } } } func addToQueue(i int) { Enqueue(context.Background(), strconv.Itoa(i), func() { notes = append(notes, i) }) } osconfig-20210219.00/util/000077500000000000000000000000001401404514100147505ustar00rootroot00000000000000osconfig-20210219.00/util/mocks/000077500000000000000000000000001401404514100160645ustar00rootroot00000000000000osconfig-20210219.00/util/mocks/mock_command_runner.go000066400000000000000000000042441401404514100224370ustar00rootroot00000000000000// Copyright 2020 Google 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. // Code generated by MockGen. DO NOT EDIT. // Source: github.com/GoogleCloudPlatform/osconfig/util (interfaces: CommandRunner) // Package utilmocks is a generated GoMock package. package utilmocks import ( context "context" gomock "github.com/golang/mock/gomock" exec "os/exec" reflect "reflect" ) // MockCommandRunner is a mock of CommandRunner interface type MockCommandRunner struct { ctrl *gomock.Controller recorder *MockCommandRunnerMockRecorder } // MockCommandRunnerMockRecorder is the mock recorder for MockCommandRunner type MockCommandRunnerMockRecorder struct { mock *MockCommandRunner } // NewMockCommandRunner creates a new mock instance func NewMockCommandRunner(ctrl *gomock.Controller) *MockCommandRunner { mock := &MockCommandRunner{ctrl: ctrl} mock.recorder = &MockCommandRunnerMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *MockCommandRunner) EXPECT() *MockCommandRunnerMockRecorder { return m.recorder } // Run mocks base method func (m *MockCommandRunner) Run(ctx context.Context, command *exec.Cmd) ([]byte, []byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Run", ctx, command) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].([]byte) ret2, _ := ret[2].(error) return ret0, ret1, ret2 } // Run indicates an expected call of Run func (mr *MockCommandRunnerMockRecorder) Run(ctx, command interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockCommandRunner)(nil).Run), ctx, command) } osconfig-20210219.00/util/util.go000066400000000000000000000117231401404514100162600ustar00rootroot00000000000000// Copyright 2019 Google 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 util contains common functions for use in the osconfig agent. package util import ( "bytes" "context" "crypto/sha256" "encoding/hex" "fmt" "io" "math/rand" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "time" "github.com/GoogleCloudPlatform/osconfig/clog" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) // Logger holds log functions. type Logger struct { Debugf func(string, ...interface{}) Infof func(string, ...interface{}) Warningf func(string, ...interface{}) Errorf func(string, ...interface{}) Fatalf func(string, ...interface{}) } // PrettyFmt uses jsonpb to marshal a proto for pretty printing. func PrettyFmt(pb proto.Message) string { m := &protojson.MarshalOptions{Indent: " ", AllowPartial: true, UseProtoNames: true, EmitUnpopulated: true, UseEnumNumbers: false} return m.Format(pb) } // NormPath transforms a windows path into an extended-length path as described in // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath // when not running on windows it will just return the input path. func NormPath(path string) (string, error) { if strings.HasPrefix(path, `\\?\`) { return path, nil } path, err := filepath.Abs(path) if err != nil { return "", err } if runtime.GOOS != "windows" { return path, nil } return `\\?\` + strings.ReplaceAll(path, "/", `\`), nil } // Exists check for the existence of a file func Exists(name string) bool { if strings.TrimSpace(name) == "" { return false } if _, err := os.Stat(name); err != nil { return false } return true } // AtomicWriteFileStream attempts to atomically write data from the provided reader to the path // checking the checksum if provided. func AtomicWriteFileStream(r io.Reader, checksum, path string, mode os.FileMode) (string, error) { path, err := NormPath(path) if err != nil { return "", err } tmp, err := TempFile(filepath.Dir(path), filepath.Base(path), mode) if err != nil { return "", fmt.Errorf("unable to create temp file: %v", err) } tmpName := tmp.Name() // Make sure we cleanup on any errors. defer func() { if err != nil { tmp.Close() os.Remove(tmpName) } }() hasher := sha256.New() if _, err = io.Copy(io.MultiWriter(tmp, hasher), r); err != nil { return "", err } computed := hex.EncodeToString(hasher.Sum(nil)) if checksum != "" && !strings.EqualFold(checksum, computed) { return "", fmt.Errorf("got %q for checksum, expected %q", computed, checksum) } if err := tmp.Close(); err != nil { return "", err } return computed, os.Rename(tmpName, path) } // CommandRunner will execute the commands and return the results of that // execution. type CommandRunner interface { Run(ctx context.Context, command *exec.Cmd) ([]byte, []byte, error) } // DefaultRunner is a default CommandRunner. type DefaultRunner struct{} // Run takes precreated exec.Cmd and returns the stdout and stderr. func (r *DefaultRunner) Run(ctx context.Context, cmd *exec.Cmd) ([]byte, []byte, error) { clog.Debugf(ctx, "Running %q with args %q\n", cmd.Path, cmd.Args[1:]) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() clog.Debugf(ctx, "%s %q output:\n%s", cmd.Path, cmd.Args[1:], strings.ReplaceAll(stdout.String(), "\n", "\n ")) return stdout.Bytes(), stderr.Bytes(), err } // TempFile is a little bit like ioutil.TempFile but takes FileMode in // order to work nicely on Windows where File.Chmod is not supported. func TempFile(dir string, pattern string, mode os.FileMode) (f *os.File, err error) { r := strconv.Itoa(rand.New(rand.NewSource(time.Now().UnixNano())).Intn(99999)) name := filepath.Join(dir, pattern+r+".tmp") return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, mode) } // AtomicWrite attempts to atomically write a file. func AtomicWrite(path string, content []byte, mode os.FileMode) (err error) { path, err = NormPath(path) if err != nil { return err } tmp, err := TempFile(filepath.Dir(path), filepath.Base(path), mode) if err != nil { return fmt.Errorf("unable to create temp file: %v", err) } tmpName := tmp.Name() // Make sure we cleanup on any errors. defer func() { if err != nil { tmp.Close() os.Remove(tmpName) } }() if _, err := tmp.Write(content); err != nil { return err } if err := tmp.Close(); err != nil { return err } return os.Rename(tmpName, path) }