Skip to content

Commit

Permalink
Merge pull request #1 from otterize/orisho/merge_cli
Browse files Browse the repository at this point in the history
Split CLI into its own repository
  • Loading branch information
orishoshan authored Sep 14, 2022
2 parents 1a26c0c + aadb34b commit 86eda59
Show file tree
Hide file tree
Showing 23 changed files with 1,785 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .gitignore
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/
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Otterize CLI

![Otter Manning Helm](./otterhelm.png)


![build](https://img.shields.io/static/v1?label=build&message=passing&color=success)
![go report](https://img.shields.io/static/v1?label=go%20report&message=A%2B&color=success)
[![GoDoc reference example](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/nanomsg.org/go/mangos/v2)
![openssf](https://img.shields.io/static/v1?label=openssf%20best%20practices&message=passing&color=success)
![community](https://img.shields.io/badge/slack-Otterize_Slack-orange.svg?logo=slack)

[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)
Binary file added otterhelm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
109 changes: 109 additions & 0 deletions src/cmd/intents/convert/convert.go
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")
}
172 changes: 172 additions & 0 deletions src/cmd/intents/export/export.go
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")
}
20 changes: 20 additions & 0 deletions src/cmd/intents/intents.go
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)
}
44 changes: 44 additions & 0 deletions src/cmd/intents/list/intents-list.go
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")
}
5 changes: 5 additions & 0 deletions src/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package main

func main() {
Execute()
}
Loading

0 comments on commit 86eda59

Please sign in to comment.