-
Notifications
You must be signed in to change notification settings - Fork 42
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
Add datasources CLI stubs #5020
Merged
Merged
Changes from 4 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
ebd23cf
add: datasources cli stubs
teodor-yanev ee2e2c3
update: refactoring & enhancements
teodor-yanev a935f5a
add: licenses to the new files
teodor-yanev c092b8a
update: lints
teodor-yanev b386b9e
add: operations by name
teodor-yanev e533249
remove: redundant ctx from requests
teodor-yanev afff20c
Merge branch 'main' into add-datasources-cli-stubs
teodor-yanev 45cbe2f
add: include cmd main file
teodor-yanev 41592cd
Merge branch 'main' into add-datasources-cli-stubs
teodor-yanev 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 |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// SPDX-FileCopyrightText: Copyright 2023 The Minder Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Package datasource provides functionalities to manage and apply data sources. | ||
package datasource | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
|
||
"github.com/mindersec/minder/internal/util" | ||
"github.com/mindersec/minder/internal/util/cli" | ||
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" | ||
) | ||
|
||
// applyCmd represents the datasource apply command | ||
var applyCmd = &cobra.Command{ | ||
Use: "apply", | ||
Short: "Apply a data source", | ||
Long: `The datasource apply subcommand lets you create or update data sources for a project within Minder.`, | ||
RunE: cli.GRPCClientWrapRunE(applyCommand), | ||
} | ||
|
||
func init() { | ||
DataSourceCmd.AddCommand(applyCmd) | ||
// Flags | ||
applyCmd.Flags().StringArrayP("file", "f", []string{}, | ||
"Path to the YAML defining the data source (or - for stdin). Can be specified multiple times. Can be a directory.") | ||
// Required | ||
if err := applyCmd.MarkFlagRequired("file"); err != nil { | ||
applyCmd.Printf("Error marking flag required: %s", err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
// applyCommand is the datasource apply subcommand | ||
func applyCommand(_ context.Context, cmd *cobra.Command, _ []string, conn *grpc.ClientConn) error { | ||
client := minderv1.NewDataSourceServiceClient(conn) | ||
|
||
project := viper.GetString("project") | ||
|
||
fileFlag, err := cmd.Flags().GetStringArray("file") | ||
if err != nil { | ||
return cli.MessageAndError("Error parsing file flag", err) | ||
} | ||
|
||
if err = validateFilesArg(fileFlag); err != nil { | ||
return cli.MessageAndError("Error validating file flag", err) | ||
} | ||
|
||
files, err := util.ExpandFileArgs(fileFlag...) | ||
if err != nil { | ||
return cli.MessageAndError("Error expanding file args", err) | ||
} | ||
|
||
// No longer print usage on returned error, since we've parsed our inputs | ||
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413 | ||
cmd.SilenceUsage = true | ||
|
||
table := initializeTableForList() | ||
|
||
applyFunc := func(ctx context.Context, fileName string, ds *minderv1.DataSource) (*minderv1.DataSource, error) { | ||
createResp, err := client.CreateDataSource(ctx, &minderv1.CreateDataSourceRequest{ | ||
DataSource: ds, | ||
}) | ||
|
||
if err == nil { | ||
return createResp.DataSource, nil | ||
} | ||
|
||
st, ok := status.FromError(err) | ||
if !ok { | ||
// We can't parse the error, so just return it | ||
return nil, fmt.Errorf("error creating data source from %s: %w", fileName, err) | ||
} | ||
|
||
if st.Code() != codes.AlreadyExists { | ||
return nil, fmt.Errorf("error creating data source from %s: %w", fileName, err) | ||
} | ||
|
||
updateResp, err := client.UpdateDataSource(ctx, &minderv1.UpdateDataSourceRequest{ | ||
DataSource: ds, | ||
}) | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("error updating data source from %s: %w", fileName, err) | ||
} | ||
|
||
return updateResp.DataSource, nil | ||
} | ||
|
||
for _, f := range files { | ||
if f.Path != "-" && shouldSkipFile(f.Path) { | ||
continue | ||
} | ||
// cmd.Context() is the root context. We need to create a new context for each file | ||
// so we can avoid the timeout. | ||
if err = executeOnOneDataSource(cmd.Context(), table, f.Path, os.Stdin, project, applyFunc); err != nil { | ||
if f.Expanded && minderv1.YouMayHaveTheWrongResource(err) { | ||
cmd.PrintErrf("Skipping file %s: not a data source\n", f.Path) | ||
// We'll skip the file if it's not a data source | ||
continue | ||
} | ||
return cli.MessageAndError(fmt.Sprintf("error applying data source from %s", f.Path), err) | ||
} | ||
} | ||
// Render the table | ||
table.Render() | ||
return nil | ||
} |
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,148 @@ | ||
// SPDX-FileCopyrightText: Copyright 2023 The Minder Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package datasource | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/spf13/viper" | ||
|
||
"github.com/mindersec/minder/internal/util" | ||
"github.com/mindersec/minder/internal/util/cli" | ||
"github.com/mindersec/minder/internal/util/cli/table" | ||
"github.com/mindersec/minder/internal/util/cli/table/layouts" | ||
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" | ||
) | ||
|
||
// executeOnOneDataSource executes a function on a single data source | ||
func executeOnOneDataSource( | ||
ctx context.Context, | ||
t table.Table, | ||
f string, | ||
dashOpen io.Reader, | ||
proj string, | ||
exec func(context.Context, string, *minderv1.DataSource) (*minderv1.DataSource, error), | ||
) error { | ||
ctx, cancel := cli.GetAppContext(ctx, viper.GetViper()) | ||
defer cancel() | ||
|
||
reader, closer, err := util.OpenFileArg(f, dashOpen) | ||
if err != nil { | ||
return fmt.Errorf("error opening file arg: %w", err) | ||
} | ||
defer closer() | ||
|
||
ds := &minderv1.DataSource{} | ||
if err := minderv1.ParseResource(reader, ds); err != nil { | ||
return fmt.Errorf("error parsing data source: %w", err) | ||
} | ||
|
||
// Override the YAML specified project with the command line argument | ||
if proj != "" { | ||
if ds.Context == nil { | ||
ds.Context = &minderv1.ContextV2{} | ||
} | ||
|
||
ds.Context.ProjectId = proj | ||
} | ||
|
||
// create or update the data source | ||
createdDS, err := exec(ctx, f, ds) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// add the data source to the table rows | ||
name := appendDataSourcePropertiesToName(createdDS) | ||
t.AddRow( | ||
createdDS.Context.ProjectId, | ||
createdDS.Id, | ||
name, | ||
cli.ConcatenateAndWrap(createdDS.Name, 20), | ||
) | ||
|
||
return nil | ||
} | ||
|
||
// validateFilesArg validates the file arguments | ||
func validateFilesArg(files []string) error { | ||
if files == nil { | ||
return fmt.Errorf("error: file must be set") | ||
} | ||
|
||
if contains(files, "") { | ||
return fmt.Errorf("error: file must be set") | ||
} | ||
|
||
if contains(files, "-") && len(files) > 1 { | ||
return fmt.Errorf("error: cannot use stdin with other files") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// contains checks if a slice contains a specific string | ||
func contains(slice []string, item string) bool { | ||
for _, s := range slice { | ||
if s == item { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// shouldSkipFile determines if a file should be skipped based on its extension | ||
func shouldSkipFile(f string) bool { | ||
// if the file is not json or yaml, skip it | ||
// Get file extension | ||
ext := filepath.Ext(f) | ||
switch ext { | ||
case ".yaml", ".yml", ".json": | ||
return false | ||
default: | ||
fmt.Fprintf(os.Stderr, "Skipping file %s: not a yaml or json file\n", f) | ||
return true | ||
} | ||
} | ||
|
||
// appendDataSourcePropertiesToName appends the data source properties to the name. The format is: | ||
// <name> (<properties>) | ||
// where <properties> is a comma separated list of properties. | ||
func appendDataSourcePropertiesToName(ds *minderv1.DataSource) string { | ||
name := ds.Name | ||
properties := []string{} | ||
// add the type property if it is present | ||
dType := getDataSourceType(ds) | ||
if dType != "" { | ||
properties = append(properties, fmt.Sprintf("type: %s", dType)) | ||
} | ||
|
||
// add other properties as needed | ||
|
||
// return the name with the properties if any | ||
if len(properties) != 0 { | ||
return fmt.Sprintf("%s (%s)", name, strings.Join(properties, ", ")) | ||
} | ||
|
||
// return only name otherwise | ||
return name | ||
} | ||
|
||
// getDataSourceType returns the type of data source | ||
func getDataSourceType(ds *minderv1.DataSource) string { | ||
if ds.GetRest() != nil { | ||
return "REST" | ||
} | ||
return "Unknown" | ||
} | ||
|
||
// initializeTableForList initializes the table for listing data sources | ||
func initializeTableForList() table.Table { | ||
return table.New(table.Simple, layouts.DataSourceList, nil) | ||
} |
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,98 @@ | ||
// SPDX-FileCopyrightText: Copyright 2023 The Minder Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package datasource | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"google.golang.org/grpc" | ||
|
||
"github.com/mindersec/minder/internal/util" | ||
"github.com/mindersec/minder/internal/util/cli" | ||
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" | ||
) | ||
|
||
// createCmd represents the datasource create command | ||
var createCmd = &cobra.Command{ | ||
Use: "create", | ||
Short: "Create a data source", | ||
Long: `The datasource create subcommand lets you create new data sources for a project within Minder.`, | ||
RunE: cli.GRPCClientWrapRunE(createCommand), | ||
} | ||
|
||
func init() { | ||
DataSourceCmd.AddCommand(createCmd) | ||
// Flags | ||
createCmd.Flags().StringArrayP("file", "f", []string{}, | ||
"Path to the YAML defining the data source (or - for stdin). Can be specified multiple times. Can be a directory.") | ||
// Required | ||
if err := createCmd.MarkFlagRequired("file"); err != nil { | ||
createCmd.Printf("Error marking flag required: %s", err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
// createCommand is the datasource create subcommand | ||
func createCommand(_ context.Context, cmd *cobra.Command, _ []string, conn *grpc.ClientConn) error { | ||
client := minderv1.NewDataSourceServiceClient(conn) | ||
|
||
project := viper.GetString("project") | ||
|
||
fileFlag, err := cmd.Flags().GetStringArray("file") | ||
if err != nil { | ||
return cli.MessageAndError("Error parsing file flag", err) | ||
} | ||
|
||
if err = validateFilesArg(fileFlag); err != nil { | ||
return cli.MessageAndError("Error validating file flag", err) | ||
} | ||
|
||
files, err := util.ExpandFileArgs(fileFlag...) | ||
if err != nil { | ||
return cli.MessageAndError("Error expanding file args", err) | ||
} | ||
|
||
// No longer print usage on returned error, since we've parsed our inputs | ||
// See https://github.com/spf13/cobra/issues/340#issuecomment-374617413 | ||
cmd.SilenceUsage = true | ||
|
||
table := initializeTableForList() | ||
|
||
createFunc := func(ctx context.Context, _ string, ds *minderv1.DataSource) (*minderv1.DataSource, error) { | ||
resp, err := client.CreateDataSource(ctx, &minderv1.CreateDataSourceRequest{ | ||
DataSource: ds, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return resp.DataSource, nil | ||
} | ||
|
||
for _, f := range files { | ||
if f.Path != "-" && shouldSkipFile(f.Path) { | ||
continue | ||
} | ||
// cmd.Context() is the root context. We need to create a new context for each file | ||
// so we can avoid the timeout. | ||
if err = executeOnOneDataSource(cmd.Context(), table, f.Path, os.Stdin, project, createFunc); err != nil { | ||
// We swallow errors if you're loading a directory to avoid failing | ||
// on test files. | ||
if f.Expanded && minderv1.YouMayHaveTheWrongResource(err) { | ||
cmd.PrintErrf("Skipping file %s: not a data source\n", f.Path) | ||
// We'll skip the file if it's not a data source | ||
continue | ||
} | ||
return cli.MessageAndError(fmt.Sprintf("Error creating data source from %s", f.Path), err) | ||
} | ||
} | ||
|
||
// Render the table | ||
table.Render() | ||
return nil | ||
} |
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,24 @@ | ||
// SPDX-FileCopyrightText: Copyright 2023 The Minder Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package datasource | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/mindersec/minder/cmd/cli/app" | ||
) | ||
|
||
// DataSourceCmd is the root command for the data source subcommands | ||
var DataSourceCmd = &cobra.Command{ | ||
Use: "datasource", | ||
Short: "Manage data sources within a minder control plane", | ||
Long: "The data source subcommand allows the management of data sources within Minder.", | ||
RunE: func(cmd *cobra.Command, _ []string) error { | ||
return cmd.Usage() | ||
}, | ||
} | ||
|
||
func init() { | ||
app.RootCmd.AddCommand(DataSourceCmd) | ||
} |
Oops, something went wrong.
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.
We need to use
ParseResourceProto
instead. This won't work because of theoneof
usage.