Skip to content

Commit

Permalink
Merge pull request #176 from manifoldco/manifold-alias
Browse files Browse the repository at this point in the history
$ manifold alias command
  • Loading branch information
jeffandersen authored Nov 3, 2017
2 parents 3b6705a + d9abde0 commit 5606160
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added
- Client-side name generation to `manifold create`
- `manifold alias` can rename configuration keys on a per-resource basis

### Fixed

Expand Down
6 changes: 3 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ required=[

[[constraint]]
name = "github.com/manifoldco/go-manifold"
version = "0.8.7"
version = "0.8.8"

[[constraint]]
name = "github.com/manifoldco/promptui"
Expand Down
158 changes: 158 additions & 0 deletions cmd/alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package main

import (
"context"
"fmt"

"github.com/urfave/cli"

"github.com/manifoldco/manifold-cli/api"
"github.com/manifoldco/manifold-cli/clients"
"github.com/manifoldco/manifold-cli/color"
"github.com/manifoldco/manifold-cli/data/catalog"
"github.com/manifoldco/manifold-cli/middleware"
"github.com/manifoldco/manifold-cli/prompts"

"github.com/manifoldco/manifold-cli/generated/marketplace/client/credential"
"github.com/manifoldco/manifold-cli/generated/marketplace/models"
)

func init() {
updateCmd := cli.Command{
Name: "alias",
ArgsUsage: "[resource-name]",
Usage: "Rename credential keys to match your needs",
Category: "RESOURCES",
Action: middleware.Chain(middleware.EnsureSession, middleware.LoadDirPrefs,
middleware.LoadTeamPrefs, aliasCredentialCmd),
Flags: append(teamFlags, []cli.Flag{
titleFlag(),
projectFlag(),
}...),
}

cmds = append(cmds, updateCmd)
}

func aliasCredentialCmd(cliCtx *cli.Context) error {
ctx := context.Background()

if err := maxOptionalArgsLength(cliCtx, 1); err != nil {
return err
}

name, err := optionalArgName(cliCtx, 0, "resource")
if err != nil {
return err
}

teamID, err := validateTeamID(cliCtx)
if err != nil {
return err
}

project, err := validateName(cliCtx, "project")
if err != nil {
return err
}

client, err := api.New(api.Catalog, api.Marketplace)
if err != nil {
return err
}

catalog, err := catalog.New(ctx, client.Catalog)
if err != nil {
return cli.NewExitError("Failed to fetch catalog data: "+err.Error(), -1)
}

prompts.SpinStart("Fetching Resources")
resourceResults, err := clients.FetchResources(ctx, client.Marketplace, teamID, project)
prompts.SpinStop()
if err != nil {
return cli.NewExitError("Could not retrieve resources: "+err.Error(), -1)
}
var resources []*models.Resource
for _, r := range resourceResults {
if r.Body.Source != nil && *r.Body.Source != "custom" {
resources = append(resources, r)
}
}
if len(resources) == 0 {
return cli.NewExitError("No resources found", -1)
}

projects, err := clients.FetchProjects(ctx, client.Marketplace, teamID)
if err != nil {
return cli.NewExitError(
fmt.Sprintf("Failed to fetch list of projects: %s", err), -1)
}

var selectedResource *models.Resource
if name != "" {
var err error
res, err := pickResourcesByName(resources, projects, name)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to fetch resource: %s", err), -1)
}
selectedResource = res
} else {
idx, _, err := prompts.SelectResource(resources, projects, name)
if err != nil {
return prompts.HandleSelectError(err, "Could not select Resource")
}
res := resources[idx]
selectedResource = res
}
if selectedResource.Body.Source == nil || *selectedResource.Body.Source == "custom" {
cli.NewExitError("Custom resources should be managed with `manifold config`", -1)
}

product, err := catalog.GetProduct(*selectedResource.Body.ProductID)
if err != nil {
cli.NewExitError("Product referenced by resource does not exist: "+
err.Error(), -1)
}

prompts.SpinStart("Fetching Credentials")
cMap, err := fetchCredentials(ctx, client.Marketplace, []*models.Resource{selectedResource}, false)
prompts.SpinStop()
if err != nil {
return cli.NewExitError("Could not retrieve credentials: "+err.Error(), -1)
}
var creds []*models.Credential
for _, cred := range cMap {
creds = append(creds, cred...)
}
if len(creds) < 1 {
return cli.NewExitError("No credentials found to be aliased", -1)
}

fmt.Printf("Choose from one of %s's credentials\n", product.Body.Name)
cred, originalName, aliasName, err := prompts.CredentialAlias(creds)
if err != nil {
return prompts.HandleSelectError(err, "Could not alias Credential")
}

alias := make(map[string]string)
alias[originalName] = aliasName
params := credential.NewPatchCredentialsIDParamsWithContext(ctx)
params.SetID(cred.ID.String())
params.SetBody(&models.UpdateCredential{
Body: &models.UpdateCredentialBody{
CustomNames: alias,
},
})
_, err = client.Marketplace.Credential.PatchCredentialsID(params, nil)
if err != nil {
return cli.NewExitError("Could not create alias: "+err.Error(), -1)
}

fmt.Println("")
if originalName == aliasName {
fmt.Printf("You have cleared the alias for %s's `%s` key.\n", color.Bold(selectedResource.Body.Label), color.Bold(originalName))
} else {
fmt.Printf("You have aliased %s's config key `%s` to `%s`.\n", color.Bold(selectedResource.Body.Label), color.Bold(originalName), color.Bold(aliasName))
}
return nil
}
2 changes: 1 addition & 1 deletion cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func deleteCmd(cliCtx *cli.Context) error {

var resource *mModels.Resource
if resourceName != "" {
resource, err = pickResourcesByName(resources, resourceName)
resource, err = pickResourcesByName(resources, projects, resourceName)
if err != nil {
return cli.NewExitError(
fmt.Sprintf("Failed to find resource \"%s\": %s", resourceName, err), -1)
Expand Down
8 changes: 6 additions & 2 deletions cmd/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func export(cliCtx *cli.Context) error {
return resources[i].Body.Name < resources[j].Body.Name
})

cMap, err := fetchCredentials(ctx, client.Marketplace, resources)
cMap, err := fetchCredentials(ctx, client.Marketplace, resources, true)
if err != nil {
return cli.NewExitError("Could not retrieve credentials: "+err.Error(), -1)
}
Expand Down Expand Up @@ -185,13 +185,17 @@ func indexResources(resources []*models.Resource) map[manifold.ID]*models.Resour
return index
}

func fetchCredentials(ctx context.Context, m *mClient.Marketplace, resources []*models.Resource) (map[manifold.ID][]*models.Credential, error) {
func fetchCredentials(ctx context.Context, m *mClient.Marketplace, resources []*models.Resource, customNames bool) (map[manifold.ID][]*models.Credential, error) {
// XXX: Reduce this into a single HTTP Call
//
// Issue: https://www.github.com/manifoldco/engineering#2536
cMap := make(map[manifold.ID][]*models.Credential)
for _, r := range resources {
p := credential.NewGetCredentialsParamsWithContext(ctx).WithResourceID([]string{r.ID.String()})
if customNames == false {
noCustomNames := "false"
p.SetCustomNames(&noCustomNames)
}
c, err := m.Credential.GetCredentials(p, nil)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func run(cliCtx *cli.Context) error {
return cli.NewExitError("Could not retrieve resources: "+err.Error(), -1)
}

cMap, err := fetchCredentials(ctx, client.Marketplace, rs)
cMap, err := fetchCredentials(ctx, client.Marketplace, rs, true)
if err != nil {
return cli.NewExitError("Could not retrieve credentials: "+err.Error(), -1)
}
Expand Down
15 changes: 6 additions & 9 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func updateResourceCmd(cliCtx *cli.Context) error {
var resource *models.Resource
if name != "" {
var err error
resource, err = pickResourcesByName(resources, name)
resource, err = pickResourcesByName(resources, projects, name)
if err != nil {
return cli.NewExitError(fmt.Sprintf("Failed to fetch resource: %s", err), -1)
}
Expand Down Expand Up @@ -118,18 +118,15 @@ func updateResourceCmd(cliCtx *cli.Context) error {
return nil
}

func pickResourcesByName(resources []*models.Resource, name string) (*models.Resource, error) {
func pickResourcesByName(resources []*models.Resource, projects []*models.Project, name string) (*models.Resource, error) {
if name == "" {
return nil, errs.ErrResourceNotFound
}

for _, resource := range resources {
if string(resource.Body.Label) == name {
return resource, nil
}
idx, _, err := prompts.SelectResource(resources, projects, name)
if err != nil {
return nil, err
}

return nil, errs.ErrResourceNotFound
return resources[idx], nil
}

func updateResource(ctx context.Context, r *models.Resource,
Expand Down
2 changes: 1 addition & 1 deletion cmd/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func view(cliCtx *cli.Context) error {

var resource *models.Resource
if resourceName != "" {
resource, err = pickResourcesByName(resources, resourceName)
resource, err = pickResourcesByName(resources, projects, resourceName)
if err != nil {
return cli.NewExitError(
fmt.Sprintf("Failed to find resource \"%s\": %s", resourceName, err), -1)
Expand Down
31 changes: 31 additions & 0 deletions prompts/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package prompts
import (
"errors"
"fmt"
"regexp"
"strings"

"github.com/asaskevich/govalidator"
Expand All @@ -13,6 +14,8 @@ import (

"github.com/manifoldco/manifold-cli/errs"
"github.com/manifoldco/manifold-cli/prompts/templates"

mModels "github.com/manifoldco/manifold-cli/generated/marketplace/models"
)

const (
Expand Down Expand Up @@ -328,3 +331,31 @@ func CreditCard() (*stripe.Token, error) {

return tkn, nil
}

var configKeyRegexp = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{0,1000}$`)

// CredentialAlias asks the user to supply a new alias for the given cred
func CredentialAlias(creds []*mModels.Credential) (*mModels.Credential, string, string, error) {
c, originalName, err := SelectCredential(creds)
if err != nil {
return nil, "", "", err
}

fmt.Printf("Creating alias for `%s`\n", originalName)

p := promptui.Prompt{
Label: "Alias Name",
Validate: func(input string) error {
if !configKeyRegexp.MatchString(input) {
return errors.New("Please provide a valid alias")
}
return nil
},
}
newName, err := p.Run()
if err != nil {
return nil, "", "", err
}

return c, originalName, newName, nil
}
33 changes: 33 additions & 0 deletions prompts/selects.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,36 @@ func SelectAPIToken(tokens []*iModels.APIToken) (*iModels.APIToken, error) {

return tokens[idx], nil
}

// SelectCredential prompts the user to choose from a list of credentials
func SelectCredential(creds []*mModels.Credential) (*mModels.Credential, string, error) {
var labels []string
var keyNames []string
var items []*mModels.Credential

for _, c := range creds {
for k := range c.Body.Values {
value := k
if c.Body.CustomNames != nil {
if alias, ok := c.Body.CustomNames[k]; ok {
value = fmt.Sprintf("%s (%s)", value, alias)
}
}
keyNames = append(keyNames, k)
labels = append(labels, value)
items = append(items, c)
}
}

prompt := promptui.Select{
Label: "Select Credential",
Items: labels,
}

idx, _, err := prompt.Run()
if err != nil {
return nil, "", err
}

return items[idx], keyNames[idx], nil
}
Loading

0 comments on commit 5606160

Please sign in to comment.