feat: add additional generator abilities to use current tokens to generate output & make caching optional

Signed-off-by: cpendery <cpendery@vt.edu>
This commit is contained in:
cpendery
2023-09-17 02:54:30 -04:00
parent 1d4b288a5d
commit 32c5ee8a32
9 changed files with 78 additions and 95 deletions
+3 -3
View File
@@ -122,7 +122,7 @@ func getSubcommandDrivenRecommendation(spec model.Subcommand, persistentOptions
}
if len(spec.Args) != 0 {
activeArg := spec.Args[0]
getGeneratorDrivenRecommendations(activeArg.Generator, &suggestions, partialToken, spec.FilterStrategy)
getGeneratorDrivenRecommendations(activeArg.Generator, &suggestions, partialToken, spec.FilterStrategy, acceptedTokens)
getSuggestionDrivenRecommendations(activeArg.Suggestions, &suggestions, partialToken, spec.FilterStrategy)
getTemplateDrivenRecommendations(activeArg.Templates, &suggestions, partialToken, spec.FilterStrategy)
}
@@ -146,7 +146,7 @@ func getArgDrivenRecommendation(args []model.Arg, spec model.Subcommand, persist
partialToken = &partialCmd.token
}
getGeneratorDrivenRecommendations(activeArg.Generator, &suggestions, partialToken, activeArg.FilterStrategy)
getGeneratorDrivenRecommendations(activeArg.Generator, &suggestions, partialToken, activeArg.FilterStrategy, acceptedTokens)
getSuggestionDrivenRecommendations(activeArg.Suggestions, &suggestions, partialToken, activeArg.FilterStrategy)
getTemplateDrivenRecommendations(activeArg.Templates, &suggestions, partialToken, activeArg.FilterStrategy)
@@ -249,7 +249,7 @@ func handleArg(tokens []commandToken, args []model.Arg, spec model.Subcommand, p
}
return
}
return handleArg(tokens[1:], args[1:], spec, persistentOptions, getPersistentTokens(acceptedTokens), fromOption)
return handleArg(tokens[1:], args[1:], spec, persistentOptions, acceptedTokens, fromOption)
}
func loadSuggestions(cmd string) (suggestions model.TermSuggestions, charsInLastCmd int) {
+1 -1
View File
@@ -140,7 +140,7 @@ func request(name, prompt, message, splitOn string) []model.TermSuggestion {
func AI(name string, prompt PromptFunction, message MessageFunction, splitOn string) *model.Generator {
return &model.Generator{
Id: uuid.New(),
Function: func() []model.TermSuggestion {
Function: func(_ []string) []model.TermSuggestion {
suggestions := []model.TermSuggestion{}
if !enabled() {
return suggestions
+62 -45
View File
@@ -1,68 +1,85 @@
package azure
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os/exec"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault"
"github.com/cpendery/clac/autocomplete/model"
"github.com/google/uuid"
)
type azShowAccountResponse struct {
SubscriptionName *string `json:"name,omitempty"`
type azListKeyvaultsResponse struct {
VaultName *string `json:"name,omitempty"`
}
func getActiveSubscription() *string {
output, err := exec.Command("az", "show", "account", "-o", "json").Output()
if err != nil {
slog.Error("az account information command failed", slog.String("error", err.Error()))
return nil
}
var accountInfo azShowAccountResponse
if err := json.Unmarshal(output, &accountInfo); err != nil {
slog.Error("az account information command provided invalid output", slog.String("error", err.Error()))
return nil
}
return accountInfo.SubscriptionName
type azListKeyvaultKeysResponse struct {
KeyName *string `json:"name,omitempty"`
}
var ListKeyVaultsGenerator = &model.Generator{
Id: uuid.New(),
Function: func() []model.TermSuggestion {
Id: uuid.New(),
Script: "az keyvault list -o json",
PostProcess: func(s string) []model.TermSuggestion {
suggestions := []model.TermSuggestion{}
subscription := getActiveSubscription()
if subscription == nil {
vaults := []azListKeyvaultsResponse{}
if err := json.Unmarshal([]byte(s), &vaults); err != nil {
slog.Error("unable to load list keyvault response", slog.String("error", err.Error()))
return suggestions
}
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
slog.Error("unable to load azure default credentials", slog.String("error", err.Error()))
return suggestions
}
client, err := armkeyvault.NewVaultsClient(*subscription, cred, nil)
if err != nil {
slog.Error("unable to create new azure vaults client", slog.String("error", err.Error()))
return suggestions
}
pager := client.NewListPager(nil)
for pager.More() {
page, err := pager.NextPage(context.Background())
if err != nil {
slog.Error("unable to request new page of vaults", slog.String("error", err.Error()))
return suggestions
}
for _, vault := range page.Value {
if vault.Name != nil {
suggestions = append(suggestions, model.TermSuggestion{
Name: *vault.Name,
Description: "Azure Key Vault",
})
}
for _, vault := range vaults {
if vault.VaultName == nil {
continue
}
suggestions = append(suggestions, model.TermSuggestion{
Name: `"` + *vault.VaultName + `"`,
Description: "Azure Key Vault",
})
}
return suggestions
},
}
var ListKeyVaultsKeysGenerator = &model.Generator{
Id: uuid.New(),
SkipCache: true,
Function: func(cmdTokens []string) []model.TermSuggestion {
suggestions := []model.TermSuggestion{}
var vaultName *string = nil
for idx, token := range cmdTokens {
if strings.TrimSpace(token) == "--vault-name" && idx+1 < len(cmdTokens) {
vaultName = &cmdTokens[idx+1]
}
}
if vaultName == nil {
return suggestions
}
output, err := exec.Command("az", fmt.Sprintf("keyvault key list --vault-name %s -o json", *vaultName)).Output()
if err != nil {
slog.Error("unable to request list keys", slog.String("error", err.Error()))
return suggestions
}
keys := []azListKeyvaultKeysResponse{}
if err := json.Unmarshal([]byte(output), &keys); err != nil {
slog.Error("unable to load list keys response", slog.String("error", err.Error()))
return suggestions
}
for _, key := range keys {
if key.KeyName == nil {
continue
}
suggestions = append(suggestions, model.TermSuggestion{
Name: `"` + *key.KeyName + `"`,
Description: "Azure Key Vault",
})
}
return suggestions
},
+3 -3
View File
@@ -13,8 +13,8 @@ var (
generatorCache = make(map[uuid.UUID][]model.TermSuggestion)
)
func Run(g model.Generator) []model.TermSuggestion {
if cachedSuggestions, executed := generatorCache[g.Id]; executed {
func Run(g model.Generator, token []string) []model.TermSuggestion {
if cachedSuggestions, executed := generatorCache[g.Id]; executed && !g.SkipCache {
return cachedSuggestions
}
suggestions := []model.TermSuggestion{}
@@ -42,7 +42,7 @@ func Run(g model.Generator) []model.TermSuggestion {
}
if g.Function != nil {
suggestions = append(suggestions, g.Function()...)
suggestions = append(suggestions, g.Function(token)...)
}
suggestions = append(suggestions, RunTemplates(g.Template)...)
+2 -1
View File
@@ -67,10 +67,11 @@ type ProcessedToken struct {
type Generator struct {
Id uuid.UUID
Script string
Function func() []TermSuggestion
Function func([]string) []TermSuggestion
PostProcess func(string) []TermSuggestion
Template []Template
SplitOn string
SkipCache bool
}
type Template string
+1 -1
View File
@@ -85,7 +85,7 @@ func init() {
{Name: []string{"show"}, Description: "Get a key's attributes and, if it's an asymmetric key, its public material.", Options: []model.Option{
{Name: []string{"--id"}, Description: "Id of the key. If specified all other 'Id' arguments should be omitted."},
{Name: []string{"--name", "-n"}, Description: "Name of the key. Required if --id is not specified", Args: []model.Arg{
{Name: "key name"},
{Name: "key name", Generator: azure.ListKeyVaultsKeysGenerator},
}},
{Name: []string{"--vault-name"}, Description: "Name of the Vault.", Args: []model.Arg{
{Name: "vault name", Generator: azure.ListKeyVaultsGenerator},
+6 -2
View File
@@ -91,11 +91,15 @@ func filterMatch[M matchable](items []M, suggestions *[]model.TermSuggestion, in
}
}
func getGeneratorDrivenRecommendations(g *model.Generator, suggestions *[]model.TermSuggestion, input *string, filterStrategy model.FilterStrategy) {
func getGeneratorDrivenRecommendations(g *model.Generator, suggestions *[]model.TermSuggestion, input *string, filterStrategy model.FilterStrategy, processedTokens []model.ProcessedToken) {
if g == nil {
return
}
filterMatch[model.TermSuggestion](generators.Run(*g), suggestions, input, filterStrategy)
termTokens := []string{}
for _, t := range processedTokens {
termTokens = append(termTokens, t.Token)
}
filterMatch[model.TermSuggestion](generators.Run(*g, termTokens), suggestions, input, filterStrategy)
}
func getTemplateDrivenRecommendations(templates []model.Template, suggestions *[]model.TermSuggestion, input *string, filterStrategy model.FilterStrategy) {
-10
View File
@@ -3,8 +3,6 @@ module github.com/cpendery/clac
go 1.21.0
require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0
github.com/adrg/xdg v0.4.0
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
@@ -19,28 +17,20 @@ require (
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
-29
View File
@@ -1,17 +1,3 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 h1:LNHhpdK7hzUcx/k1LIcuh5k7k1LGIWLQfCjaneSj7Fc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0 h1:8d4U82r7ItT1Es91x3eUcAQweih36KWvUha8AZ9X0Rs=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0/go.mod h1:/1bkGperHinQbAHMWivoec/Ucu6//iXo6jn5mhmqCVU=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
@@ -30,16 +16,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
@@ -59,8 +39,6 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -78,16 +56,12 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
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/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
@@ -95,7 +69,6 @@ golang.org/x/sync v0.1.0/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/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=
@@ -123,8 +96,6 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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=