Skip to content

Commit b1a4a69

Browse files
committed
refactor: configure output centrally to allow testing output
1 parent b813d74 commit b1a4a69

File tree

109 files changed

+1098
-606
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+1098
-606
lines changed

api/client.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/base64"
66
"fmt"
7+
"io"
78
"os"
89
"os/user"
910
"path/filepath"
@@ -31,6 +32,8 @@ type Client struct {
3132
Project string
3233
Log *log.Client
3334
KubeconfigContext string
35+
36+
writer format.Writer
3437
}
3538

3639
type ClientOpt func(c *Client) error
@@ -43,6 +46,7 @@ func New(ctx context.Context, apiClusterContext, project string, opts ...ClientO
4346
client := &Client{
4447
Project: project,
4548
KubeconfigContext: apiClusterContext,
49+
writer: format.NewWriter(os.Stdout),
4650
}
4751
if err := client.loadConfig(apiClusterContext); err != nil {
4852
return nil, err
@@ -70,6 +74,14 @@ func New(ctx context.Context, apiClusterContext, project string, opts ...ClientO
7074
return client, nil
7175
}
7276

77+
// OutputWriter sets the writer for the client.
78+
func OutputWriter(w io.Writer) ClientOpt {
79+
return func(c *Client) error {
80+
c.writer = format.NewWriter(w)
81+
return nil
82+
}
83+
}
84+
7385
// LogClient sets up a log client connected to the provided address.
7486
func LogClient(ctx context.Context, address string, insecure bool) ClientOpt {
7587
return func(c *Client) error {

api/list.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"sync"
1212

1313
management "github.com/ninech/apis/management/v1alpha1"
14-
"github.com/ninech/nctl/internal/format"
1514
"k8s.io/apimachinery/pkg/api/meta"
1615
"k8s.io/apimachinery/pkg/conversion"
1716
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -150,7 +149,7 @@ func (c *Client) ListObjects(ctx context.Context, list runtimeclient.ObjectList,
150149
tempList := reflect.New(reflect.TypeOf(list).Elem()).Interface().(runtimeclient.ObjectList)
151150
tempList.GetObjectKind().SetGroupVersionKind(list.GetObjectKind().GroupVersionKind())
152151
if err := c.List(ctx, tempList, append(tempOpts, runtimeclient.InNamespace(proj.Name))...); err != nil {
153-
format.PrintWarningf("error when searching in project %s: %s\n", proj.Name, err)
152+
c.writer.Warningf("error when searching in project %s: %s\n", proj.Name, err)
154153
return
155154
}
156155
tempListItems := reflect.ValueOf(tempList).Elem().FieldByName("Items")

api/validation/apps.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Package validation provides functionality to validate git repositories
2+
// and their access configurations.
13
package validation
24

35
import (
@@ -15,6 +17,8 @@ import (
1517

1618
// RepositoryValidator validates a git repository
1719
type RepositoryValidator struct {
20+
format.Writer
21+
1822
GitInformationServiceURL string
1923
Token string
2024
Debug bool
@@ -27,7 +31,7 @@ func (v *RepositoryValidator) Validate(ctx context.Context, git *apps.GitTarget,
2731
return err
2832
}
2933
msg := " testing repository access 🔐"
30-
spinner, err := format.NewSpinner(msg, msg)
34+
spinner, err := v.Spinner(msg, msg)
3135
if err != nil {
3236
return err
3337
}

apply/apply.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
// Package apply provides the implementation for the apply command,
2+
// allowing users to apply resources from files.
13
package apply
24

3-
import "os"
5+
import (
6+
"os"
7+
8+
"github.com/ninech/nctl/internal/format"
9+
)
410

511
type Cmd struct {
12+
format.Writer
13+
614
Filename *os.File `short:"f" completion-predictor:"file"`
715
FromFile fromFile `cmd:"" default:"1" name:"-f <file>" help:"Apply any resource from a yaml or json file."`
816
}

apply/file.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ import (
1414
"sigs.k8s.io/controller-runtime/pkg/client"
1515
)
1616

17-
type fromFile struct {
18-
}
17+
type fromFile struct{}
1918

2019
func (cmd *Cmd) Run(ctx context.Context, client *api.Client, apply *Cmd) error {
21-
return File(ctx, client, apply.Filename, UpdateOnExists())
20+
return File(ctx, cmd.Writer, client, apply.Filename, UpdateOnExists())
2221
}
2322

2423
type Option func(*config)
@@ -40,7 +39,7 @@ func Delete() Option {
4039
}
4140
}
4241

43-
func File(ctx context.Context, client *api.Client, file *os.File, opts ...Option) error {
42+
func File(ctx context.Context, w format.Writer, client *api.Client, file *os.File, opts ...Option) error {
4443
if file == nil {
4544
return fmt.Errorf("missing flag -f, --filename=STRING")
4645
}
@@ -60,7 +59,7 @@ func File(ctx context.Context, client *api.Client, file *os.File, opts ...Option
6059
if err := client.Delete(ctx, obj); err != nil {
6160
return err
6261
}
63-
format.PrintSuccessf("🗑", "deleted %s", formatObj(obj))
62+
w.Successf("🗑", "deleted %s", formatObj(obj))
6463

6564
return nil
6665
}
@@ -72,7 +71,7 @@ func File(ctx context.Context, client *api.Client, file *os.File, opts ...Option
7271
return err
7372
}
7473

75-
format.PrintSuccessf("🏗", "created %s", formatObj(obj))
74+
w.Successf("🏗", "created %s", formatObj(obj))
7675
return nil
7776
}
7877

@@ -99,7 +98,6 @@ func update(ctx context.Context, client *api.Client, obj *unstructured.Unstructu
9998
return err
10099
}
101100

102-
format.PrintSuccessf("🏗", "applied %s", formatObj(obj))
103101
return nil
104102
}
105103

apply/file_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
runtimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
1010
iam "github.com/ninech/apis/iam/v1alpha1"
11+
"github.com/ninech/nctl/internal/format"
1112
"github.com/ninech/nctl/internal/test"
1213
"github.com/stretchr/testify/require"
1314
"k8s.io/apimachinery/pkg/api/errors"
@@ -59,6 +60,7 @@ func TestFile(t *testing.T) {
5960
ctx := context.Background()
6061
apiClient, err := test.SetupClient()
6162
require.NoError(t, err)
63+
w := format.NewWriter(t.Output())
6264

6365
tests := map[string]struct {
6466
name string
@@ -128,7 +130,7 @@ func TestFile(t *testing.T) {
128130
if tc.delete || tc.update {
129131
fileToCreate, err := os.Open(f.Name())
130132
require.NoError(t, err)
131-
err = File(ctx, apiClient, fileToCreate) // This will close fileToCreate
133+
err = File(ctx, w, apiClient, fileToCreate) // This will close fileToCreate
132134
require.NoError(t, err)
133135
}
134136

@@ -150,7 +152,7 @@ func TestFile(t *testing.T) {
150152
fileToApply, err := os.Open(f.Name())
151153
require.NoError(t, err)
152154

153-
if err := File(ctx, apiClient, fileToApply, opts...); err != nil {
155+
if err := File(ctx, w, apiClient, fileToApply, opts...); err != nil {
154156
if tc.expectedErr {
155157
return
156158
}

auth/cluster.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ import (
1212
"github.com/ninech/nctl/api"
1313
"github.com/ninech/nctl/api/config"
1414
"github.com/ninech/nctl/api/util"
15+
"github.com/ninech/nctl/internal/format"
16+
1517
"k8s.io/apimachinery/pkg/types"
1618
)
1719

1820
type ClusterCmd struct {
21+
format.Writer
1922
Name string `arg:"" help:"Name of the cluster to authenticate with. Also accepts 'name/project' format."`
2023
ExecPlugin bool `help:"Automatically run exec plugin after writing the kubeconfig."`
2124
}
@@ -91,7 +94,7 @@ func (a *ClusterCmd) Run(ctx context.Context, client *api.Client) error {
9194
}
9295
}
9396

94-
if err := login(cfg, client.KubeconfigPath, userInfo.User, "", switchCurrentContext()); err != nil {
97+
if err := login(a.Writer, cfg, client.KubeconfigPath, userInfo.User, "", switchCurrentContext()); err != nil {
9598
return fmt.Errorf("error logging in to cluster %s: %w", name, err)
9699
}
97100

auth/login.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const (
2626
)
2727

2828
type LoginCmd struct {
29+
format.Writer
2930
API API `embed:"" prefix:"api-"`
3031
Organization string `help:"Name of your organization to use when providing an API client ID/secret." env:"NCTL_ORGANIZATION"`
3132
IssuerURL string `help:"OIDC issuer URL of the API." default:"${issuer_url}" hidden:""`
@@ -69,7 +70,7 @@ func (l *LoginCmd) Run(ctx context.Context) error {
6970
if err != nil {
7071
return err
7172
}
72-
return login(cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(l.Organization))
73+
return login(l.Writer, cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(l.Organization))
7374
}
7475

7576
if l.API.ClientID != "" {
@@ -92,7 +93,7 @@ func (l *LoginCmd) Run(ctx context.Context) error {
9293
if userInfo.Project != "" {
9394
proj = userInfo.Project
9495
}
95-
return login(cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(proj))
96+
return login(l.Writer, cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(proj))
9697
}
9798

9899
if !l.ForceInteractiveEnvOverride && !format.IsInteractiveEnvironment(os.Stdout) {
@@ -117,17 +118,33 @@ func (l *LoginCmd) Run(ctx context.Context) error {
117118

118119
org := userInfo.Orgs[0]
119120
if len(userInfo.Orgs) > 1 {
120-
fmt.Printf("Multiple organizations found for the account %q.\n", userInfo.User)
121-
fmt.Printf("Defaulting to %q\n", org)
122-
printAvailableOrgsString(org, userInfo.Orgs)
121+
l.Printf("Multiple organizations found for the account %q.\n", userInfo.User)
122+
l.Printf("Defaulting to %q\n", org)
123+
l.printAvailableOrgsString(org, userInfo.Orgs)
123124
}
124125

125126
cfg, err := newAPIConfig(apiURL, issuerURL, command, l.ClientID, withOrganization(org))
126127
if err != nil {
127128
return err
128129
}
129130

130-
return login(cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(org))
131+
return login(l.Writer, cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(org))
132+
}
133+
134+
func (l *LoginCmd) printAvailableOrgsString(currentorg string, orgs []string) {
135+
l.Println("\nAvailable Organizations:")
136+
137+
for _, org := range orgs {
138+
activeMarker := ""
139+
if currentorg == org {
140+
activeMarker = "*"
141+
}
142+
143+
l.Printf("%s\t%s\n", activeMarker, org)
144+
}
145+
146+
l.Printf("\nTo switch the organization use the following command:\n")
147+
l.Printf("$ nctl auth set-org <org-name>\n")
131148
}
132149

133150
func (l *LoginCmd) tokenGetter() api.TokenGetter {
@@ -254,7 +271,7 @@ func switchCurrentContext() loginOption {
254271
}
255272
}
256273

257-
func login(newConfig *clientcmdapi.Config, kubeconfigPath, userName string, toOrg string, opts ...loginOption) error {
274+
func login(w format.Writer, newConfig *clientcmdapi.Config, kubeconfigPath, userName string, toOrg string, opts ...loginOption) error {
258275
loginConfig := &loginConfig{}
259276
for _, opt := range opts {
260277
opt(loginConfig)
@@ -284,15 +301,15 @@ func login(newConfig *clientcmdapi.Config, kubeconfigPath, userName string, toOr
284301
}
285302

286303
if toOrg != "" {
287-
format.PrintSuccessf("🏢", "switched to the organization %q", toOrg)
304+
w.Successf("🏢", "switched to the organization %q\n", toOrg)
288305
}
289-
format.PrintSuccessf("📋", "added %s to kubeconfig", newConfig.CurrentContext)
306+
w.Successf("📋", "added %s to kubeconfig\n", newConfig.CurrentContext)
290307

291308
loginMessage := fmt.Sprintf("logged into cluster %s", newConfig.CurrentContext)
292309
if strings.TrimSpace(userName) != "" {
293310
loginMessage = fmt.Sprintf("logged into cluster %s as %s", newConfig.CurrentContext, userName)
294311
}
295-
format.PrintSuccess("🚀", loginMessage)
312+
w.Success("🚀", loginMessage+"\n")
296313

297314
return nil
298315
}

auth/logout.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
)
2323

2424
type LogoutCmd struct {
25+
format.Writer
2526
APIURL string `help:"URL of the Nine API." default:"https://nineapis.ch" env:"NCTL_API_URL" name:"api-url"`
2627
IssuerURL string `help:"OIDC issuer URL of the API." default:"https://auth.nine.ch/auth/realms/pub"`
2728
ClientID string `help:"OIDC client ID of the API." default:"nineapis.ch-f178254"`
@@ -43,12 +44,15 @@ func (l *LogoutCmd) Run(ctx context.Context) error {
4344
filePath := path.Join(homedir.HomeDir(), api.DefaultTokenCachePath, filename)
4445

4546
if _, err = os.Stat(filePath); err != nil {
46-
format.PrintFailuref("🤔", "seems like you are already logged out from %s", l.APIURL)
47+
l.Failuref("🤔", "seems like you are already logged out from %s\n", l.APIURL)
4748
return nil
4849
}
4950

5051
r := repository.Repository{}
51-
cache, err := r.FindByKey(tokencache.Config{Directory: path.Join(homedir.HomeDir(), api.DefaultTokenCachePath)}, key)
52+
cache, err := r.FindByKey(
53+
tokencache.Config{Directory: path.Join(homedir.HomeDir(), api.DefaultTokenCachePath)},
54+
key,
55+
)
5256
if err != nil {
5357
return fmt.Errorf("error finding cache file: %w", err)
5458
}
@@ -57,9 +61,17 @@ func (l *LogoutCmd) Run(ctx context.Context) error {
5761
form.Add("client_id", l.ClientID)
5862
form.Add("refresh_token", cache.RefreshToken)
5963

60-
logoutEndpoint := strings.Join([]string{l.IssuerURL, "protocol", "openid-connect", "logout"}, "/")
61-
62-
req, err := http.NewRequestWithContext(ctx, http.MethodPost, logoutEndpoint, strings.NewReader(form.Encode()))
64+
logoutEndpoint := strings.Join(
65+
[]string{l.IssuerURL, "protocol", "openid-connect", "logout"},
66+
"/",
67+
)
68+
69+
req, err := http.NewRequestWithContext(
70+
ctx,
71+
http.MethodPost,
72+
logoutEndpoint,
73+
strings.NewReader(form.Encode()),
74+
)
6375
if err != nil {
6476
return fmt.Errorf("error creating request: %w", err)
6577
}
@@ -87,7 +99,7 @@ func (l *LogoutCmd) Run(ctx context.Context) error {
8799
return fmt.Errorf("error removing the local cache: %w", err)
88100
}
89101

90-
format.PrintSuccessf("👋", "logged out from %s", l.APIURL)
102+
l.Successf("👋", "logged out from %s\n", l.APIURL)
91103

92104
return nil
93105
}

0 commit comments

Comments
 (0)