-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bootstrap Get command for tink-cli #406
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
7bd1743
Bootstrap Get command for tink-cli
9f7d6fa
Skip tests that are currently failing because not well written
af040f9
Refactor hardware subcommands to remove globals and init
3d1da41
Use MOQ to generate grpc mock for hardware proto
76793ee
Removed init function for cli template pkg
0cbb1f9
Remove init from cli/workflow
4c1ed62
Fixed test name for resource commands
de79a38
Change MetaClient with FullClient
5b0bafd
Replace get output with format
a1d0390
Rename a couple of variables and flags
923c726
Create make target to generate protos mock
40dfbab
Implement get by IDs for hardware, template and workflow
dee255e
Move client setup as part of the command itself
68a7185
Remove FullClient from command New function
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
export TINKERBELL_GRPC_AUTHORITY=127.0.0.1:42113 | ||
export TINKERBELL_CERT_URL=http://127.0.0.1:42114/cert | ||
|
||
which nix &>/dev/null && use nix && unset GOPATH |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,6 +8,7 @@ import ( | |||||
"os" | ||||||
|
||||||
"github.com/pkg/errors" | ||||||
"github.com/spf13/pflag" | ||||||
"github.com/tinkerbell/tink/protos/events" | ||||||
"github.com/tinkerbell/tink/protos/hardware" | ||||||
"github.com/tinkerbell/tink/protos/template" | ||||||
|
@@ -24,6 +25,83 @@ var ( | |||||
EventsClient events.EventsServiceClient | ||||||
) | ||||||
|
||||||
// FullClient aggregates all the grpc clients available from Tinkerbell Server | ||||||
type FullClient struct { | ||||||
TemplateClient template.TemplateServiceClient | ||||||
WorkflowClient workflow.WorkflowServiceClient | ||||||
HardwareClient hardware.HardwareServiceClient | ||||||
EventsClient events.EventsServiceClient | ||||||
} | ||||||
|
||||||
// NewFullClientFromGlobal is a dirty hack that returns a FullClient using the | ||||||
// global variables exposed by the client package. Globals should be avoided | ||||||
// and we will deprecated them at some point replacing this function with | ||||||
// NewFullClient. If you are strating a new project please use the last one | ||||||
func NewFullClientFromGlobal() (*FullClient, error) { | ||||||
// This is required because we use init() too often, even more in the | ||||||
// CLI and based on where you are sometime the clients are not initialised | ||||||
if TemplateClient == nil { | ||||||
err := Setup() | ||||||
if err != nil { | ||||||
panic(err) | ||||||
} | ||||||
} | ||||||
return &FullClient{ | ||||||
TemplateClient: TemplateClient, | ||||||
WorkflowClient: WorkflowClient, | ||||||
HardwareClient: HardwareClient, | ||||||
EventsClient: EventsClient, | ||||||
}, nil | ||||||
} | ||||||
|
||||||
// NewFullClient returns a FullClient. A structure that contains all the | ||||||
// clients made available from tink-server. This is the function you should use | ||||||
// instead of NewFullClientFromGlobal that will be deprecated soon | ||||||
func NewFullClient(conn grpc.ClientConnInterface) *FullClient { | ||||||
return &FullClient{ | ||||||
TemplateClient: template.NewTemplateServiceClient(conn), | ||||||
WorkflowClient: workflow.NewWorkflowServiceClient(conn), | ||||||
HardwareClient: hardware.NewHardwareServiceClient(conn), | ||||||
EventsClient: events.NewEventsServiceClient(conn), | ||||||
} | ||||||
} | ||||||
|
||||||
type ConnOptions struct { | ||||||
CertURL string | ||||||
GRPCAuthority string | ||||||
} | ||||||
|
||||||
func (o *ConnOptions) SetFlags(flagSet *pflag.FlagSet) { | ||||||
flagSet.StringVar(&o.CertURL, "tinkerbell-cert-url", "http://127.0.0.1:42114/cert", "The URL where the certificate is located") | ||||||
flagSet.StringVar(&o.GRPCAuthority, "tinkerbell-grpc-authority", "127.0.0.1:42113", "Link to tink-server grcp api") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Sigh I do this all the time |
||||||
} | ||||||
|
||||||
func NewClientConn(opt *ConnOptions) (*grpc.ClientConn, error) { | ||||||
resp, err := http.Get(opt.CertURL) | ||||||
if err != nil { | ||||||
return nil, errors.Wrap(err, "fetch cert") | ||||||
} | ||||||
defer resp.Body.Close() | ||||||
|
||||||
certs, err := ioutil.ReadAll(resp.Body) | ||||||
if err != nil { | ||||||
return nil, errors.Wrap(err, "read cert") | ||||||
} | ||||||
|
||||||
cp := x509.NewCertPool() | ||||||
ok := cp.AppendCertsFromPEM(certs) | ||||||
if !ok { | ||||||
return nil, errors.Wrap(err, "parse cert") | ||||||
} | ||||||
|
||||||
creds := credentials.NewClientTLSFromCert(cp, "") | ||||||
conn, err := grpc.Dial(opt.GRPCAuthority, grpc.WithTransportCredentials(creds)) | ||||||
if err != nil { | ||||||
return nil, errors.Wrap(err, "connect to tinkerbell server") | ||||||
} | ||||||
return conn, nil | ||||||
} | ||||||
|
||||||
// GetConnection returns a gRPC client connection | ||||||
func GetConnection() (*grpc.ClientConn, error) { | ||||||
certURL := os.Getenv("TINKERBELL_CERT_URL") | ||||||
gianarb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Get is a reusable implementation of the Get command for the tink cli. The | ||
// Get command lists and filters resources. It supports different kind of | ||
// visualisation and it is designed to be extendible and usable across | ||
// resources. | ||
package get |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package get | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/pkg/errors" | ||
|
||
"github.com/jedib0t/go-pretty/table" | ||
"github.com/spf13/cobra" | ||
"github.com/tinkerbell/tink/client" | ||
"google.golang.org/grpc" | ||
) | ||
|
||
type Options struct { | ||
// Headers is the list of headers you want to print as part of the list | ||
Headers []string | ||
// RetrieveData reaches out to Tinkerbell and it gets the required data | ||
RetrieveData func(context.Context, *client.FullClient) ([]interface{}, error) | ||
// RetrieveByID is used when a get command has a list of arguments | ||
RetrieveByID func(context.Context, *client.FullClient, string) (interface{}, error) | ||
// PopulateTable populates a table with the data retrieved with the RetrieveData function. | ||
PopulateTable func([]interface{}, table.Writer) error | ||
|
||
clientConnOpt *client.ConnOptions | ||
fullClient *client.FullClient | ||
|
||
// Format specifies the format you want the list of resources printed | ||
// out. By default it is table but it can be JSON ar CSV. | ||
Format string | ||
// NoHeaders does not print the header line | ||
NoHeaders bool | ||
} | ||
|
||
func (o *Options) SetClientConnOpt(co *client.ConnOptions) { | ||
o.clientConnOpt = co | ||
} | ||
|
||
func (o *Options) SetFullClient(cl *client.FullClient) { | ||
o.fullClient = cl | ||
} | ||
|
||
const shortDescr = `display one or many resources` | ||
|
||
const longDescr = `Prints a table containing the most important information about a specific | ||
resource. You can specify the kind of output you want to receive. It can be | ||
table, csv or json. | ||
` | ||
|
||
const exampleDescr = `# List all hardware in table output format. | ||
tink hardware get | ||
|
||
# List all workflow in csv output format. | ||
tink template get --format csv | ||
|
||
# List a single template in json output format. | ||
tink workflow get --format json [id] | ||
` | ||
|
||
func NewGetCommand(opt Options) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "get", | ||
Short: shortDescr, | ||
Long: longDescr, | ||
Example: exampleDescr, | ||
DisableFlagsInUseLine: true, | ||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { | ||
if opt.fullClient != nil { | ||
return nil | ||
} | ||
if opt.clientConnOpt == nil { | ||
opt.SetClientConnOpt(&client.ConnOptions{}) | ||
} | ||
opt.clientConnOpt.SetFlags(cmd.PersistentFlags()) | ||
return nil | ||
}, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
if opt.fullClient == nil { | ||
var err error | ||
var conn *grpc.ClientConn | ||
conn, err = client.NewClientConn(opt.clientConnOpt) | ||
if err != nil { | ||
println("Flag based client configuration failed with err: %s. Trying with env var legacy method...", err) | ||
// Fallback to legacy Setup via env var | ||
conn, err = client.GetConnection() | ||
if err != nil { | ||
return errors.Wrap(err, "failed to setup connection to tink-server") | ||
} | ||
} | ||
opt.SetFullClient(client.NewFullClient(conn)) | ||
} | ||
return nil | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
var err error | ||
var data []interface{} | ||
|
||
t := table.NewWriter() | ||
t.SetOutputMirror(cmd.OutOrStdout()) | ||
|
||
if len(args) != 0 { | ||
if opt.RetrieveByID == nil { | ||
return errors.New("Get by ID is not implemented for this resource yet. Please have a look at the issue in GitHub or open a new one.") | ||
} | ||
for _, requestedID := range args { | ||
s, err := opt.RetrieveByID(cmd.Context(), opt.fullClient, requestedID) | ||
if err != nil { | ||
continue | ||
} | ||
data = append(data, s) | ||
} | ||
} else { | ||
data, err = opt.RetrieveData(cmd.Context(), opt.fullClient) | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !opt.NoHeaders { | ||
header := table.Row{} | ||
for _, h := range opt.Headers { | ||
header = append(header, h) | ||
} | ||
t.AppendHeader(header) | ||
} | ||
|
||
// TODO(gianarb): Technically this is not needed for | ||
// all the output formats but for now that's fine | ||
if err := opt.PopulateTable(data, t); err != nil { | ||
return err | ||
} | ||
|
||
switch opt.Format { | ||
case "json": | ||
// TODO(gianarb): the table library we use do | ||
// not support JSON right now. I am not even | ||
// sure I like tables! So complicated... | ||
gianarb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
b, err := json.Marshal(struct { | ||
Data interface{} `json:"data"` | ||
}{Data: data}) | ||
gianarb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return err | ||
} | ||
fmt.Fprint(cmd.OutOrStdout(), string(b)) | ||
gianarb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
case "csv": | ||
t.RenderCSV() | ||
default: | ||
t.Render() | ||
} | ||
return nil | ||
}, | ||
} | ||
cmd.PersistentFlags().StringVarP(&opt.Format, "format", "", "table", "The format you expect the list to be printed out. Currently supported format are table, JSON and CSV") | ||
cmd.PersistentFlags().BoolVar(&opt.NoHeaders, "no-headers", false, "Table contains an header with the columns' name. You can disable it from being printed out") | ||
return cmd | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be great if these were proper deps for the necessary binaries so that they would always be up to date. Doesn't have to be now, I really want to see this PR done with :D. I can open up an issue when this PR is merged though