diff --git a/Makefile b/Makefile index c6b6c7c41..6209c685f 100644 --- a/Makefile +++ b/Makefile @@ -31,3 +31,10 @@ build-all-platforms: # Run all the tests and code checks ci: build-all-platforms test lint .PHONY: ci + +$(GOBIN)/mockgen: + @cd && GO111MODULE=on go get github.com/golang/mock/mockgen@v1.4.4 + +.PHONY: mocks +mocks: $(GOBIN)/mockgen + @go generate ./... diff --git a/go.mod b/go.mod index ebdda55c7..86638b69d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/AlecAivazis/survey/v2 v2.2.7 github.com/benbjohnson/clock v1.1.0 // indirect - github.com/briandowns/spinner v1.11.1 + github.com/briandowns/spinner v1.12.0 github.com/fatih/color v1.9.0 // indirect github.com/golang/mock v1.4.4 github.com/google/go-cmp v0.5.4 diff --git a/go.sum b/go.sum index 3b3a7ff89..ecbe6d9f4 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/briandowns/spinner v1.11.1 h1:OixPqDEcX3juo5AjQZAnFPbeUA0jvkp2qzB5gOZJ/L0= github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= +github.com/briandowns/spinner v1.12.0 h1:72O0PzqGJb6G3KgrcIOtL/JAGGZ5ptOMCn9cUHmqsmw= +github.com/briandowns/spinner v1.12.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= diff --git a/internal/ansi/spinner.go b/internal/ansi/spinner.go index e8af8ce13..9a88d1cd2 100644 --- a/internal/ansi/spinner.go +++ b/internal/ansi/spinner.go @@ -36,11 +36,6 @@ func Spinner(text string, fn func() error) error { s.FinalMSG = s.Prefix + spinnerTextFailed } - // FIXME(cyx): this is causing a race condition. The problem is - // with our dependency on briandowns/spinner. For now adding an - // artificial sleep removes the race condition. - time.Sleep(time.Microsecond) - s.Stop() }() diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 414b58598..1745e1cfc 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -45,6 +45,7 @@ var requiredScopes = []string{ "create:rules", "delete:rules", "read:rules", "update:rules", "read:client_keys", "read:logs", "create:roles", "delete:roles", "read:roles", "update:roles", + "create:custom_domains", "delete:custom_domains", "read:custom_domains", "update:custom_domains", } type Authenticator struct { diff --git a/internal/auth0/auth0.go b/internal/auth0/auth0.go index d21d45a57..5e1f04b7a 100644 --- a/internal/auth0/auth0.go +++ b/internal/auth0/auth0.go @@ -17,6 +17,7 @@ type API struct { Rule RuleAPI ResourceServer ResourceServerAPI Role RoleAPI + CustomDomain CustomDomainAPI } func NewAPI(m *management.Management) *API { @@ -30,6 +31,7 @@ func NewAPI(m *management.Management) *API { ResourceServer: m.ResourceServer, Rule: m.Rule, Role: m.Role, + CustomDomain: m.CustomDomain, } } diff --git a/internal/auth0/custom_domain.go b/internal/auth0/custom_domain.go new file mode 100644 index 000000000..970618476 --- /dev/null +++ b/internal/auth0/custom_domain.go @@ -0,0 +1,22 @@ +//go:generate mockgen -source=custom_domain.go -destination=custom_domain_mock.go -package=auth0 + +package auth0 + +import "gopkg.in/auth0.v5/management" + +type CustomDomainAPI interface { + // Create a new custom domain. + Create(r *management.CustomDomain, opts ...management.RequestOption) (err error) + + // Retrieve a custom domain configuration and status. + Read(id string, opts ...management.RequestOption) (c *management.CustomDomain, err error) + + // Run the verification process on a custom domain. + Verify(id string, opts ...management.RequestOption) (c *management.CustomDomain, err error) + + // Delete a custom domain and stop serving requests for it. + Delete(id string, opts ...management.RequestOption) (err error) + + // List all custom domains. + List(opts ...management.RequestOption) (c []*management.CustomDomain, err error) +} diff --git a/internal/auth0/custom_domain_mock.go b/internal/auth0/custom_domain_mock.go new file mode 100644 index 000000000..1d551aab1 --- /dev/null +++ b/internal/auth0/custom_domain_mock.go @@ -0,0 +1,131 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: custom_domain.go + +// Package auth0 is a generated GoMock package. +package auth0 + +import ( + gomock "github.com/golang/mock/gomock" + management "gopkg.in/auth0.v5/management" + reflect "reflect" +) + +// MockCustomDomainAPI is a mock of CustomDomainAPI interface +type MockCustomDomainAPI struct { + ctrl *gomock.Controller + recorder *MockCustomDomainAPIMockRecorder +} + +// MockCustomDomainAPIMockRecorder is the mock recorder for MockCustomDomainAPI +type MockCustomDomainAPIMockRecorder struct { + mock *MockCustomDomainAPI +} + +// NewMockCustomDomainAPI creates a new mock instance +func NewMockCustomDomainAPI(ctrl *gomock.Controller) *MockCustomDomainAPI { + mock := &MockCustomDomainAPI{ctrl: ctrl} + mock.recorder = &MockCustomDomainAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockCustomDomainAPI) EXPECT() *MockCustomDomainAPIMockRecorder { + return m.recorder +} + +// Create mocks base method +func (m *MockCustomDomainAPI) Create(r *management.CustomDomain, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{r} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create +func (mr *MockCustomDomainAPIMockRecorder) Create(r interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{r}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockCustomDomainAPI)(nil).Create), varargs...) +} + +// Read mocks base method +func (m *MockCustomDomainAPI) Read(id string, opts ...management.RequestOption) (*management.CustomDomain, error) { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Read", varargs...) + ret0, _ := ret[0].(*management.CustomDomain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read +func (mr *MockCustomDomainAPIMockRecorder) Read(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockCustomDomainAPI)(nil).Read), varargs...) +} + +// Verify mocks base method +func (m *MockCustomDomainAPI) Verify(id string, opts ...management.RequestOption) (*management.CustomDomain, error) { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Verify", varargs...) + ret0, _ := ret[0].(*management.CustomDomain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Verify indicates an expected call of Verify +func (mr *MockCustomDomainAPIMockRecorder) Verify(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockCustomDomainAPI)(nil).Verify), varargs...) +} + +// Delete mocks base method +func (m *MockCustomDomainAPI) Delete(id string, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete +func (mr *MockCustomDomainAPIMockRecorder) Delete(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCustomDomainAPI)(nil).Delete), varargs...) +} + +// List mocks base method +func (m *MockCustomDomainAPI) List(opts ...management.RequestOption) ([]*management.CustomDomain, error) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].([]*management.CustomDomain) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List +func (mr *MockCustomDomainAPIMockRecorder) List(opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockCustomDomainAPI)(nil).List), opts...) +} diff --git a/internal/cli/custom_domains.go b/internal/cli/custom_domains.go new file mode 100644 index 000000000..910dfdf1d --- /dev/null +++ b/internal/cli/custom_domains.go @@ -0,0 +1,266 @@ +package cli + +import ( + "github.com/AlecAivazis/survey/v2" + "github.com/auth0/auth0-cli/internal/ansi" + "github.com/auth0/auth0-cli/internal/auth0" + "github.com/spf13/cobra" + "gopkg.in/auth0.v5/management" +) + +func customDomainsCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "custom-domains", + Short: "manage resources for custom-domains.", + } + + cmd.SetUsageTemplate(resourceUsageTemplate()) + cmd.AddCommand(customDomainsListCmd(cli)) + cmd.AddCommand(customDomainsCreateCmd(cli)) + cmd.AddCommand(customDomainsDeleteCmd(cli)) + cmd.AddCommand(customDomainsGetCmd(cli)) + cmd.AddCommand(customDomainsVerifyCmd(cli)) + + return cmd +} + +func customDomainsListCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Get custom domains configurations", + Long: ` +Retrieve details on custom domains. + + $ auth0 custom-domains list + +`, + RunE: func(cmd *cobra.Command, args []string) error { + var customDomains []*management.CustomDomain + err := ansi.Spinner("Getting custom domains", func() error { + var err error + customDomains, err = cli.api.CustomDomain.List() + return err + }) + + if err != nil { + return err + } + + cli.renderer.CustomDomainList(customDomains) + return nil + }, + } + + return cmd +} + +func customDomainsCreateCmd(cli *cli) *cobra.Command { + var flags struct { + Domain string + Type string + VerificationMethod string + } + cmd := &cobra.Command{ + Use: "create", + Short: "Configure a new custom domain", + Long: ` +Create a new custom domain. + +Note: The custom domain will need to be verified before it will accept requests. + + $ auth0 custom-domain create --domain example.org --type auth0_managed_certs --type txt + +`, + RunE: func(cmd *cobra.Command, args []string) error { + + if !cmd.Flags().Changed("domain") { + qs := []*survey.Question{ + { + Name: "Domain", + Prompt: &survey.Input{ + Message: "Domain:", + Help: "Domain name.", + }, + }, + } + err := survey.Ask(qs, &flags) + if err != nil { + return err + } + } + + customDomain := &management.CustomDomain{ + Domain: auth0.String(flags.Domain), + Type: auth0.String(flags.Type), + VerificationMethod: auth0.String(flags.VerificationMethod), + } + + err := ansi.Spinner("Creating custom domain", func() error { + return cli.api.CustomDomain.Create(customDomain) + }) + if err != nil { + return err + } + + cli.renderer.CustomDomainCreate(customDomain) + return nil + }, + } + + cmd.Flags().StringVarP(&flags.Domain, "domain", "d", "", "Domain name.") + cmd.Flags().StringVarP(&flags.Type, "type", "t", "auth0_managed_certs", "Custom domain provisioning type. Must be auth0_managed_certs or self_managed_certs. Defaults to auth0_managed_certs") + cmd.Flags().StringVarP(&flags.VerificationMethod, "verification-method", "v", "txt", "Custom domain verification method. Must be txt.") + + return cmd +} + +func customDomainsDeleteCmd(cli *cli) *cobra.Command { + var flags struct { + CustomDomainID string + } + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete a custom domain configuration", + Long: ` +Delete a custom domain and stop serving requests for it. + + $ auth0 custom-domains delete --custom-domain-id myCustomDomainID +`, + RunE: func(cmd *cobra.Command, args []string) error { + + if !cmd.Flags().Changed("custom-domain-id") { + qs := []*survey.Question{ + { + Name: "CustomDomainID", + Prompt: &survey.Input{ + Message: "CustomDomainID:", + Help: "ID of the custom domain to delete.", + }, + }, + } + err := survey.Ask(qs, &flags) + if err != nil { + return err + } + } + + return ansi.Spinner("Deleting custom domain", func() error { + return cli.api.CustomDomain.Delete(flags.CustomDomainID) + }) + }, + } + + cmd.Flags().StringVarP(&flags.CustomDomainID, "custom-domain-id", "i", "", "ID of the custom domain to delete.") + + return cmd +} + +func customDomainsGetCmd(cli *cli) *cobra.Command { + var flags struct { + CustomDomainID string + } + cmd := &cobra.Command{ + Use: "get", + Short: "Get custom domain configuration", + Long: ` +Retrieve a custom domain configuration and status. + + $ auth0 custom-domain get --custom-domain-id myCustomDomainID +`, + RunE: func(cmd *cobra.Command, args []string) error { + + if !cmd.Flags().Changed("custom-domain-id") { + qs := []*survey.Question{ + { + Name: "CustomDomainID", + Prompt: &survey.Input{ + Message: "CustomDomainID:", + Help: "ID of the custom domain to retrieve.", + }, + }, + } + err := survey.Ask(qs, &flags) + if err != nil { + return err + } + } + + var customDomain *management.CustomDomain + err := ansi.Spinner("Getting custom domain", func() error { + var err error + customDomain, err = cli.api.CustomDomain.Read(flags.CustomDomainID) + return err + }) + if err != nil { + return err + } + + cli.renderer.CustomDomainGet(customDomain) + return nil + }, + } + + cmd.Flags().StringVarP(&flags.CustomDomainID, "custom-domain-id", "i", "", "ID of the custom domain to retrieve.") + + return cmd +} + +func customDomainsVerifyCmd(cli *cli) *cobra.Command { + var flags struct { + CustomDomainID string + } + cmd := &cobra.Command{ + Use: "verify", + Short: "Verify a custom domain", + Long: ` +Run the verification process on a custom domain. + +Note: Check the status field to see its verification status. Once verification is complete, it may take up to 10 minutes before the custom domain can start accepting requests. + +For self_managed_certs, when the custom domain is verified for the first time, the response will also include the cname_api_key which you will need to configure your proxy. This key must be kept secret, and is used to validate the proxy requests. + +Learn more about verifying custom domains that use Auth0 Managed certificates: + - https://auth0.com/docs/custom-domains#step-2-verify-ownership + +Learn more about verifying custom domains that use Self Managed certificates: + - https://auth0.com/docs/custom-domains/self-managed-certificates#step-2-verify-ownership + + $ auth0 custom-domain verify --custom-domain-id myCustomDomainID +`, + RunE: func(cmd *cobra.Command, args []string) error { + + if !cmd.Flags().Changed("custom-domain-id") { + qs := []*survey.Question{ + { + Name: "CustomDomainID", + Prompt: &survey.Input{ + Message: "CustomDomainID:", + Help: "ID of the custom domain to verify.", + }, + }, + } + err := survey.Ask(qs, &flags) + if err != nil { + return err + } + } + + var customDomain *management.CustomDomain + err := ansi.Spinner("Verifying custom domain", func() error { + var err error + customDomain, err = cli.api.CustomDomain.Verify(flags.CustomDomainID) + return err + }) + if err != nil { + return err + } + + cli.renderer.CustomDomainVerify(customDomain) + return nil + }, + } + + cmd.Flags().StringVarP(&flags.CustomDomainID, "custom-domain-id", "i", "", "ID of the custom domain to retrieve.") + + return cmd +} diff --git a/internal/cli/custom_domains_test.go b/internal/cli/custom_domains_test.go new file mode 100644 index 000000000..b18afda5f --- /dev/null +++ b/internal/cli/custom_domains_test.go @@ -0,0 +1,130 @@ +package cli + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/auth0/auth0-cli/internal/auth0" + "github.com/auth0/auth0-cli/internal/display" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "gopkg.in/auth0.v5/management" +) + +func TestCustomDomainsCmd(t *testing.T) { + t.Run("List", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + m := auth0.NewMockCustomDomainAPI(ctrl) + m.EXPECT().List().MaxTimes(1).Return([]*management.CustomDomain{{ID: auth0.String("testID"), Domain: auth0.String("testDomain"), Type: auth0.String("testType")}}, nil) + stdout := &bytes.Buffer{} + cli := &cli{ + renderer: &display.Renderer{ + MessageWriter: ioutil.Discard, + ResultWriter: stdout, + Format: display.OutputFormat("json"), + }, + api: &auth0.API{CustomDomain: m}, + } + + cmd := customDomainsListCmd(cli) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + + assert.JSONEq(t, `[{"ID": "testID", "Domain": "testDomain", "Type": "testType", "Primary": false, "Status": "", "VerificationMethod": "","Verification": {"Methods": null}}]`, stdout.String()) + }) + + t.Run("Create", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + m := auth0.NewMockCustomDomainAPI(ctrl) + m.EXPECT().Create(gomock.Any()).MaxTimes(1).Return(nil) + stdout := &bytes.Buffer{} + cli := &cli{ + renderer: &display.Renderer{ + MessageWriter: ioutil.Discard, + ResultWriter: stdout, + Format: display.OutputFormat("json"), + }, + api: &auth0.API{CustomDomain: m}, + } + + cmd := customDomainsCreateCmd(cli) + cmd.SetArgs([]string{"--domain=testDomain", "--type=testType", "--verification-method=testVerificationMethod"}) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + + assert.JSONEq(t, `[{"ID": "", "Domain": "testDomain", "Type": "testType", "Primary": false, "Status": "", "VerificationMethod": "testVerificationMethod", "Verification": {"Methods": null}}]`, stdout.String()) + }) + + t.Run("Delete", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + m := auth0.NewMockCustomDomainAPI(ctrl) + m.EXPECT().Delete(gomock.Any()).MaxTimes(1).Return(nil) + stdout := &bytes.Buffer{} + cli := &cli{ + renderer: &display.Renderer{ + MessageWriter: ioutil.Discard, + ResultWriter: stdout, + Format: display.OutputFormat("json"), + }, + api: &auth0.API{CustomDomain: m}, + } + + cmd := customDomainsDeleteCmd(cli) + cmd.SetArgs([]string{"--custom-domain-id=testCustomDomainID"}) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + }) + + t.Run("Get", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + m := auth0.NewMockCustomDomainAPI(ctrl) + m.EXPECT().Read(gomock.Any()).MaxTimes(1).Return(&management.CustomDomain{ID: auth0.String("testID"), Domain: auth0.String("testDomain"), Type: auth0.String("testType")}, nil) + stdout := &bytes.Buffer{} + cli := &cli{ + renderer: &display.Renderer{ + MessageWriter: ioutil.Discard, + ResultWriter: stdout, + Format: display.OutputFormat("json"), + }, + api: &auth0.API{CustomDomain: m}, + } + + cmd := customDomainsGetCmd(cli) + cmd.SetArgs([]string{"--custom-domain-id=testCustomDomainID"}) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + assert.JSONEq(t, `[{"ID": "testID", "Domain": "testDomain", "Type": "testType", "Primary": false, "Status": "", "VerificationMethod": "", "Verification": {"Methods": null}}]`, stdout.String()) + }) + + t.Run("Verify", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + m := auth0.NewMockCustomDomainAPI(ctrl) + m.EXPECT().Verify(gomock.Any()).MaxTimes(1).Return(&management.CustomDomain{ID: auth0.String("testID"), Domain: auth0.String("testDomain"), Type: auth0.String("testType")}, nil) + stdout := &bytes.Buffer{} + cli := &cli{ + renderer: &display.Renderer{ + MessageWriter: ioutil.Discard, + ResultWriter: stdout, + Format: display.OutputFormat("json"), + }, + api: &auth0.API{CustomDomain: m}, + } + + cmd := customDomainsVerifyCmd(cli) + cmd.SetArgs([]string{"--custom-domain-id=testCustomDomainID"}) + if err := cmd.Execute(); err != nil { + t.Fatal(err) + } + assert.JSONEq(t, `[{"ID": "testID", "Domain": "testDomain", "Type": "testType", "Primary": false, "Status": "", "VerificationMethod": "", "Verification": {"Methods": null}}]`, stdout.String()) + }) +} diff --git a/internal/cli/root.go b/internal/cli/root.go index 2936f141c..e0ec01bf1 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -66,6 +66,7 @@ func Execute() { rootCmd.AddCommand(connectionsCmd(cli)) rootCmd.AddCommand(completionCmd(cli)) rootCmd.AddCommand(rolesCmd(cli)) + rootCmd.AddCommand(customDomainsCmd(cli)) // TODO(cyx): backport this later on using latest auth0/v5. // rootCmd.AddCommand(actionsCmd(cli)) diff --git a/internal/display/custom_domains.go b/internal/display/custom_domains.go new file mode 100644 index 000000000..f97534753 --- /dev/null +++ b/internal/display/custom_domains.go @@ -0,0 +1,83 @@ +package display + +import ( + "strconv" + + "github.com/auth0/auth0-cli/internal/ansi" + "github.com/auth0/auth0-cli/internal/auth0" + "gopkg.in/auth0.v5/management" +) + +type customDomainView struct { + ID string + Domain string + Type string + Primary bool + Status string + VerificationMethod string + Verification customDomainVerificationView +} + +type customDomainVerificationView struct { + Methods []map[string]interface{} +} + +func (v *customDomainView) AsTableHeader() []string { + return []string{"Domain", "Custom Domain ID", "Type", "Primary", "Status", "Verification Method"} +} + +func (v *customDomainView) AsTableRow() []string { + return []string{v.Domain, v.ID, v.Type, strconv.FormatBool(v.Primary), v.Status, v.VerificationMethod} +} + +func (r *Renderer) CustomDomainList(customDomains []*management.CustomDomain) { + r.Heading(ansi.Bold(r.Tenant), "custom-domains\n") + var res []View + for _, c := range customDomains { + res = append(res, &customDomainView{ + ID: auth0.StringValue(c.ID), + Domain: auth0.StringValue(c.Domain), + Type: auth0.StringValue(c.Type), + Primary: auth0.BoolValue(c.Primary), + Status: auth0.StringValue(c.Status), + VerificationMethod: auth0.StringValue(c.VerificationMethod), + }) + } + r.Results(res) +} + +func (r *Renderer) CustomDomainCreate(customDomain *management.CustomDomain) { + r.Heading(ansi.Bold(r.Tenant), "custom-domain created\n") + r.Results([]View{&customDomainView{ + Domain: auth0.StringValue(customDomain.Domain), + Type: auth0.StringValue(customDomain.Type), + ID: auth0.StringValue(customDomain.ID), + Primary: auth0.BoolValue(customDomain.Primary), + Status: auth0.StringValue(customDomain.Status), + VerificationMethod: auth0.StringValue(customDomain.VerificationMethod), + }}) +} + +func (r *Renderer) CustomDomainGet(customDomain *management.CustomDomain) { + r.Heading(ansi.Bold(r.Tenant), "custom-domain\n") + r.Results([]View{&customDomainView{ + Domain: auth0.StringValue(customDomain.Domain), + Type: auth0.StringValue(customDomain.Type), + ID: auth0.StringValue(customDomain.ID), + Primary: auth0.BoolValue(customDomain.Primary), + Status: auth0.StringValue(customDomain.Status), + VerificationMethod: auth0.StringValue(customDomain.VerificationMethod), + }}) +} + +func (r *Renderer) CustomDomainVerify(customDomain *management.CustomDomain) { + r.Heading(ansi.Bold(r.Tenant), "custom-domain verified\n") + r.Results([]View{&customDomainView{ + Domain: auth0.StringValue(customDomain.Domain), + Type: auth0.StringValue(customDomain.Type), + ID: auth0.StringValue(customDomain.ID), + Primary: auth0.BoolValue(customDomain.Primary), + Status: auth0.StringValue(customDomain.Status), + VerificationMethod: auth0.StringValue(customDomain.VerificationMethod), + }}) +} diff --git a/vendor/github.com/briandowns/spinner/README.md b/vendor/github.com/briandowns/spinner/README.md index 516b0f37d..ddae178b6 100644 --- a/vendor/github.com/briandowns/spinner/README.md +++ b/vendor/github.com/briandowns/spinner/README.md @@ -17,52 +17,52 @@ go get github.com/briandowns/spinner ## Available Character Sets (Numbered by their slice index) -index | character set | sample gif -------|---------------|--------------- -0 | ```←↖↑↗→↘↓↙``` | ![Sample Gif](gifs/0.gif) -1 | ```▁▃▄▅▆▇█▇▆▅▄▃▁``` | ![Sample Gif](gifs/1.gif) -2 | ```▖▘▝▗``` | ![Sample Gif](gifs/2.gif) -3 | ```┤┘┴└├┌┬┐``` | ![Sample Gif](gifs/3.gif) -4 | ```◢◣◤◥``` | ![Sample Gif](gifs/4.gif) -5 | ```◰◳◲◱``` | ![Sample Gif](gifs/5.gif) -6 | ```◴◷◶◵``` | ![Sample Gif](gifs/6.gif) -7 | ```◐◓◑◒``` | ![Sample Gif](gifs/7.gif) -8 | ```.oO@*``` | ![Sample Gif](gifs/8.gif) -9 | ```\|/-\``` | ![Sample Gif](gifs/9.gif) -10 | ```◡◡⊙⊙◠◠``` | ![Sample Gif](gifs/10.gif) -11 | ```⣾⣽⣻⢿⡿⣟⣯⣷``` | ![Sample Gif](gifs/11.gif) -12 | ```>))'> >))'> >))'> >))'> >))'> <'((< <'((< <'((<``` | ![Sample Gif](gifs/12.gif) -13 | ```⠁⠂⠄⡀⢀⠠⠐⠈``` | ![Sample Gif](gifs/13.gif) -14 | ```⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏``` | ![Sample Gif](gifs/14.gif) -15 | ```abcdefghijklmnopqrstuvwxyz``` | ![Sample Gif](gifs/15.gif) -16 | ```▉▊▋▌▍▎▏▎▍▌▋▊▉``` | ![Sample Gif](gifs/16.gif) -17 | ```■□▪▫``` | ![Sample Gif](gifs/17.gif) -18 | ```←↑→↓``` | ![Sample Gif](gifs/18.gif) -19 | ```╫╪``` | ![Sample Gif](gifs/19.gif) -20 | ```⇐⇖⇑⇗⇒⇘⇓⇙``` | ![Sample Gif](gifs/20.gif) -21 | ```⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈``` | ![Sample Gif](gifs/21.gif) -22 | ```⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈``` | ![Sample Gif](gifs/22.gif) -23 | ```⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁``` | ![Sample Gif](gifs/23.gif) -24 | ```⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋``` | ![Sample Gif](gifs/24.gif) -25 | ```ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン``` | ![Sample Gif](gifs/25.gif) -26 | ```. .. ...``` | ![Sample Gif](gifs/26.gif) -27 | ```▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁``` | ![Sample Gif](gifs/27.gif) -28 | ```.oO°Oo.``` | ![Sample Gif](gifs/28.gif) -29 | ```+x``` | ![Sample Gif](gifs/29.gif) -30 | ```v<^>``` | ![Sample Gif](gifs/30.gif) -31 | ```>>---> >>---> >>---> >>---> >>---> <---<< <---<< <---<< <---<< <---<<``` | ![Sample Gif](gifs/31.gif) -32 | ```\| \|\| \|\|\| \|\|\|\| \|\|\|\|\| \|\|\|\|\|\| \|\|\|\|\| \|\|\|\| \|\|\| \|\| \|``` | ![Sample Gif](gifs/32.gif) -33 | ```[] [=] [==] [===] [====] [=====] [======] [=======] [========] [=========] [==========]``` | ![Sample Gif](gifs/33.gif) -34 | ```(*---------) (-*--------) (--*-------) (---*------) (----*-----) (-----*----) (------*---) (-------*--) (--------*-) (---------*)``` | ![Sample Gif](gifs/34.gif) -35 | ```█▒▒▒▒▒▒▒▒▒ ███▒▒▒▒▒▒▒ █████▒▒▒▒▒ ███████▒▒▒ ██████████``` | ![Sample Gif](gifs/35.gif) -36 | ```[ ] [=> ] [===> ] [=====> ] [======> ] [========> ] [==========> ] [============> ] [==============> ] [================> ] [==================> ] [===================>]``` | ![Sample Gif](gifs/36.gif) -37 | ```🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛``` | ![Sample Gif](gifs/37.gif) -38 | ```🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧``` | ![Sample Gif](gifs/38.gif) -39 | ```🌍 🌎 🌏``` | ![Sample Gif](gifs/39.gif) -40 | ```◜ ◝ ◞ ◟``` | ![Sample Gif](gifs/40.gif) -41 | ```⬒ ⬔ ⬓ ⬕``` | ![Sample Gif](gifs/41.gif) -42 | ```⬖ ⬘ ⬗ ⬙``` | ![Sample Gif](gifs/42.gif) -43 | ```[>>> >] []>>>> [] [] >>>> [] [] >>>> [] [] >>>> [] [] >>>>[] [>> >>]``` | ![Sample Gif](gifs/43.gif) +| index | character set | sample gif | +| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| 0 | ```←↖↑↗→↘↓↙``` | ![Sample Gif](gifs/0.gif) | +| 1 | ```▁▃▄▅▆▇█▇▆▅▄▃▁``` | ![Sample Gif](gifs/1.gif) | +| 2 | ```▖▘▝▗``` | ![Sample Gif](gifs/2.gif) | +| 3 | ```┤┘┴└├┌┬┐``` | ![Sample Gif](gifs/3.gif) | +| 4 | ```◢◣◤◥``` | ![Sample Gif](gifs/4.gif) | +| 5 | ```◰◳◲◱``` | ![Sample Gif](gifs/5.gif) | +| 6 | ```◴◷◶◵``` | ![Sample Gif](gifs/6.gif) | +| 7 | ```◐◓◑◒``` | ![Sample Gif](gifs/7.gif) | +| 8 | ```.oO@*``` | ![Sample Gif](gifs/8.gif) | +| 9 | ```\|/-\``` | ![Sample Gif](gifs/9.gif) | +| 10 | ```◡◡⊙⊙◠◠``` | ![Sample Gif](gifs/10.gif) | +| 11 | ```⣾⣽⣻⢿⡿⣟⣯⣷``` | ![Sample Gif](gifs/11.gif) | +| 12 | ```>))'> >))'> >))'> >))'> >))'> <'((< <'((< <'((<``` | ![Sample Gif](gifs/12.gif) | +| 13 | ```⠁⠂⠄⡀⢀⠠⠐⠈``` | ![Sample Gif](gifs/13.gif) | +| 14 | ```⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏``` | ![Sample Gif](gifs/14.gif) | +| 15 | ```abcdefghijklmnopqrstuvwxyz``` | ![Sample Gif](gifs/15.gif) | +| 16 | ```▉▊▋▌▍▎▏▎▍▌▋▊▉``` | ![Sample Gif](gifs/16.gif) | +| 17 | ```■□▪▫``` | ![Sample Gif](gifs/17.gif) | +| 18 | ```←↑→↓``` | ![Sample Gif](gifs/18.gif) | +| 19 | ```╫╪``` | ![Sample Gif](gifs/19.gif) | +| 20 | ```⇐⇖⇑⇗⇒⇘⇓⇙``` | ![Sample Gif](gifs/20.gif) | +| 21 | ```⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈``` | ![Sample Gif](gifs/21.gif) | +| 22 | ```⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈``` | ![Sample Gif](gifs/22.gif) | +| 23 | ```⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁``` | ![Sample Gif](gifs/23.gif) | +| 24 | ```⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋``` | ![Sample Gif](gifs/24.gif) | +| 25 | ```ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン``` | ![Sample Gif](gifs/25.gif) | +| 26 | ```. .. ...``` | ![Sample Gif](gifs/26.gif) | +| 27 | ```▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁``` | ![Sample Gif](gifs/27.gif) | +| 28 | ```.oO°Oo.``` | ![Sample Gif](gifs/28.gif) | +| 29 | ```+x``` | ![Sample Gif](gifs/29.gif) | +| 30 | ```v<^>``` | ![Sample Gif](gifs/30.gif) | +| 31 | ```>>---> >>---> >>---> >>---> >>---> <---<< <---<< <---<< <---<< <---<<``` | ![Sample Gif](gifs/31.gif) | +| 32 | ```\| \|\| \|\|\| \|\|\|\| \|\|\|\|\| \|\|\|\|\|\| \|\|\|\|\| \|\|\|\| \|\|\| \|\| \|``` | ![Sample Gif](gifs/32.gif) | +| 33 | ```[] [=] [==] [===] [====] [=====] [======] [=======] [========] [=========] [==========]``` | ![Sample Gif](gifs/33.gif) | +| 34 | ```(*---------) (-*--------) (--*-------) (---*------) (----*-----) (-----*----) (------*---) (-------*--) (--------*-) (---------*)``` | ![Sample Gif](gifs/34.gif) | +| 35 | ```█▒▒▒▒▒▒▒▒▒ ███▒▒▒▒▒▒▒ █████▒▒▒▒▒ ███████▒▒▒ ██████████``` | ![Sample Gif](gifs/35.gif) | +| 36 | ```[ ] [=> ] [===> ] [=====> ] [======> ] [========> ] [==========> ] [============> ] [==============> ] [================> ] [==================> ] [===================>]``` | ![Sample Gif](gifs/36.gif) | +| 37 | ```🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛``` | ![Sample Gif](gifs/37.gif) | +| 38 | ```🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧``` | ![Sample Gif](gifs/38.gif) | +| 39 | ```🌍 🌎 🌏``` | ![Sample Gif](gifs/39.gif) | +| 40 | ```◜ ◝ ◞ ◟``` | ![Sample Gif](gifs/40.gif) | +| 41 | ```⬒ ⬔ ⬓ ⬕``` | ![Sample Gif](gifs/41.gif) | +| 42 | ```⬖ ⬘ ⬗ ⬙``` | ![Sample Gif](gifs/42.gif) | +| 43 | ```[>>> >] []>>>> [] [] >>>> [] [] >>>> [] [] >>>> [] [] >>>>[] [>> >>]``` | ![Sample Gif](gifs/43.gif) | ## Features @@ -139,7 +139,7 @@ s.Prefix = "prefixed text: " // Prefix text before the spinner s.Suffix = " :appended text" // Append text after the spinner ``` -## Set or change the color of the spinner. Default color is white. This will restart the spinner with the new color. +## Set or change the color of the spinner. Default color is white. The spinner will need to be restarted to pick up the change. ```Go s.Color("red") // Set the spinner color to red diff --git a/vendor/github.com/briandowns/spinner/spinner.go b/vendor/github.com/briandowns/spinner/spinner.go index 29d98eb09..5ce68703e 100644 --- a/vendor/github.com/briandowns/spinner/spinner.go +++ b/vendor/github.com/briandowns/spinner/spinner.go @@ -14,13 +14,13 @@ package spinner import ( - "encoding/hex" "errors" "fmt" "io" "os" "runtime" "strconv" + "strings" "sync" "time" "unicode/utf8" @@ -285,10 +285,11 @@ func (s *Spinner) Start() { case <-s.stopChan: return default: + s.mu.Lock() if !s.active { + s.mu.Unlock() return } - s.mu.Lock() s.erase() if s.PreUpdate != nil { @@ -303,9 +304,9 @@ func (s *Spinner) Start() { outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix) } } else { - outColor = fmt.Sprintf("%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix) + outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix) } - outPlain := fmt.Sprintf("%s%s%s ", s.Prefix, s.chars[i], s.Suffix) + outPlain := fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix) fmt.Fprint(s.Writer, outColor) s.lastOutput = outPlain delay := s.Delay @@ -334,7 +335,7 @@ func (s *Spinner) Stop() { } s.erase() if s.FinalMSG != "" { - fmt.Fprintf(s.Writer, s.FinalMSG) + fmt.Fprint(s.Writer, s.FinalMSG) } s.stopChan <- struct{}{} } @@ -355,7 +356,8 @@ func (s *Spinner) Reverse() { } } -// Color will set the struct field for the given color to be used. +// Color will set the struct field for the given color to be used. The spinner +// will need to be explicitly restarted. func (s *Spinner) Color(colors ...string) error { colorAttributes := make([]color.Attribute, len(colors)) @@ -370,7 +372,6 @@ func (s *Spinner) Color(colors ...string) error { s.mu.Lock() s.color = color.New(colorAttributes...).SprintFunc() s.mu.Unlock() - s.Restart() return nil } @@ -393,20 +394,13 @@ func (s *Spinner) UpdateCharSet(cs []string) { func (s *Spinner) erase() { n := utf8.RuneCountInString(s.lastOutput) if runtime.GOOS == "windows" { - clearString := "\r" - for i := 0; i < n; i++ { - clearString += " " - } - clearString += "\r" - fmt.Fprintf(s.Writer, clearString) + clearString := "\r" + strings.Repeat(" ", n) + "\r" + fmt.Fprint(s.Writer, clearString) s.lastOutput = "" return } - del, _ := hex.DecodeString("7f") - for _, c := range []string{"\b", string(del)} { - for i := 0; i < n; i++ { - fmt.Fprintf(s.Writer, c) - } + for _, c := range []string{"\b", "\127", "\b", "\033[K"} { // "\033[K" for macOS Terminal + fmt.Fprint(s.Writer, strings.Repeat(c, n)) } fmt.Fprintf(s.Writer, "\r\033[K") // erases to end of line s.lastOutput = "" diff --git a/vendor/modules.txt b/vendor/modules.txt index 6f53e5c35..deeac9a9b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -7,7 +7,7 @@ github.com/AlecAivazis/survey/v2/terminal github.com/PuerkitoBio/rehttp # github.com/benbjohnson/clock v1.1.0 ## explicit -# github.com/briandowns/spinner v1.11.1 +# github.com/briandowns/spinner v1.12.0 ## explicit github.com/briandowns/spinner # github.com/davecgh/go-spew v1.1.1