-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from otterize/orisho/merge_cli
Split CLI into its own repository
- Loading branch information
Showing
23 changed files
with
1,785 additions
and
0 deletions.
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,18 @@ | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
# IDE | ||
.idea/ |
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,21 @@ | ||
# Otterize CLI | ||
|
||
 | ||
|
||
|
||
 | ||
 | ||
[](https://godoc.org/nanomsg.org/go/mangos/v2) | ||
 | ||
 | ||
|
||
[About](#about) | [Quickstart](https://docs.otterize.com/documentation/quick-tutorials/network-mapper) | [Contributing](#contributing) | [Slack](#slack) | ||
|
||
## About | ||
|
||
## Contributing | ||
1. Feel free to fork and open a pull request! Include tests and document your code in [Godoc style](https://go.dev/blog/godoc) | ||
2. In your pull request, please refer to an existing issue or open a new one. | ||
|
||
## Slack | ||
[Join the Otterize Slack!](https://join.slack.com/t/otterizeworkspace/shared_invite/zt-1fnbnl1lf-ub6wler4QrW6ZzIn2U9x1A) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,109 @@ | ||
package convert | ||
|
||
import ( | ||
"bufio" | ||
"errors" | ||
"fmt" | ||
"github.com/amit7itz/goset" | ||
"github.com/otterize/intents-operator/src/operator/api/v1alpha1" | ||
"github.com/otterize/otterize-cli/src/pkg/consts" | ||
"github.com/otterize/otterize-cli/src/pkg/intentsprinter" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"io" | ||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
k8syaml "k8s.io/apimachinery/pkg/util/yaml" | ||
"os" | ||
"path/filepath" | ||
"sigs.k8s.io/yaml" | ||
) | ||
|
||
const regularFile = 0 | ||
const FilepathKey = "filename" | ||
const FilepathShorthand = "f" | ||
|
||
func NewIntentsResourceFromIntentsSpec(spec v1alpha1.IntentsSpec) *v1alpha1.ClientIntents { | ||
return &v1alpha1.ClientIntents{ | ||
TypeMeta: v1.TypeMeta{ | ||
Kind: consts.IntentsKind, | ||
APIVersion: consts.IntentsAPIVersion, | ||
}, | ||
ObjectMeta: v1.ObjectMeta{ | ||
Name: spec.Service.Name, | ||
}, | ||
Spec: spec.DeepCopy(), | ||
} | ||
} | ||
|
||
var ConvertCmd = &cobra.Command{ | ||
Use: "convert", | ||
Short: "Converts Otterize intents to Kubernetes ClientIntents resources.", | ||
Long: ``, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
printer := intentsprinter.IntentsPrinter{} | ||
allowedExts := goset.NewSet(".yaml", ".yml") | ||
fileInfo, err := os.Stat(viper.GetString(FilepathKey)) | ||
if err != nil { | ||
return fmt.Errorf("failed to get info for path %s: %w", viper.GetString(FilepathKey), err) | ||
} | ||
filePaths := make([]string, 0) | ||
if fileInfo.IsDir() { | ||
entries, err := os.ReadDir(viper.GetString(FilepathKey)) | ||
if err != nil { | ||
return fmt.Errorf("failed to read dir %s: %w", viper.GetString(FilepathKey), err) | ||
} | ||
for _, entry := range entries { | ||
if !allowedExts.Contains(filepath.Ext(entry.Name())) || entry.Type() != regularFile { | ||
continue | ||
} | ||
filePaths = append(filePaths, filepath.Join(viper.GetString(FilepathKey), entry.Name())) | ||
} | ||
} else { | ||
filePaths = append(filePaths, viper.GetString(FilepathKey)) | ||
} | ||
|
||
for _, path := range filePaths { | ||
err := func() error { | ||
file, err := os.Open(path) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
yamlReader := k8syaml.NewYAMLReader(bufio.NewReader(file)) | ||
for { | ||
doc, err := yamlReader.Read() | ||
if err != nil { | ||
if errors.Is(err, io.EOF) { | ||
break | ||
} | ||
|
||
return fmt.Errorf("unable to parse YAML file %s: %w", path, err) | ||
} | ||
|
||
var intentsSpec v1alpha1.IntentsSpec | ||
err = yaml.UnmarshalStrict(doc, &intentsSpec) | ||
if err != nil { | ||
return fmt.Errorf("unable to parse YAML file %s: %w", path, err) | ||
} | ||
|
||
resource := NewIntentsResourceFromIntentsSpec(intentsSpec) | ||
err = printer.PrintObj(resource, os.Stdout) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
}() | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
func init() { | ||
ConvertCmd.Flags().StringP(FilepathKey, FilepathShorthand, ".", | ||
"filename that contains the intents, or a directory containing intents") | ||
} |
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,172 @@ | ||
package export | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/otterize/intents-operator/src/operator/api/v1alpha1" | ||
"github.com/otterize/otterize-cli/src/pkg/consts" | ||
"github.com/otterize/otterize-cli/src/pkg/intentsprinter" | ||
"github.com/otterize/otterize-cli/src/pkg/mapperclient" | ||
"github.com/otterize/otterize-cli/src/pkg/output" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
) | ||
|
||
const OutputLocationKey = "output" | ||
const OutputLocationShorthand = "o" | ||
const OutputTypeKey = "output-type" | ||
const OutputTypeDefault = OutputTypeSingleFile | ||
const OutputTypeSingleFile = "single-file" | ||
const OutputTypeDirectory = "dir" | ||
const OutputFormatKey = "format" | ||
const OutputFormatDefault = OutputFormatYAML | ||
const OutputFormatYAML = "yaml" | ||
const OutputFormatJSON = "json" | ||
const NamespacesKey = "namespaces" | ||
const NamespacesShorthand = "n" | ||
|
||
func writeIntentsFile(filePath string, intents []v1alpha1.ClientIntents) error { | ||
f, err := os.Create(filePath) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
formatted, err := getFormattedIntents(intents) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = f.WriteString(formatted) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
var ExportCmd = &cobra.Command{ | ||
Use: "export", | ||
Short: "", | ||
Long: ``, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return mapperclient.WithClient(func(c *mapperclient.Client) error { | ||
ctxTimeout, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||
defer cancel() | ||
namespacesFilter := viper.GetStringSlice(NamespacesKey) | ||
intentsFromMapper, err := c.ServiceIntents(ctxTimeout, namespacesFilter) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
outputList := make([]v1alpha1.ClientIntents, 0) | ||
|
||
for _, serviceIntents := range intentsFromMapper { | ||
intentList := make([]v1alpha1.Intent, 0) | ||
|
||
for _, serviceIntent := range serviceIntents.Intents { | ||
intent := v1alpha1.Intent{ | ||
Type: v1alpha1.IntentTypeHTTP, | ||
Name: serviceIntent.Name, | ||
} | ||
if len(serviceIntent.Namespace) != 0 { | ||
intent.Namespace = serviceIntent.Namespace | ||
} | ||
intentList = append(intentList, intent) | ||
} | ||
|
||
intentsOutput := v1alpha1.ClientIntents{ | ||
TypeMeta: v1.TypeMeta{ | ||
Kind: consts.IntentsKind, | ||
APIVersion: consts.IntentsAPIVersion, | ||
}, | ||
ObjectMeta: v1.ObjectMeta{ | ||
Name: serviceIntents.Client.Name, | ||
Namespace: serviceIntents.Client.Namespace, | ||
}, | ||
Spec: &v1alpha1.IntentsSpec{Service: v1alpha1.Service{Name: serviceIntents.Client.Name}}, | ||
} | ||
|
||
if len(intentList) != 0 { | ||
intentsOutput.Spec.Calls = intentList | ||
} | ||
|
||
outputList = append(outputList, intentsOutput) | ||
} | ||
|
||
if viper.GetString(OutputLocationKey) != "" { | ||
switch outputTypeVal := viper.GetString(OutputTypeKey); { | ||
case outputTypeVal == OutputTypeSingleFile: | ||
err := writeIntentsFile(viper.GetString(OutputLocationKey), outputList) | ||
if err != nil { | ||
return err | ||
} | ||
output.PrintStderr("Successfully wrote intents into %s", viper.GetString(OutputLocationKey)) | ||
case outputTypeVal == OutputTypeDirectory: | ||
err := os.MkdirAll(viper.GetString(OutputLocationKey), 0700) | ||
if err != nil { | ||
return fmt.Errorf("could not create dir %s: %w", viper.GetString(OutputLocationKey), err) | ||
} | ||
|
||
for _, intent := range outputList { | ||
filePath := fmt.Sprintf("%s.%s.yaml", intent.Name, intent.Namespace) | ||
if err != nil { | ||
return err | ||
} | ||
filePath = filepath.Join(viper.GetString(OutputLocationKey), filePath) | ||
err := writeIntentsFile(filePath, []v1alpha1.ClientIntents{intent}) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
output.PrintStderr("Successfully wrote intents into %s", viper.GetString(OutputLocationKey)) | ||
default: | ||
return fmt.Errorf("unexpected output type %s, use one of (%s, %s)", outputTypeVal, OutputTypeSingleFile, OutputTypeDirectory) | ||
} | ||
|
||
} else { | ||
formatted, err := getFormattedIntents(outputList) | ||
if err != nil { | ||
return err | ||
} | ||
output.PrintStdout(formatted) | ||
} | ||
return nil | ||
}) | ||
}, | ||
} | ||
|
||
func getFormattedIntents(intentList []v1alpha1.ClientIntents) (string, error) { | ||
switch outputFormatVal := viper.GetString(OutputFormatKey); { | ||
case outputFormatVal == OutputFormatJSON: | ||
formatted, err := json.MarshalIndent(intentList, "", " ") | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return string(formatted), nil | ||
case outputFormatVal == OutputFormatYAML: | ||
buf := bytes.Buffer{} | ||
|
||
printer := intentsprinter.IntentsPrinter{} | ||
for _, intentYAML := range intentList { | ||
err := printer.PrintObj(&intentYAML, &buf) | ||
if err != nil { | ||
return "", err | ||
} | ||
} | ||
return buf.String(), nil | ||
default: | ||
return "", fmt.Errorf("unexpected output format %s, use one of (%s, %s)", outputFormatVal, OutputFormatJSON, OutputFormatYAML) | ||
} | ||
} | ||
|
||
func init() { | ||
ExportCmd.Flags().StringP(OutputLocationKey, OutputLocationShorthand, "", "file or dir path to write the output into") | ||
ExportCmd.Flags().String(OutputTypeKey, OutputTypeDefault, fmt.Sprintf("whether to write output to file or dir: %s/%s", OutputTypeSingleFile, OutputTypeDirectory)) | ||
ExportCmd.Flags().String(OutputFormatKey, OutputFormatDefault, fmt.Sprintf("format to output the intents - %s/%s", OutputFormatYAML, OutputFormatJSON)) | ||
ExportCmd.Flags().StringSliceP(NamespacesKey, NamespacesShorthand, nil, "filter for specific namespaces") | ||
} |
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,20 @@ | ||
package intents | ||
|
||
import ( | ||
"github.com/otterize/otterize-cli/src/cmd/intents/convert" | ||
"github.com/otterize/otterize-cli/src/cmd/intents/export" | ||
"github.com/otterize/otterize-cli/src/cmd/intents/list" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var IntentsCmd = &cobra.Command{ | ||
Use: "intents", | ||
Short: "", | ||
Long: ``, | ||
} | ||
|
||
func init() { | ||
IntentsCmd.AddCommand(export.ExportCmd) | ||
IntentsCmd.AddCommand(list.ListCmd) | ||
IntentsCmd.AddCommand(convert.ConvertCmd) | ||
} |
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,44 @@ | ||
package list | ||
|
||
import ( | ||
"context" | ||
"github.com/otterize/otterize-cli/src/pkg/mapperclient" | ||
"github.com/otterize/otterize-cli/src/pkg/output" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
const NamespacesKey = "namespaces" | ||
const NamespacesShorthand = "n" | ||
|
||
var ListCmd = &cobra.Command{ | ||
Use: "list", | ||
Short: "", | ||
Long: ``, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return mapperclient.WithClient(func(c *mapperclient.Client) error { | ||
namespacesFilter := viper.GetStringSlice(NamespacesKey) | ||
servicesIntents, err := c.ServiceIntents(context.Background(), namespacesFilter) | ||
if err != nil { | ||
return err | ||
} | ||
for _, service := range servicesIntents { | ||
output.PrintStdout("%s in namespace %s calls:", service.Client.Name, service.Client.Namespace) | ||
for _, intent := range service.Intents { | ||
if len(intent.Namespace) != 0 { | ||
output.PrintStdout(" - %s in namespace %s", intent.Name, intent.Namespace) | ||
continue | ||
} | ||
|
||
output.PrintStdout(" - %s in same namespace", intent.Name) | ||
} | ||
|
||
} | ||
return nil | ||
}) | ||
}, | ||
} | ||
|
||
func init() { | ||
ListCmd.Flags().StringSliceP(NamespacesKey, NamespacesShorthand, nil, "filter for specific namespaces") | ||
} |
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 @@ | ||
package main | ||
|
||
func main() { | ||
Execute() | ||
} |
Oops, something went wrong.