From d1552ed5bf714b1d21466dee73feefd77815dbce Mon Sep 17 00:00:00 2001 From: David Simansky Date: Wed, 24 May 2023 14:07:53 +0200 Subject: [PATCH] Fix plugin inlining that uses client-pkg dependency --- go.mod | 2 + go.sum | 2 + pkg/kn/commands/plugin/list.go | 9 +- .../knative.dev/client-pkg/pkg/plugin/LICENSE | 201 +++++++ vendor/knative.dev/client-pkg/LICENSE | 201 +++++++ .../client-pkg/pkg/plugin/manager.go | 514 ++++++++++++++++++ .../knative.dev/client-pkg/pkg/plugin/stat.go | 36 ++ .../client-pkg/pkg/plugin/stat_windows.go | 24 + .../client-pkg/pkg/plugin/verify.go | 256 +++++++++ vendor/modules.txt | 3 + 10 files changed, 1244 insertions(+), 4 deletions(-) create mode 100644 third_party/VENDOR-LICENSE/knative.dev/client-pkg/pkg/plugin/LICENSE create mode 100644 vendor/knative.dev/client-pkg/LICENSE create mode 100644 vendor/knative.dev/client-pkg/pkg/plugin/manager.go create mode 100644 vendor/knative.dev/client-pkg/pkg/plugin/stat.go create mode 100644 vendor/knative.dev/client-pkg/pkg/plugin/stat_windows.go create mode 100644 vendor/knative.dev/client-pkg/pkg/plugin/verify.go diff --git a/go.mod b/go.mod index 7a57425cb7..87d698cd9f 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,8 @@ require ( sigs.k8s.io/yaml v1.3.0 ) +require knative.dev/client-pkg v0.0.0-20230524014834-83c91f47f64f + require ( contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect diff --git a/go.sum b/go.sum index 443ff59437..dbc62c7739 100644 --- a/go.sum +++ b/go.sum @@ -1063,6 +1063,8 @@ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+O k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 h1:GfD9OzL11kvZN5iArC6oTS7RTj7oJOIfnislxYlqTj8= k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +knative.dev/client-pkg v0.0.0-20230524014834-83c91f47f64f h1:hlLa78gxH9gkgLC/fmsnh9+x/8zCpPS40q0c8K065aQ= +knative.dev/client-pkg v0.0.0-20230524014834-83c91f47f64f/go.mod h1:oYnznlTBCj/bVEHo5vUSM/VS3oDFNJKDmH5+k1aC9/8= knative.dev/eventing v0.37.1-0.20230524084610-5e245ac60ff1 h1:SGl13bqxsUO1ay5iq8jvUN/WCOi7T4UcmHI808Jt4XA= knative.dev/eventing v0.37.1-0.20230524084610-5e245ac60ff1/go.mod h1:y+smuOGee5eJxaZ6jqtuT93TROmEk65yVganVJPu8l4= knative.dev/hack v0.0.0-20230524013611-5812c57cc1ac h1:XQrhtfWPsyuxwSxv4GhcFnqZh75Nq+L1iZWhFMz4aPg= diff --git a/pkg/kn/commands/plugin/list.go b/pkg/kn/commands/plugin/list.go index a97ca5d88b..7637398d68 100644 --- a/pkg/kn/commands/plugin/list.go +++ b/pkg/kn/commands/plugin/list.go @@ -23,7 +23,8 @@ import ( "knative.dev/client/pkg/kn/commands" "knative.dev/client/pkg/kn/config" - "knative.dev/client/pkg/kn/plugin" + + pkgplugin "knative.dev/client-pkg/pkg/plugin" ) // ValidPluginFilenamePrefixes controls the prefix for all kn plugins @@ -62,7 +63,7 @@ Available plugins are those that are: // List plugins by looking up in plugin directory and path func listPlugins(cmd *cobra.Command, flags pluginListFlags) error { - factory := plugin.NewManager(config.GlobalConfig.PluginsDir(), config.GlobalConfig.LookupPluginsInPath()) + factory := pkgplugin.NewManager(config.GlobalConfig.PluginsDir(), config.GlobalConfig.LookupPluginsInPath()) pluginsFound, err := factory.ListPlugins() if err != nil { @@ -111,7 +112,7 @@ func listPlugins(cmd *cobra.Command, flags pluginListFlags) error { return nil } -// create an info label which can be appended to an verbose output +// create an info label which can be appended to a verbose output func extraLabelIfPathNotExists(path string) string { _, err := os.Stat(path) if err == nil { @@ -123,7 +124,7 @@ func extraLabelIfPathNotExists(path string) string { return "" } -func addErrorIfOverwritingExistingCommand(eaw plugin.VerificationErrorsAndWarnings, rootCmd *cobra.Command, plugins []plugin.Plugin) plugin.VerificationErrorsAndWarnings { +func addErrorIfOverwritingExistingCommand(eaw pkgplugin.VerificationErrorsAndWarnings, rootCmd *cobra.Command, plugins []pkgplugin.Plugin) pkgplugin.VerificationErrorsAndWarnings { for _, plugin := range plugins { cmd, args, err := rootCmd.Find(plugin.CommandParts()) diff --git a/third_party/VENDOR-LICENSE/knative.dev/client-pkg/pkg/plugin/LICENSE b/third_party/VENDOR-LICENSE/knative.dev/client-pkg/pkg/plugin/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/third_party/VENDOR-LICENSE/knative.dev/client-pkg/pkg/plugin/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/knative.dev/client-pkg/LICENSE b/vendor/knative.dev/client-pkg/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/knative.dev/client-pkg/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/knative.dev/client-pkg/pkg/plugin/manager.go b/vendor/knative.dev/client-pkg/pkg/plugin/manager.go new file mode 100644 index 0000000000..007404ca8c --- /dev/null +++ b/vendor/knative.dev/client-pkg/pkg/plugin/manager.go @@ -0,0 +1,514 @@ +// Copyright © 2020 The Knative Authors +// +// 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 plugin + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "sort" + "strings" + "text/template" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" +) + +// Allow plugins to register to this slice for inlining +var InternalPlugins PluginList + +// Interface describing a plugin +type Plugin interface { + // Get the name of the plugin (the file name without extensions) + Name() string + + // Execute the plugin with the given arguments + Execute(args []string) error + + // Return a description of the plugin (if support by the plugin binary) + Description() (string, error) + + // The command path leading to this plugin. + // Eg. for a plugin "kn source github" this will be [ "source", "github" ] + CommandParts() []string + + // Location of the plugin where it is stored in the filesystem + Path() string +} + +type ContextData map[string]string + +type PluginManifest struct { + // ProducesContextDataKeys is a list of keys for the ContextData that + // a plugin can produce. Nil or an empty list declares that this + // plugin is not ContextDataProducer + ProducesContextDataKeys []string + + // ConsumesContextDataKeys is a list of keys from a ContextData that a + // plugin is interested in to consume. Nil or an empty list declares + // that this plugin is not a ContextDataConsumer + ConsumesContextDataKeys []string +} + +type ContextDataConsumer interface { + // ExecuteWithContextData executes the plugin with the given args much like + // Execute() but with an additional argument that holds the ContextData + ExecuteWithContextData(args []string, data ContextData) error +} + +type Manager struct { + // Dedicated plugin directory as configured + pluginsDir string + + // Whether to check the OS path or not + lookupInPath bool +} + +type plugin struct { + // Path to the plugin to execute + path string + + // Name of the plugin + name string + + // Commands leading to the execution of this plugin (e.g. "service","log" for a plugin kn-service-log) + commandParts []string +} + +// All extensions that are supposed to be windows executable +var windowsExecExtensions = []string{".bat", ".cmd", ".com", ".exe", ".ps1"} + +// Used for sorting a list of plugins +type PluginList []Plugin + +func (p PluginList) Len() int { return len(p) } +func (p PluginList) Less(i, j int) bool { return p[i].Name() < p[j].Name() } +func (p PluginList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// === PluginManager ======================================================================= + +// NewManager creates a new manager for looking up plugins on the file system +func NewManager(pluginDir string, lookupInPath bool) *Manager { + return &Manager{ + pluginsDir: pluginDir, + lookupInPath: lookupInPath, + } +} + +// FindPlugin checks if a plugin for the given parts exist and return it. +// The args given must not contain any options and contain only +// the commands (like in [ "source", "github" ] for a plugin called 'kn-source-github' +// The plugin with the most specific (longest) name is returned or nil if non is found. +// An error is returned if the lookup fails for some reason like an io error +func (manager *Manager) FindPlugin(parts []string) (Plugin, error) { + if len(parts) == 0 { + // No command given + return nil, nil + } + + // Try to find internal plugin fist + plugin := lookupInternalPlugin(parts) + if plugin != nil { + return plugin, nil + } + + // Try to find plugin in pluginsDir + pluginDir, err := homedir.Expand(manager.pluginsDir) + if err != nil { + return nil, err + } + + return findMostSpecificPluginInPath(pluginDir, parts, manager.lookupInPath) +} + +// ListPlugins lists all plugins that can be found in the plugin directory or in the path (if configured) +func (manager *Manager) ListPlugins() (PluginList, error) { + return manager.ListPluginsForCommandGroup(nil) +} + +// ListPluginsForCommandGroup lists all plugins that can be found in the plugin directory or in the path (if configured), +// and which fits to a command group +func (manager *Manager) ListPluginsForCommandGroup(commandGroupParts []string) (PluginList, error) { + + // Initialize with list of internal plugins + var plugins = append([]Plugin{}, filterPluginsByCommandGroup(InternalPlugins, commandGroupParts)...) + + dirs, err := manager.pluginLookupDirectories() + if err != nil { + return nil, err + } + + // Examine all files in possible plugin directories + hasSeen := make(map[string]bool) + for _, pl := range plugins { + hasSeen[pl.Name()] = true + } + for _, dir := range dirs { + files, err := os.ReadDir(dir) + + // Ignore non-existing directories + if os.IsNotExist(err) { + continue + } + + // Check for plugins within given directory + for _, f := range files { + name := f.Name() + if f.IsDir() { + continue + } + if !strings.HasPrefix(name, "kn-") { + continue + } + + // Check if plugin matches a command group + if !isPluginFileNamePartOfCommandGroup(commandGroupParts, f.Name()) { + continue + } + + // Ignore all plugins that are shadowed + if seen, ok := hasSeen[name]; !ok || !seen { + plugins = append(plugins, &plugin{ + path: filepath.Join(dir, f.Name()), + name: stripWindowsExecExtensions(f.Name()), + commandParts: extractPluginCommandFromFileName(f.Name()), + }) + hasSeen[name] = true + } + } + } + + // Sort according to name + sort.Sort(PluginList(plugins)) + return plugins, nil +} + +func filterPluginsByCommandGroup(plugins PluginList, commandGroupParts []string) PluginList { + ret := PluginList{} + for _, pl := range plugins { + if isPartOfCommandGroup(commandGroupParts, pl.CommandParts()) { + ret = append(ret, pl) + } + } + return ret +} + +func isPartOfCommandGroup(commandGroupParts []string, commandParts []string) bool { + if len(commandParts) != len(commandGroupParts)+1 { + return false + } + for i := range commandGroupParts { + if commandParts[i] != commandGroupParts[i] { + return false + } + } + return true +} + +func isPluginFileNamePartOfCommandGroup(commandGroupParts []string, pluginFileName string) bool { + if commandGroupParts == nil { + return true + } + + commandParts := extractPluginCommandFromFileName(pluginFileName) + if len(commandParts) != len(commandGroupParts)+1 { + return false + } + for i := range commandGroupParts { + if commandParts[i] != commandGroupParts[i] { + return false + } + } + return true +} + +// PluginsDir returns the configured directory holding plugins +func (manager *Manager) PluginsDir() string { + return manager.pluginsDir +} + +// LookupInPath returns true if plugins should be also looked up within the path +func (manager *Manager) LookupInPath() bool { + return manager.lookupInPath +} + +// === Plugin ============================================================================== + +// Execute the plugin with the given arguments +func (plugin *plugin) Execute(args []string) error { + //nolint:gosec // Passing the arguments through is expected, the plugins are trusted. + cmd := exec.Command(plugin.path, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + cmd.Env = os.Environ() + return cmd.Run() +} + +// Return a description of the plugin (if support by the plugin binary) +func (plugin *plugin) Description() (string, error) { + // TODO: Call out to the plugin to find a description. + // For now just use the path to the plugin + return plugin.path, nil + // return strings.Join(plugin.commandParts, "-"), nil +} + +// The the command path leading to this plugin. +// Eg. for a plugin "kn source github" this will be [ "source", "github" ] +func (plugin *plugin) CommandParts() []string { + return plugin.commandParts +} + +// Return the path to the plugin +func (plugin *plugin) Path() string { + return plugin.path +} + +// Name of the plugin +func (plugin *plugin) Name() string { + return plugin.name +} + +// ========================================================================================= + +// Find out all directories that might hold a plugin +func (manager *Manager) pluginLookupDirectories() ([]string, error) { + pluginPath, err := homedir.Expand(manager.pluginsDir) + if err != nil { + return nil, err + } + dirs := []string{pluginPath} + if manager.lookupInPath { + dirs = append(dirs, filepath.SplitList(os.Getenv("PATH"))...) + } + dirs = uniquePathsList(dirs) + return dirs, nil +} + +// HelpTemplateFuncs returns a function map which can be used in templates for resolving +// plugin related help messages +func (manager *Manager) HelpTemplateFuncs() *template.FuncMap { + ret := template.FuncMap{ + "listPlugins": manager.listPluginsHelpMessage(), + } + + return &ret +} + +// listPluginsHelpMessage returns a function which returns all plugins that are directly below the given +// command as a properly formatted string +func (manager *Manager) listPluginsHelpMessage() func(cmd *cobra.Command) string { + return func(cmd *cobra.Command) string { + if !cmd.HasSubCommands() { + return "" + } + list, err := manager.ListPluginsForCommandGroup(extractCommandGroup(cmd, []string{})) + if err != nil || len(list) == 0 { + // We don't show plugins if there is an error + return "" + } + var plugins []string + for _, pl := range list { + t := fmt.Sprintf(" %%-%ds %%s", cmd.NamePadding()) + desc, _ := pl.Description() + command := (pl.CommandParts())[len(pl.CommandParts())-1] + help := fmt.Sprintf(t, command, desc) + plugins = append(plugins, help) + } + return strings.Join(plugins, "\n") + } +} + +// extractCommandGroup constructs the command path as array of strings +func extractCommandGroup(cmd *cobra.Command, parts []string) []string { + if cmd.HasParent() { + parts = extractCommandGroup(cmd.Parent(), parts) + parts = append(parts, cmd.Name()) + } + return parts +} + +// uniquePathsList deduplicates a given slice of strings without +// sorting or otherwise altering its order in any way. +func uniquePathsList(paths []string) []string { + seen := map[string]bool{} + newPaths := make([]string, 0, len(paths)) + for _, p := range paths { + if seen[p] { + continue + } + seen[p] = true + newPaths = append(newPaths, p) + } + return newPaths +} + +// Split up a command name, discard the initial prefix ("kn-") and convert +// parts to command syntax (i.e. replace _ with -) +func extractPluginCommandFromFileName(name string) []string { + // Remove extension on windows + name = stripWindowsExecExtensions(name) + parts := strings.Split(name, "-") + if len(parts) < 1 { + return []string{} + } + ret := make([]string, 0, len(parts)-1) + for _, p := range parts[1:] { + ret = append(ret, convertUnderscoreToDash(p)) + } + return ret +} + +// Strip any extension that indicates an EXE on Windows +func stripWindowsExecExtensions(name string) string { + if runtime.GOOS == "windows" { + ext := filepath.Ext(name) + if len(ext) > 0 { + for _, e := range windowsExecExtensions { + if ext == e { + name = name[:len(name)-len(ext)] + break + } + } + } + } + return name +} + +// Return the path and the parts building the most specific plugin in the given directory +// If lookupInPath is true, then also the OS PATH is checked. +// An error returned if any IO operation fails +func findMostSpecificPluginInPath(dir string, parts []string, lookupInPath bool) (Plugin, error) { + for i := len(parts); i > 0; i-- { + + // Construct plugin name to lookup + var nameParts []string + var commandParts []string + for _, p := range parts[0:i] { + // for arguments that contain the path separator, + // stop the loop once the separator appears + if strings.Contains(p, string(os.PathSeparator)) { + break + } + // Subcommands with "-" are translated to "_" + // (e.g. a command "kn log-all" is translated to a plugin "kn-log_all") + nameParts = append(nameParts, convertDashToUnderscore(p)) + commandParts = append(commandParts, p) + } + name := fmt.Sprintf("kn-%s", strings.Join(nameParts, "-")) + + // Check for the name in plugin directory and PATH (if requested) + path, err := findInDirOrPath(name, dir, lookupInPath) + if err != nil { + return nil, fmt.Errorf("cannot lookup plugin %s in directory %s (lookup in path: %t): %w", name, dir, lookupInPath, err) + } + + // Found, return it + if path != "" { + return &plugin{ + path: path, + commandParts: commandParts, + name: name, + }, nil + } + } + + // Nothing found + return nil, nil +} + +// convertDashToUnderscore converts from the command name to the file name +func convertDashToUnderscore(p string) string { + return strings.Replace(p, "-", "_", -1) +} + +// convertUnderscoreToDash converts from the filename to the command name +func convertUnderscoreToDash(p string) string { + return strings.Replace(p, "_", "-", -1) +} + +// Find a command with name in the given directory or on the execution PATH (if lookupInPath is true) +// On Windows, also check well known extensions for executables +// Return the full path found or "" if none has found +// Return an error on any IO error. +func findInDirOrPath(name string, dir string, lookupInPath bool) (string, error) { + + exts := []string{""} + if runtime.GOOS == "windows" { + // Add also well known extensions for windows + exts = append(exts, windowsExecExtensions...) + } + + for _, ext := range exts { + nameExt := name + ext + + // Check plugin dir first + path := filepath.Join(dir, nameExt) + _, err := os.Stat(path) + if err == nil { + // Found in dir + return path, nil + } + if !os.IsNotExist(err) { + return "", fmt.Errorf("i/o error while reading %s: %w", path, err) + } + + // Check in PATH if requested + if lookupInPath { + path, err = exec.LookPath(name) + if err == nil { + // Found in path + return path, nil + } + if !errors.Is(err, exec.ErrNotFound) { + return "", fmt.Errorf("error for looking up %s in path: %w", name, err) + } + } + } + + // Not found + return "", nil +} + +// lookupInternalPlugin looks up internally registered plugins. Return nil if none is found. +// Start with longest argument path first to find the most specific match +func lookupInternalPlugin(parts []string) Plugin { + for i := len(parts); i > 0; i-- { + checkParts := parts[0:i] + for _, plugin := range InternalPlugins { + if equalsSlice(plugin.CommandParts(), checkParts) { + return plugin + } + } + } + return nil +} + +// equalsSlice return true if two string slices contain the same elements +func equalsSlice(a, b []string) bool { + if len(a) != len(b) || len(a) == 0 { + return false + } + + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/vendor/knative.dev/client-pkg/pkg/plugin/stat.go b/vendor/knative.dev/client-pkg/pkg/plugin/stat.go new file mode 100644 index 0000000000..d7cd7a1949 --- /dev/null +++ b/vendor/knative.dev/client-pkg/pkg/plugin/stat.go @@ -0,0 +1,36 @@ +// Copyright © 2019 The Knative Authors +// +// 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. + +//go:build !windows +// +build !windows + +package plugin + +// This file doesn't compile for Windows platform, therefor a second stat_windows.go is +// added with a no-op + +import ( + "fmt" + "os" + "syscall" +) + +func statFileOwner(fileInfo os.FileInfo) (uint32, uint32, error) { + var sys *syscall.Stat_t + var ok bool + if sys, ok = fileInfo.Sys().(*syscall.Stat_t); !ok { + return 0, 0, fmt.Errorf("cannot check owner/group of file %s", fileInfo.Name()) + } + return sys.Uid, sys.Gid, nil +} diff --git a/vendor/knative.dev/client-pkg/pkg/plugin/stat_windows.go b/vendor/knative.dev/client-pkg/pkg/plugin/stat_windows.go new file mode 100644 index 0000000000..8a0af80f94 --- /dev/null +++ b/vendor/knative.dev/client-pkg/pkg/plugin/stat_windows.go @@ -0,0 +1,24 @@ +// Copyright © 2019 The Knative Authors +// +// 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 plugin + +import ( + "os" +) + +// statFileOwner is a no-op on windows +func statFileOwner(fileInfo os.FileInfo) (uint32, uint32, error) { + return 0, 0, nil +} diff --git a/vendor/knative.dev/client-pkg/pkg/plugin/verify.go b/vendor/knative.dev/client-pkg/pkg/plugin/verify.go new file mode 100644 index 0000000000..5f5a03cea7 --- /dev/null +++ b/vendor/knative.dev/client-pkg/pkg/plugin/verify.go @@ -0,0 +1,256 @@ +// Copyright © 2019 The Knative Authors +// +// 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 plugin + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "strings" +) + +// Collection of errors and warning collected during verifications +type VerificationErrorsAndWarnings struct { + Errors []string + Warnings []string +} + +// permission bits for execute +const ( + UserExecute = 1 << 6 + GroupExecute = 1 << 3 + OtherExecute = 1 << 0 +) + +// Verification of a ll plugins. This method returns all errors and warnings +// for the verification. The following criteria are verified (for each plugin): +// * If the plugin is executable +// * If the plugin is overshadowed by a previous plugin +func (manager *Manager) Verify() VerificationErrorsAndWarnings { + eaw := VerificationErrorsAndWarnings{} + + dirs, err := manager.pluginLookupDirectories() + if err != nil { + return eaw.AddError("cannot lookup plugin directories: %v", err) + } + + // Examine all files in possible plugin directories + + seenPlugins := make(map[string]string) + for _, dir := range dirs { + files, err := os.ReadDir(dir) + + // Ignore non-existing directories + if os.IsNotExist(err) { + continue + } + + if err != nil { + eaw.AddError("unable to read directory '%s' from your plugin path: %v", dir, err) + continue + } + + for _, f := range files { + if f.IsDir() { + continue + } + if !strings.HasPrefix(f.Name(), "kn-") { + continue + } + eaw = verifyPath(filepath.Join(dir, f.Name()), seenPlugins, eaw) + } + } + return eaw +} + +func verifyPath(path string, seenPlugins map[string]string, eaw VerificationErrorsAndWarnings) VerificationErrorsAndWarnings { + + // Verify that plugin actually exists + fileInfo, err := os.Stat(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return eaw.AddError("cannot find plugin in %s", path) + } + return eaw.AddError("cannot stat %s: %v", path, err) + } + + eaw = addWarningIfNotExecutable(eaw, path, fileInfo) + eaw = addWarningIfAlreadySeen(eaw, seenPlugins, path) + + // Remember each verified plugin for duplicate check + seenPlugins[filepath.Base(path)] = path + + return eaw +} + +func addWarningIfNotExecutable(eaw VerificationErrorsAndWarnings, path string, fileInfo os.FileInfo) VerificationErrorsAndWarnings { + if runtime.GOOS == "windows" { + return checkForWindowsExecutable(eaw, fileInfo, path) + } + + mode := fileInfo.Mode() + if !mode.IsRegular() && !isSymlink(mode) { + return eaw.AddWarning("%s is not a file", path) + } + perms := uint32(mode.Perm()) + + uid, gid, err := statFileOwner(fileInfo) + if err != nil { + return eaw.AddWarning("%s", err.Error()) + } + isOwner := checkIfUserIsFileOwner(uid) + isInGroup, err := checkIfUserInGroup(gid) + if err != nil { + return eaw.AddError("cannot get group ids for checking executable status of file %s", path) + } + + // User is owner and owner can execute + if canOwnerExecute(perms, isOwner) { + return eaw + } + + // User is in group which can execute, but user is not file owner + if canGroupExecute(perms, isOwner, isInGroup) { + return eaw + } + + // All can execute, and the user is not file owner and not in the file's perm group + if canOtherExecute(perms, isOwner, isInGroup) { + return eaw + } + + return eaw.AddWarning("%s is not executable by current user", path) +} + +func addWarningIfAlreadySeen(eaw VerificationErrorsAndWarnings, seenPlugins map[string]string, path string) VerificationErrorsAndWarnings { + fileName := filepath.Base(path) + if existingPath, ok := seenPlugins[fileName]; ok { + return eaw.AddWarning("%s is ignored because it is shadowed by an equally named plugin: %s", path, existingPath) + } + return eaw +} + +func checkForWindowsExecutable(eaw VerificationErrorsAndWarnings, fileInfo os.FileInfo, path string) VerificationErrorsAndWarnings { + name := fileInfo.Name() + nameWithoutExecExtension := stripWindowsExecExtensions(name) + + if name == nameWithoutExecExtension { + return eaw.AddWarning("%s is not executable as it does not have a Windows exec extension (one of %s)", path, strings.Join(windowsExecExtensions, ", ")) + } + return eaw +} + +func checkIfUserInGroup(gid uint32) (bool, error) { + groups, err := os.Getgroups() + if err != nil { + return false, err + } + for _, g := range groups { + if int(gid) == g { + return true, nil + } + } + return false, nil +} + +func checkIfUserIsFileOwner(uid uint32) bool { + return int(uid) == os.Getuid() +} + +// Check if all can execute, and the user is not file owner and not in the file's perm group +func canOtherExecute(perms uint32, isOwner bool, isInGroup bool) bool { + if perms&OtherExecute != 0 { + if os.Getuid() == 0 { + return true + } + if !isOwner && !isInGroup { + return true + } + } + return false +} + +// Check if user is owner and owner can execute +func canOwnerExecute(perms uint32, isOwner bool) bool { + if perms&UserExecute != 0 { + if os.Getuid() == 0 { + return true + } + if isOwner { + return true + } + } + return false +} + +// Check if user is in group which can execute, but user is not file owner +func canGroupExecute(perms uint32, isOwner bool, isInGroup bool) bool { + if perms&GroupExecute != 0 { + if os.Getuid() == 0 { + return true + } + if !isOwner && isInGroup { + return true + } + } + return false +} + +func (eaw *VerificationErrorsAndWarnings) AddError(format string, args ...interface{}) VerificationErrorsAndWarnings { + eaw.Errors = append(eaw.Errors, fmt.Sprintf(format, args...)) + return *eaw +} + +func (eaw *VerificationErrorsAndWarnings) AddWarning(format string, args ...interface{}) VerificationErrorsAndWarnings { + eaw.Warnings = append(eaw.Warnings, fmt.Sprintf(format, args...)) + return *eaw +} + +func (eaw *VerificationErrorsAndWarnings) PrintWarningsAndErrors(out io.Writer) { + printSection(out, "ERROR", eaw.Errors) + printSection(out, "WARNING", eaw.Warnings) +} + +func (eaw *VerificationErrorsAndWarnings) HasErrors() bool { + return len(eaw.Errors) > 0 +} + +func (eaw *VerificationErrorsAndWarnings) IsEmpty() bool { + return len(eaw.Errors)+len(eaw.Warnings) == 0 +} + +func printSection(out io.Writer, label string, values []string) { + if len(values) > 0 { + printLabelWithConditionalPluralS(out, label, len(values)) + for _, value := range values { + fmt.Fprintf(out, " - %s\n", value) + } + } +} + +func printLabelWithConditionalPluralS(out io.Writer, label string, nr int) { + if nr == 1 { + fmt.Fprintf(out, "%s:\n", label) + } else { + fmt.Fprintf(out, "%ss:\n", label) + } +} + +func isSymlink(mode os.FileMode) bool { + return mode&os.ModeSymlink != 0 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index bcad14e84e..46a384e36e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -934,6 +934,9 @@ k8s.io/utils/net k8s.io/utils/pointer k8s.io/utils/strings/slices k8s.io/utils/trace +# knative.dev/client-pkg v0.0.0-20230524014834-83c91f47f64f +## explicit; go 1.18 +knative.dev/client-pkg/pkg/plugin # knative.dev/eventing v0.37.1-0.20230524084610-5e245ac60ff1 ## explicit; go 1.19 knative.dev/eventing/pkg/apis/config