From ae3e12904f68843d8783bb1aa9c23ea57811f93a Mon Sep 17 00:00:00 2001 From: Richard Weston <22933083+riweston@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:03:11 +0000 Subject: [PATCH] refactor: Complete rewrite (#20) * refactor(#major): Complete rewrite * chore: Allow ambiguous patch version of Go * fix: Strip the config file usage as I don't use this really as a flag * chore: Linter issues with unhandled errors returned, these now panic properly * fix: Remove an unused test file * chore: Rename packages as the linter doesn't like underscores * ci: Bump actions versions * ci: Super-linter being not so super, switching out for golangci-lint * docs: Update the readme to reflect the current state and new feature * ci: Let dependabot handle GHA dependancy management --- .github/dependabot.yml | 6 +- .github/workflows/ci.lint_test.yml | 36 +++-- LICENSE | 223 +++-------------------------- README.md | 10 +- cmd/aztx/contexts.go | 136 ------------------ cmd/root.go | 120 ++++++++++++++++ go.mod | 41 +++++- go.sum | 143 ++++++++++++++---- go.work | 2 + go.work.sum | 124 ++++++++++++++++ main.go | 30 ++-- pkg/profile/azureProfile.json | 47 ++++++ pkg/profile/config.go | 74 ++++++++++ pkg/profile/errors.go | 74 ++++++++++ pkg/profile/models.go | 25 ++++ pkg/profile/terminal.go | 23 +++ pkg/profile/user_profile.go | 128 +++++++++++++++++ pkg/profile/user_profile_test.go | 58 ++++++++ pkg/state/state.go | 47 ++++++ test/aztx_function_test.go | 29 ---- test/aztx_integration_test.go | 24 ---- test/azureProfile.json | 47 ------ test/helpers_test.go | 36 ----- 23 files changed, 944 insertions(+), 539 deletions(-) delete mode 100644 cmd/aztx/contexts.go create mode 100644 cmd/root.go create mode 100644 go.work create mode 100644 go.work.sum create mode 100644 pkg/profile/azureProfile.json create mode 100644 pkg/profile/config.go create mode 100644 pkg/profile/errors.go create mode 100644 pkg/profile/models.go create mode 100644 pkg/profile/terminal.go create mode 100644 pkg/profile/user_profile.go create mode 100644 pkg/profile/user_profile_test.go create mode 100644 pkg/state/state.go delete mode 100644 test/aztx_function_test.go delete mode 100644 test/aztx_integration_test.go delete mode 100644 test/azureProfile.json delete mode 100644 test/helpers_test.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 36b24f2..32787ea 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,4 +3,8 @@ updates: - package-ecosystem: gomod directory: / schedule: - interval: daily + interval: weekly + - package-ecosystem: github-actions + directory: .github/workflows + schedule: + interval: weekly diff --git a/.github/workflows/ci.lint_test.yml b/.github/workflows/ci.lint_test.yml index ccb83e4..27f7166 100644 --- a/.github/workflows/ci.lint_test.yml +++ b/.github/workflows/ci.lint_test.yml @@ -10,22 +10,23 @@ on: jobs: Lint: name: "CI: Linting" - runs-on: ubuntu-latest - + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - - name: Lint Code Base - uses: docker://ghcr.io/github/super-linter:v4 - env: - VALIDATE_ALL_CODEBASE: true - DEFAULT_BRANCH: master - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VALIDATE_GO: true - FILTER_REGEX_EXCLUDE: ".*test/.*" + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: stable + - name: go mod tidy + run: go mod tidy + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 Test: name: "CI: Go Tests" @@ -35,18 +36,13 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: arnested/go-version-action@v1 - id: go-version - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: ${{ steps.go-version.outputs.latest }} - check-latest: true - - name: Checkout code - uses: actions/checkout@v2 + go-version: stable - name: Test run: | go mod tidy diff --git a/LICENSE b/LICENSE index d645695..5ac678e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,202 +1,21 @@ - - 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. +The MIT License (MIT) + +Copyright © 2024 Richard Weston + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 78cd9e9..65c2472 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

aztx

- Version + Version Documentation @@ -9,11 +9,13 @@ Maintenance - License: Apache + License: MIT

-> This tool is a helper for azure-cli that leverages fzf for a nice interface to switch between subscription contexts. +This tool is a helper for azure-cli that leverages fzf for a nice interface to switch between subscription contexts. + +Additionally it can also be used to quickly switch back to a previous subscription context using the `aztx -` command in a similar way to `cd -` in bash. ### 🏠 [Homepage](https://github.com/riweston/aztx#readme) @@ -78,7 +80,7 @@ Give a ⭐️ if this project helped you! ## 📝 License Copyright © 2021 [Richard Weston](https://github.com/riweston).
-This project is [Apache](https://github.com/riweston/aztx/blob/master/LICENSE) licensed. +This project is [MIT](https://github.com/riweston/aztx/blob/master/LICENSE) licensed. *** _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ diff --git a/cmd/aztx/contexts.go b/cmd/aztx/contexts.go deleted file mode 100644 index fbeaf04..0000000 --- a/cmd/aztx/contexts.go +++ /dev/null @@ -1,136 +0,0 @@ -/* -Copyright © 2021 Richard Weston github@riweston.io - -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 aztx - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "os" - - "github.com/google/uuid" - "github.com/ktr0731/go-fuzzyfinder" -) - -const ( - InfoColor = "\033[0;32m%s\033[0m" - NoticeColor = "\033[0;36m%s\033[0m" - WarningColor = "\033[1;33m%s\033[0m" - ErrorColor = "\033[1;31m%s\033[0m" - DebugColor = "\033[0;36m%s\033[0m" -) - -type Subscription struct { - EnvironmentName string `json:"environmentName"` - HomeTenantID uuid.UUID `json:"homeTenantId"` - ID uuid.UUID `json:"id"` - IsDefault bool `json:"isDefault"` - ManagedByTenants []string `json:"managedByTenants"` - Name string `json:"name"` - State string `json:"state"` - TenantID uuid.UUID `json:"tenantId"` - User struct { - Name string `json:"name"` - AccountType string `json:"type"` - } `json:"user"` -} - -type File struct { - InstallationID uuid.UUID `json:"installationId"` - Subscriptions []Subscription `json:"subscriptions"` -} - -func SelectAzureAccountsDisplayName() { - home, errHome := os.UserHomeDir() - if errHome != nil { - panic(errHome) - } - azureProfile := home + "/.azure/azureProfile.json" - d := ReadAzureProfile(azureProfile) - currentCtx := ReadAzureProfileDefault(d) - - idx, err := fuzzyfinder.Find( - d.Subscriptions, - func(i int) string { - return d.Subscriptions[i].Name - }, - fuzzyfinder.WithHeader(currentCtx)) - if err != nil { - fmt.Printf(NoticeColor, "cancelled\n") - msg := fmt.Sprintf("%s\n", currentCtx) - fmt.Printf(InfoColor, msg) - return - } - - errWrite := WriteAzureProfile(d, d.Subscriptions[idx].ID, azureProfile) - if errWrite != nil { - panic(errWrite) - } - msg := fmt.Sprintf("Set Context: %s (%s)\n", d.Subscriptions[idx].Name, d.Subscriptions[idx].ID) - fmt.Printf(InfoColor, msg) -} - -func ReadAzureProfile(file string) File { - jsonFile, err := os.Open(file) - if err != nil { - fmt.Println(err) - } - byteValue, errByte := ioutil.ReadAll(jsonFile) - if errByte != nil { - fmt.Println(errByte) - } - byteValue = bytes.TrimPrefix(byteValue, []byte("\xef\xbb\xbf")) - var jsonData File - errJSON := json.Unmarshal(byteValue, &jsonData) - if errJSON != nil { - fmt.Println(err) - } - - return jsonData -} - -func ReadAzureProfileDefault(file File) (subscription string) { - var subscriptionName string - var subscriptionID uuid.UUID - - for idx := range file.Subscriptions { - if file.Subscriptions[idx].IsDefault { - subscriptionName = file.Subscriptions[idx].Name - subscriptionID = file.Subscriptions[idx].ID - } - } - return fmt.Sprintf("Current Context: %s (%s)", subscriptionName, subscriptionID) -} - -func WriteAzureProfile(file File, id uuid.UUID, outFile string) error { - for idx := range file.Subscriptions { - if file.Subscriptions[idx].ID == id { - file.Subscriptions[idx].IsDefault = true - } else { - file.Subscriptions[idx].IsDefault = false - } - } - - byteValue, err := json.Marshal(&file) - if err != nil { - return err - } - - err = ioutil.WriteFile(outFile, byteValue, 0600) - return err -} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..11511f1 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,120 @@ +/* +Copyright © 2024 Richard Weston + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "errors" + "fmt" + "github.com/ktr0731/go-fuzzyfinder" + "github.com/riweston/aztx/pkg/profile" + "github.com/riweston/aztx/pkg/state" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "aztx", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Args: cobra.MaximumNArgs(1), + // Uncomment the following line if your bare application + // has an action associated with it: + Run: func(cmd *cobra.Command, args []string) { + cfg := state.ViperAdapter{Viper: viper.GetViper()} + lc := state.NewStateReaderWriter(&cfg) + userProfileAdapter := profile.UserProfileFileAdapter{} + c := profile.NewConfigurationAdapter(&userProfileAdapter) + + if len(args) > 0 { + if args[0] == "-" { + if err := c.SetPreviousContext(lc); err != nil { + fmt.Println(err) + os.Exit(1) + } else { + os.Exit(0) + } + } + + } + ac, err := c.SelectWithFinder() + if errors.Is(err, fuzzyfinder.ErrAbort) { + os.Exit(0) + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } + if err := c.SetContext(lc, ac); err != nil { + fmt.Println(err) + os.Exit(1) + } + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".aztx" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yml") + viper.SetConfigName(".aztx") + if err := viper.ReadInConfig(); err != nil { + // If the config file doesn't exist, create it. + if err := viper.SafeWriteConfigAs(home + "/.aztx.yml"); err != nil { + fmt.Println("Can't write config:", err) + os.Exit(1) + } + } +} diff --git a/go.mod b/go.mod index 136f119..d95e0ba 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,43 @@ module github.com/riweston/aztx -go 1.16 +go 1.22 require ( - github.com/google/uuid v1.3.0 - github.com/ktr0731/go-fuzzyfinder v0.6.0 + github.com/google/uuid v1.6.0 + github.com/ktr0731/go-fuzzyfinder v0.8.0 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 +) + +require ( + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gdamore/encoding v1.0.0 // indirect + github.com/gdamore/tcell/v2 v2.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/ktr0731/go-ansisgr v0.1.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/nsf/termbox-go v1.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.3 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6b7f390..46af24c 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,129 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM= -github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= +github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/ktr0731/go-fuzzyfinder v0.6.0 h1:lP6B3B8CjqbKGf/K5f1X5kdpxiSkCH0+9AzgA3Cm+VU= -github.com/ktr0731/go-fuzzyfinder v0.6.0/go.mod h1:QrbU5RFMEFBbPZnlJBqctX6028IV8qW/yCX3DCAzi1Y= -github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= -github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ktr0731/go-ansisgr v0.1.0 h1:fbuupput8739hQbEmZn1cEKjqQFwtCCZNznnF6ANo5w= +github.com/ktr0731/go-ansisgr v0.1.0/go.mod h1:G9lxwgBwH0iey0Dw5YQd7n6PmQTwTuTM/X5Sgm/UrzE= +github.com/ktr0731/go-fuzzyfinder v0.8.0 h1:+yobwo9lqZZ7jd1URPdCgZXTE2U1mpIVTkQoo4roi6w= +github.com/ktr0731/go-fuzzyfinder v0.8.0/go.mod h1:Bjpz5im+tppKE9Ii6UK1h+6RaX/lUvJ0ruO4LIYRkqo= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/nsf/termbox-go v0.0.0-20201124104050-ed494de23a00 h1:Rl8NelBe+n7SuLbJyw13ho7CGWUt2BjGGKIoreCWQ/c= -github.com/nsf/termbox-go v0.0.0-20201124104050-ed494de23a00/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= +github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= -golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.work b/go.work new file mode 100644 index 0000000..c9e7481 --- /dev/null +++ b/go.work @@ -0,0 +1,2 @@ +go 1.22 +use . diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..ca83e6d --- /dev/null +++ b/go.work.sum @@ -0,0 +1,124 @@ +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw= +cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= +cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= +github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= +github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= +github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= +github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/sagikazarmark/crypt v0.17.0 h1:ZA/7pXyjkHoK4bW4mIdnCLvL8hd+Nrbiw7Dqk7D4qUk= +github.com/sagikazarmark/crypt v0.17.0/go.mod h1:SMtHTvdmsZMuY/bpZoqokSoChIrcJ/epOxZN58PbZDg= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= +go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= +go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0= +go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= +go.etcd.io/etcd/client/v2 v2.305.10 h1:MrmRktzv/XF8CvtQt+P6wLUlURaNpSDJHFZhe//2QE4= +go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= +go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao= +go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4= +google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= +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-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/main.go b/main.go index 3e89a73..38c4151 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,28 @@ /* -Copyright © 2021 NAME HERE +Copyright © 2024 Richard Weston -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 +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ package main -import aztx "github.com/riweston/aztx/cmd/aztx" +import "github.com/riweston/aztx/cmd" func main() { - aztx.SelectAzureAccountsDisplayName() + cmd.Execute() } diff --git a/pkg/profile/azureProfile.json b/pkg/profile/azureProfile.json new file mode 100644 index 0000000..42ea0e2 --- /dev/null +++ b/pkg/profile/azureProfile.json @@ -0,0 +1,47 @@ +{ + "installationId": "e960b7cc-c5d9-11ea-a6f5-00155d82a4f4", + "subscriptions": [ + { + "id": "9e7969ef-4cb8-4a2d-959f-bfdaae452a3d", + "name": "A reeeeeally long subscription name that might be truncated.", + "state": "Enabled", + "user": { + "name": "First Last", + "type": "user" + }, + "isDefault": false, + "tenantId": "9e7969ef-4cb8-4a2d-959f-bfdaae452a3d", + "environmentName": "AzureCloud", + "homeTenantId": "9e7969ef-4cb8-4a2d-959f-bfdaae452a3d", + "managedByTenants": [] + }, + { + "id": "9bb28eee-ebaa-442a-83ba-5511810fb151", + "name": "Test Subscription 2", + "state": "Enabled", + "user": { + "name": "First Last", + "type": "user" + }, + "isDefault": false, + "tenantId": "9bb28eee-ebaa-442a-83ba-5511810fb151", + "environmentName": "AzureCloud", + "homeTenantId": "9bb28eee-ebaa-442a-83ba-5511810fb151", + "managedByTenants": [] + }, + { + "id": "8fff24dd-2842-4dbb-8a66-1410c7bc231f", + "name": "Short", + "state": "Enabled", + "user": { + "name": "First Last", + "type": "user" + }, + "isDefault": false, + "tenantId": "8fff24dd-2842-4dbb-8a66-1410c7bc231f", + "environmentName": "AzureCloud", + "homeTenantId": "8fff24dd-2842-4dbb-8a66-1410c7bc231f", + "managedByTenants": [] + } + ] +} diff --git a/pkg/profile/config.go b/pkg/profile/config.go new file mode 100644 index 0000000..20d23b4 --- /dev/null +++ b/pkg/profile/config.go @@ -0,0 +1,74 @@ +package profile + +import ( + "errors" + "fmt" + "github.com/google/uuid" + "github.com/ktr0731/go-fuzzyfinder" + azurestate "github.com/riweston/aztx/pkg/state" +) + +type ConfigurationAdapter struct { + userProfile userProfileReadWriter +} + +func NewConfigurationAdapter(userProfile userProfileReadWriter) *ConfigurationAdapter { + return &ConfigurationAdapter{ + userProfile: userProfile, + } +} + +func (c *ConfigurationAdapter) SelectWithFinder() (*Subscription, error) { + cfg, err := c.userProfile.Read() + if err != nil { + return nil, ErrReadingConfiguration(err) + } + idx, err := c.userProfile.Find(cfg) + if errors.Is(err, fuzzyfinder.ErrAbort) { + PrintNotice("Operation aborted") + return nil, err + } + if err != nil { + return nil, ErrSelectingSubscription(err) + } + return &cfg.Subscriptions[idx], nil +} + +func (c *ConfigurationAdapter) SetPreviousContext(lastContext *azurestate.LastContext) error { + lastContextId := lastContext.ReadLastContextId() + lastContextDisplayName := lastContext.ReadLastContextDisplayName() + if lastContextId == "" || lastContextDisplayName == "" { + return ErrNoPreviousContext + } + if err := c.SetContext(lastContext, &Subscription{ID: uuid.MustParse(lastContextId), Name: lastContextDisplayName}); err != nil { + return ErrSettingPreviousContext(err) + } + return nil +} + +func (c *ConfigurationAdapter) SetContext(lastContext *azurestate.LastContext, selectedContext *Subscription) error { + cfg, err := c.userProfile.Read() + var errNotFound bool + if err != nil { + return ErrReadingConfiguration(err) + } + for i, sub := range cfg.Subscriptions { + if sub.IsDefault { + lastContext.WriteLastContext(sub.ID.String(), sub.Name) + } + cfg.Subscriptions[i].IsDefault = false + if sub.ID == selectedContext.ID { + errNotFound = false + cfg.Subscriptions[i].IsDefault = true + } + } + if errNotFound { + return ErrSubscriptionNotFound + } + err = c.userProfile.Write(cfg) + if err != nil { + return ErrWritingConfiguration(err) + } + PrintInfo(fmt.Sprintf("Switched to \"%s\" (%s)", selectedContext.Name, selectedContext.ID)) + return nil +} diff --git a/pkg/profile/errors.go b/pkg/profile/errors.go new file mode 100644 index 0000000..5460013 --- /dev/null +++ b/pkg/profile/errors.go @@ -0,0 +1,74 @@ +package profile + +import ( + "errors" + "fmt" +) + +// The errors file contains the error types used by the azure_cli package. + +var ( + // ErrFileDoesNotExist is returned when the file does not exist. + ErrFileDoesNotExist = func(err error) error { + return fmt.Errorf("file does not exist: %w", err) + } + + // ErrGettingHomeDirectory is returned when there is an error getting the home directory. + ErrGettingHomeDirectory = func(err error) error { + return fmt.Errorf("error getting home directory: %w", err) + } + + // ErrMarshallingJSON is returned when there is an error marshalling JSON. + ErrMarshallingJSON = func(err error) error { + return fmt.Errorf("error marshalling JSON: %w", err) + } + + // ErrUnmarshallingJSON is returned when there is an error unmarshalling JSON. + ErrUnmarshallingJSON = func(err error) error { + return fmt.Errorf("error unmarshalling JSON: %w", err) + } + + // ErrWritingFile is returned when there is an error writing the file. + ErrWritingFile = func(err error) error { + return fmt.Errorf("error writing file: %w", err) + } + + // ErrReadingFile is returned when there is an error reading the file. + ErrReadingFile = func(err error) error { + return fmt.Errorf("error reading file: %w", err) + } + + // ErrPathIsEmpty is returned when the sampleConfigFilePath is empty. + ErrPathIsEmpty = errors.New("sampleConfigFilePath is empty") + + // ErrReadingConfiguration is returned when there is an error reading the configuration. + ErrReadingConfiguration = func(err error) error { + return fmt.Errorf("error reading configuration: %w", err) + } + + // ErrWritingConfiguration is returned when there is an error writing the configuration. + ErrWritingConfiguration = func(err error) error { + return fmt.Errorf("error writing configuration: %w", err) + } + + // ErrSelectingSubscription is returned when there is an error selecting the subscription. + ErrSelectingSubscription = func(err error) error { + return fmt.Errorf("error selecting subscription: %w", err) + } + + // ErrSettingPreviousContext is returned when there is an error setting the previous context. + ErrSettingPreviousContext = func(err error) error { + return fmt.Errorf("error setting previous context: %w", err) + } + + // ErrNoPreviousContext is returned when there is no previous context. + ErrNoPreviousContext = errors.New("no previous context, check ~/.aztx.yml is present and has content") + + // ErrSubscriptionNotFound is returned when there is no subscription found. + ErrSubscriptionNotFound = errors.New("no subscription found") + + // ErrFetchingUserProfile is returned when there is an error fetching the user profile. + ErrFetchingUserProfile = func(err error) error { + return fmt.Errorf("error fetching user profile: %w", err) + } +) diff --git a/pkg/profile/models.go b/pkg/profile/models.go new file mode 100644 index 0000000..fe9c219 --- /dev/null +++ b/pkg/profile/models.go @@ -0,0 +1,25 @@ +package profile + +import "github.com/google/uuid" + +type Configuration struct { + InstallationID uuid.UUID `json:"installationId"` + Subscriptions []Subscription `json:"subscriptions"` +} + +type Subscription struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + State string `json:"state"` + User struct { + Name string `json:"name"` + Type string `json:"type"` + } `json:"user"` + IsDefault bool `json:"isDefault"` + TenantID uuid.UUID `json:"tenantId"` + EnvironmentName string `json:"environmentName"` + HomeTenantID uuid.UUID `json:"homeTenantId"` + ManagedByTenants []struct { + TenantID uuid.UUID `json:"tenantId"` + } `json:"managedByTenants"` +} diff --git a/pkg/profile/terminal.go b/pkg/profile/terminal.go new file mode 100644 index 0000000..d15126b --- /dev/null +++ b/pkg/profile/terminal.go @@ -0,0 +1,23 @@ +package profile + +import "fmt" + +// The terminal file contains helper functions primarily for sending messages to the terminal. + +const ( + InfoColor = "\033[0;32m%s\033[0m" + NoticeColor = "\033[0;36m%s\033[0m" + WarningColor = "\033[1;33m%s\033[0m" + ErrorColor = "\033[1;31m%s\033[0m" + DebugColor = "\033[0;36m%s\033[0m" +) + +// PrintInfo prints an info message to the terminal. +func PrintInfo(message string) { + fmt.Println(fmt.Sprintf(InfoColor, message)) +} + +// PrintNotice prints a notice message to the terminal. +func PrintNotice(message string) { + fmt.Println(fmt.Sprintf(NoticeColor, message)) +} diff --git a/pkg/profile/user_profile.go b/pkg/profile/user_profile.go new file mode 100644 index 0000000..2051649 --- /dev/null +++ b/pkg/profile/user_profile.go @@ -0,0 +1,128 @@ +package profile + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/ktr0731/go-fuzzyfinder" + "io" + "os" +) + +type userProfileReadWriter interface { + Fetch() error + Read() (*Configuration, error) + Write(*Configuration) error + Find(*Configuration) (int, error) +} + +type UserProfileFileAdapter struct { + path string + configuration *Configuration +} + +func (u *UserProfileFileAdapter) Fetch() error { + if u.path != "" { + return nil + } + home, err := os.UserHomeDir() + if err != nil { + return ErrGettingHomeDirectory(err) + } + defaultPath := home + "/.azure/azureProfile.json" + if _, err := os.Stat(defaultPath); os.IsNotExist(err) { + return ErrFileDoesNotExist(err) + } + u.path = defaultPath + return nil +} + +func (u *UserProfileFileAdapter) Write(cfg *Configuration) error { + if u.path == "" { + return ErrPathIsEmpty + } + jsonData, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return ErrMarshallingJSON(err) + } + err = os.WriteFile(u.path, jsonData, 0644) + if err != nil { + return ErrWritingFile(err) + } + return nil +} + +func (u *UserProfileFileAdapter) Read() (*Configuration, error) { + if u.configuration != nil { + return u.configuration, nil + } + if err := u.Fetch(); err != nil { + return nil, ErrFetchingUserProfile(err) + } + file, err := u.openConfigFile() + if err != nil { + return nil, ErrReadingFile(err) + } + var d Configuration + err = u.unmarshalConfig(file, &d) + if err != nil { + return nil, ErrUnmarshallingJSON(err) + } + u.configuration = &d + return u.configuration, nil +} + +func (u *UserProfileFileAdapter) openConfigFile() ([]byte, error) { + if u.path == "" { + return nil, ErrPathIsEmpty + } + file, err := os.Open(u.path) + if err != nil { + return nil, err + } + defer file.Close() + b, err := io.ReadAll(file) + if err != nil { + return nil, err + } + return b, nil +} + +func (u *UserProfileFileAdapter) unmarshalConfig(data []byte, d *Configuration) error { + // handle zero width space character + data = bytes.Replace(data, []byte("\uFEFF"), []byte(""), -1) + err := json.Unmarshal(data, &d) + if err != nil { + return err + } + return nil +} + +func (u *UserProfileFileAdapter) Find(cfg *Configuration) (int, error) { + columnWidth := u.longestDisplayNameCharacterWidth() + format := fmt.Sprintf("%%-%ds %%s", columnWidth) + idx, err := fuzzyfinder.Find( + cfg.Subscriptions, + func(i int) string { + if cfg.Subscriptions[i].IsDefault { + currentContext := fmt.Sprintf(format, cfg.Subscriptions[i].Name, cfg.Subscriptions[i].ID) + return currentContext + } + return fmt.Sprintf(format, cfg.Subscriptions[i].Name, cfg.Subscriptions[i].ID) + }, + ) + if err != nil { + return 0, err + } + return idx, nil +} + +func (u *UserProfileFileAdapter) longestDisplayNameCharacterWidth() int { + var max int + for _, sub := range u.configuration.Subscriptions { + if len(sub.Name) > max { + max = len(sub.Name) + } + } + return max + 2 +} diff --git a/pkg/profile/user_profile_test.go b/pkg/profile/user_profile_test.go new file mode 100644 index 0000000..72f3f0f --- /dev/null +++ b/pkg/profile/user_profile_test.go @@ -0,0 +1,58 @@ +package profile + +import ( + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestUserProfileFileAdapter_Read(t *testing.T) { + type fields struct { + sampleConfigFilePath string + } + tests := []struct { + name string + fields fields + assertion assert.ComparisonAssertionFunc + expected *Configuration + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Reads the configuration file", + fields: fields{ + sampleConfigFilePath: "azureProfile.json", + }, + assertion: assert.EqualValues, + expected: &Configuration{ + Subscriptions: []Subscription{ + { + ID: uuid.MustParse("9e7969ef-4cb8-4a2d-959f-bfdaae452a3d"), + Name: "A reeeeeally long subscription name that might be truncated.", + }, + { + ID: uuid.MustParse("9bb28eee-ebaa-442a-83ba-5511810fb151"), + Name: "Test Subscription 2", + }, + { + ID: uuid.MustParse("8fff24dd-2842-4dbb-8a66-1410c7bc231f"), + Name: "Short", + }, + }, + }, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + u := &UserProfileFileAdapter{ + path: tt.fields.sampleConfigFilePath, + } + got, err := u.Read() + for i := range got.Subscriptions { + tt.assertion(t, tt.expected.Subscriptions[i].ID, got.Subscriptions[i].ID) + tt.assertion(t, tt.expected.Subscriptions[i].Name, got.Subscriptions[i].Name) + } + tt.wantErr(t, err) + }) + } +} diff --git a/pkg/state/state.go b/pkg/state/state.go new file mode 100644 index 0000000..bf43b73 --- /dev/null +++ b/pkg/state/state.go @@ -0,0 +1,47 @@ +package state + +import "github.com/spf13/viper" + +type stateReaderWriter interface { + Read(key string) string + Write(key, value string) +} + +type LastContext struct { + rw stateReaderWriter +} + +func NewStateReaderWriter(rw stateReaderWriter) *LastContext { + return &LastContext{ + rw: rw, + } +} + +func (lc *LastContext) ReadLastContextId() string { + return lc.rw.Read("lastContextId") + +} + +func (lc *LastContext) ReadLastContextDisplayName() string { + return lc.rw.Read("lastContextDisplayName") +} + +func (lc *LastContext) WriteLastContext(id string, name string) { + lc.rw.Write("lastContextId", id) + lc.rw.Write("lastContextDisplayName", name) +} + +type ViperAdapter struct { + Viper *viper.Viper +} + +func (v *ViperAdapter) Read(key string) string { + return v.Viper.GetString(key) +} + +func (v *ViperAdapter) Write(key, value string) { + v.Viper.Set(key, value) + if err := v.Viper.WriteConfig(); err != nil { + panic(err) + } +} diff --git a/test/aztx_function_test.go b/test/aztx_function_test.go deleted file mode 100644 index 873e3e1..0000000 --- a/test/aztx_function_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build !integration - -package aztx_test - -import ( - "testing" - - "github.com/google/uuid" - "github.com/riweston/aztx/cmd/aztx" -) - -func TestReadFile(t *testing.T) { - value := aztx.ReadAzureProfile("azureProfile.json") - equals(t, "Test Subscription 1", value.Subscriptions[0].Name) -} - -func TestWriteFile(t *testing.T) { - inputFile := aztx.ReadAzureProfile("azureProfile.json") - uuid, err := uuid.Parse("9e7969ef-4cb8-4a2d-959f-bfdaae452a3d") - if err != nil { - panic(err) - } - outFile := aztx.WriteAzureProfile(inputFile, uuid, "azureProfile_test.json") - ok(t, outFile) - value := aztx.ReadAzureProfile("azureProfile_test.json") - equals(t, true, value.Subscriptions[0].IsDefault) - equals(t, false, value.Subscriptions[1].IsDefault) - equals(t, false, value.Subscriptions[2].IsDefault) -} diff --git a/test/aztx_integration_test.go b/test/aztx_integration_test.go deleted file mode 100644 index 36b08dc..0000000 --- a/test/aztx_integration_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build integration - -package aztx - -import ( - "os/exec" - "testing" -) - -func TestAzBinary(t *testing.T) { - _, err := exec.LookPath("az") - - if err != nil { - t.Errorf(err.Error()) - } -} - -func TestFzfBinary(t *testing.T) { - _, err := exec.LookPath("fzf") - - if err != nil { - t.Errorf(err.Error()) - } -} diff --git a/test/azureProfile.json b/test/azureProfile.json deleted file mode 100644 index e97e14a..0000000 --- a/test/azureProfile.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "installationId": "e960b7cc-c5d9-11ea-a6f5-00155d82a4f4", - "subscriptions": [ - { - "id": "9e7969ef-4cb8-4a2d-959f-bfdaae452a3d", - "name": "Test Subscription 1", - "state": "Enabled", - "user": { - "name": "First Last", - "type": "user" - }, - "isDefault": false, - "tenantId": "9e7969ef-4cb8-4a2d-959f-bfdaae452a3d", - "environmentName": "AzureCloud", - "homeTenantId": "9e7969ef-4cb8-4a2d-959f-bfdaae452a3d", - "managedByTenants": [] - }, - { - "id": "9bb28eee-ebaa-442a-83ba-5511810fb151", - "name": "Test Subscription 2", - "state": "Enabled", - "user": { - "name": "First Last", - "type": "user" - }, - "isDefault": false, - "tenantId": "9bb28eee-ebaa-442a-83ba-5511810fb151", - "environmentName": "AzureCloud", - "homeTenantId": "9bb28eee-ebaa-442a-83ba-5511810fb151", - "managedByTenants": [] - }, - { - "id": "8fff24dd-2842-4dbb-8a66-1410c7bc231f", - "name": "Test Subscription 3", - "state": "Enabled", - "user": { - "name": "First Last", - "type": "user" - }, - "isDefault": false, - "tenantId": "8fff24dd-2842-4dbb-8a66-1410c7bc231f", - "environmentName": "AzureCloud", - "homeTenantId": "8fff24dd-2842-4dbb-8a66-1410c7bc231f", - "managedByTenants": [] - } - ] -} diff --git a/test/helpers_test.go b/test/helpers_test.go deleted file mode 100644 index 19adf7f..0000000 --- a/test/helpers_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package aztx_test - -import ( - "fmt" - "path/filepath" - "reflect" - "runtime" - "testing" -) - -// assert fails the test if the condition is false. -func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { - if !condition { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) - tb.FailNow() - } -} - -// ok fails the test if an err is not nil. -func ok(tb testing.TB, err error) { - if err != nil { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) - tb.FailNow() - } -} - -// equals fails the test if exp is not equal to act. -func equals(tb testing.TB, exp interface{}, act interface{}) { - if !reflect.DeepEqual(exp, act) { - _, file, line, _ := runtime.Caller(1) - fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) - tb.FailNow() - } -}