Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"fmt"
"io"
"os"
"os/user"
"path/filepath"
Expand Down Expand Up @@ -31,6 +32,8 @@ type Client struct {
Project string
Log *log.Client
KubeconfigContext string

writer format.Writer
}

type ClientOpt func(c *Client) error
Expand All @@ -43,6 +46,7 @@ func New(ctx context.Context, apiClusterContext, project string, opts ...ClientO
client := &Client{
Project: project,
KubeconfigContext: apiClusterContext,
writer: format.NewWriter(os.Stdout),
}
if err := client.loadConfig(apiClusterContext); err != nil {
return nil, err
Expand Down Expand Up @@ -70,6 +74,14 @@ func New(ctx context.Context, apiClusterContext, project string, opts ...ClientO
return client, nil
}

// OutputWriter sets the writer for the client.
func OutputWriter(w io.Writer) ClientOpt {
return func(c *Client) error {
c.writer = format.NewWriter(w)
return nil
}
}

// LogClient sets up a log client connected to the provided address.
func LogClient(ctx context.Context, address string, insecure bool) ClientOpt {
return func(c *Client) error {
Expand Down
3 changes: 1 addition & 2 deletions api/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"sync"

management "github.com/ninech/apis/management/v1alpha1"
"github.com/ninech/nctl/internal/format"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/conversion"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -150,7 +149,7 @@ func (c *Client) ListObjects(ctx context.Context, list runtimeclient.ObjectList,
tempList := reflect.New(reflect.TypeOf(list).Elem()).Interface().(runtimeclient.ObjectList)
tempList.GetObjectKind().SetGroupVersionKind(list.GetObjectKind().GroupVersionKind())
if err := c.List(ctx, tempList, append(tempOpts, runtimeclient.InNamespace(proj.Name))...); err != nil {
format.PrintWarningf("error when searching in project %s: %s\n", proj.Name, err)
c.writer.Warningf("error when searching in project %s: %s\n", proj.Name, err)
return
}
tempListItems := reflect.ValueOf(tempList).Elem().FieldByName("Items")
Expand Down
File renamed without changes.
6 changes: 5 additions & 1 deletion api/validation/apps.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Package validation provides functionality to validate git repositories
// and their access configurations.
package validation

import (
Expand All @@ -15,6 +17,8 @@ import (

// RepositoryValidator validates a git repository
type RepositoryValidator struct {
format.Writer

GitInformationServiceURL string
Token string
Debug bool
Expand All @@ -27,7 +31,7 @@ func (v *RepositoryValidator) Validate(ctx context.Context, git *apps.GitTarget,
return err
}
msg := " testing repository access 🔐"
spinner, err := format.NewSpinner(msg, msg)
spinner, err := v.Spinner(msg, msg)
if err != nil {
return err
}
Expand Down
10 changes: 9 additions & 1 deletion apply/apply.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
// Package apply provides the implementation for the apply command,
// allowing users to apply resources from files.
package apply

import "os"
import (
"os"

"github.com/ninech/nctl/internal/format"
)

type Cmd struct {
format.Writer

Filename *os.File `short:"f" completion-predictor:"file"`
FromFile fromFile `cmd:"" default:"1" name:"-f <file>" help:"Apply any resource from a yaml or json file."`
}
12 changes: 5 additions & 7 deletions apply/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

type fromFile struct {
}
type fromFile struct{}

func (cmd *Cmd) Run(ctx context.Context, client *api.Client, apply *Cmd) error {
return File(ctx, client, apply.Filename, UpdateOnExists())
return File(ctx, cmd.Writer, client, apply.Filename, UpdateOnExists())
}

type Option func(*config)
Expand All @@ -40,7 +39,7 @@ func Delete() Option {
}
}

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

return nil
}
Expand All @@ -72,7 +71,7 @@ func File(ctx context.Context, client *api.Client, file *os.File, opts ...Option
return err
}

format.PrintSuccessf("🏗", "created %s", formatObj(obj))
w.Successf("🏗", "created %s", formatObj(obj))
return nil
}

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

format.PrintSuccessf("🏗", "applied %s", formatObj(obj))
return nil
}

Expand Down
6 changes: 4 additions & 2 deletions apply/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

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

tests := map[string]struct {
name string
Expand Down Expand Up @@ -128,7 +130,7 @@ func TestFile(t *testing.T) {
if tc.delete || tc.update {
fileToCreate, err := os.Open(f.Name())
require.NoError(t, err)
err = File(ctx, apiClient, fileToCreate) // This will close fileToCreate
err = File(ctx, w, apiClient, fileToCreate) // This will close fileToCreate
require.NoError(t, err)
}

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

if err := File(ctx, apiClient, fileToApply, opts...); err != nil {
if err := File(ctx, w, apiClient, fileToApply, opts...); err != nil {
if tc.expectedErr {
return
}
Expand Down
5 changes: 4 additions & 1 deletion auth/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import (
"github.com/ninech/nctl/api"
"github.com/ninech/nctl/api/config"
"github.com/ninech/nctl/api/util"
"github.com/ninech/nctl/internal/format"

"k8s.io/apimachinery/pkg/types"
)

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

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

Expand Down
37 changes: 27 additions & 10 deletions auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
)

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

if l.API.ClientID != "" {
Expand All @@ -92,7 +93,7 @@ func (l *LoginCmd) Run(ctx context.Context) error {
if userInfo.Project != "" {
proj = userInfo.Project
}
return login(cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(proj))
return login(l.Writer, cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(proj))
}

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

org := userInfo.Orgs[0]
if len(userInfo.Orgs) > 1 {
fmt.Printf("Multiple organizations found for the account %q.\n", userInfo.User)
fmt.Printf("Defaulting to %q\n", org)
printAvailableOrgsString(org, userInfo.Orgs)
l.Printf("Multiple organizations found for the account %q.\n", userInfo.User)
l.Printf("Defaulting to %q\n", org)
l.printAvailableOrgsString(org, userInfo.Orgs)
}

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

return login(cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(org))
return login(l.Writer, cfg, loadingRules.GetDefaultFilename(), userInfo.User, "", project(org))
}

func (l *LoginCmd) printAvailableOrgsString(currentorg string, orgs []string) {
l.Println("\nAvailable Organizations:")

for _, org := range orgs {
activeMarker := ""
if currentorg == org {
activeMarker = "*"
}

l.Printf("%s\t%s\n", activeMarker, org)
}

l.Printf("\nTo switch the organization use the following command:\n")
l.Printf("$ nctl auth set-org <org-name>\n")
}

func (l *LoginCmd) tokenGetter() api.TokenGetter {
Expand Down Expand Up @@ -254,7 +271,7 @@ func switchCurrentContext() loginOption {
}
}

func login(newConfig *clientcmdapi.Config, kubeconfigPath, userName string, toOrg string, opts ...loginOption) error {
func login(w format.Writer, newConfig *clientcmdapi.Config, kubeconfigPath, userName string, toOrg string, opts ...loginOption) error {
loginConfig := &loginConfig{}
for _, opt := range opts {
opt(loginConfig)
Expand Down Expand Up @@ -284,15 +301,15 @@ func login(newConfig *clientcmdapi.Config, kubeconfigPath, userName string, toOr
}

if toOrg != "" {
format.PrintSuccessf("🏢", "switched to the organization %q", toOrg)
w.Successf("🏢", "switched to the organization %q\n", toOrg)
}
format.PrintSuccessf("📋", "added %s to kubeconfig", newConfig.CurrentContext)
w.Successf("📋", "added %s to kubeconfig\n", newConfig.CurrentContext)

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

return nil
}
Expand Down
24 changes: 18 additions & 6 deletions auth/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
)

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

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

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

logoutEndpoint := strings.Join([]string{l.IssuerURL, "protocol", "openid-connect", "logout"}, "/")

req, err := http.NewRequestWithContext(ctx, http.MethodPost, logoutEndpoint, strings.NewReader(form.Encode()))
logoutEndpoint := strings.Join(
[]string{l.IssuerURL, "protocol", "openid-connect", "logout"},
"/",
)

req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
logoutEndpoint,
strings.NewReader(form.Encode()),
)
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
Expand Down Expand Up @@ -87,7 +99,7 @@ func (l *LogoutCmd) Run(ctx context.Context) error {
return fmt.Errorf("error removing the local cache: %w", err)
}

format.PrintSuccessf("👋", "logged out from %s", l.APIURL)
l.Successf("👋", "logged out from %s\n", l.APIURL)

return nil
}
Expand Down
10 changes: 6 additions & 4 deletions auth/print_access_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package auth

import (
"context"
"fmt"

"github.com/ninech/nctl/api"
"github.com/ninech/nctl/internal/format"
)

type PrintAccessTokenCmd struct{}
type PrintAccessTokenCmd struct {
format.Writer
}

func (o *PrintAccessTokenCmd) Run(ctx context.Context, client *api.Client) error {
fmt.Println(client.Token(ctx))
func (cmd *PrintAccessTokenCmd) Run(ctx context.Context, client *api.Client) error {
cmd.Println(client.Token(ctx))
return nil
}
Loading