Skip to content
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

Get kind from openapi #23

Merged
merged 2 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,6 @@ How to run integration test:
First login to your teleport proxy, for example:
```
tsh login --proxy=teleport-01.prd.tooling.cdkt.dev --auth=github
```

```
conduktor get application --cert $(tsh apps config --format=cert) --key $(tsh apps config --format=key)
```

Or:
```
export CDK_CERT=$(tsh apps config --format=cert)
export CDK_KEY=$(tsh apps config --format=key)
conduktor get application
Expand Down
90 changes: 40 additions & 50 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"os"
"strings"

"github.com/conduktor/ctl/printutils"
"github.com/conduktor/ctl/resource"
"github.com/conduktor/ctl/utils"
"github.com/go-resty/resty/v2"
"os"
"strings"
)

type Client struct {
Expand All @@ -18,16 +18,16 @@ type Client struct {
client *resty.Client
}

func Make(token string, baseUrl string, debug bool, key, cert string) Client {
func Make(token string, baseUrl string, debug bool, key, cert string) *Client {
certificate, _ := tls.LoadX509KeyPair(cert, key)
return Client{
return &Client{
token: token,
baseUrl: baseUrl + "/public/v1",
baseUrl: baseUrl,
client: resty.New().SetDebug(debug).SetHeader("Authorization", "Bearer "+token).SetCertificates(certificate),
}
}

func MakeFromEnv(debug bool, key, cert string) Client {
func MakeFromEnv() *Client {
token := os.Getenv("CDK_TOKEN")
if token == "" {
fmt.Fprintln(os.Stderr, "Please set CDK_TOKEN")
Expand All @@ -38,16 +38,11 @@ func MakeFromEnv(debug bool, key, cert string) Client {
fmt.Fprintln(os.Stderr, "Please set CDK_BASE_URL")
os.Exit(2)
}
finalKey := key
finalCert := cert
if finalKey == "" {
finalKey = os.Getenv("CDK_KEY")
}
if finalCert == "" {
finalCert = os.Getenv("CDK_CERT")
}
debug := strings.ToLower(os.Getenv("CDK_DEBUG")) == "true"
key := os.Getenv("CDK_KEY")
cert := os.Getenv("CDK_CERT")

return Make(token, baseUrl, debug, finalKey, finalCert)
return Make(token, baseUrl, debug, key, cert)
}

type UpsertResponse struct {
Expand All @@ -64,17 +59,24 @@ func extractApiError(resp *resty.Response) string {
}
}

func (client *Client) publicV1Url() string {
return client.baseUrl + "/public/v1"
}

func (client *Client) ActivateDebug() {
client.client.SetDebug(true)
}

func (client *Client) Apply(resource *resource.Resource, dryMode bool) (string, error) {
url := client.baseUrl + "/" + UpperCamelToKebab(resource.Kind)
url := client.publicV1Url() + "/" + utils.UpperCamelToKebab(resource.Kind)
builder := client.client.R().SetBody(resource.Json)
if dryMode {
builder = builder.SetQueryParam("dryMode", "true")
}
resp, err := builder.Put(url)
if err != nil {
return "", err
}
if resp.IsError() {
} else if resp.IsError() {
return "", fmt.Errorf(extractApiError(resp))
}
bodyBytes := resp.Body()
Expand All @@ -97,32 +99,33 @@ func printResponseAsYaml(bytes []byte) error {
}

func (client *Client) Get(kind string) error {
url := client.baseUrl + "/" + UpperCamelToKebab(kind)
url := client.publicV1Url() + "/" + utils.UpperCamelToKebab(kind)
resp, err := client.client.R().Get(url)
if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
}
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
}
return printResponseAsYaml(resp.Body())
}

func (client *Client) Describe(kind, name string) error {
url := client.baseUrl + "/" + UpperCamelToKebab(kind) + "/" + name
url := client.publicV1Url() + "/" + utils.UpperCamelToKebab(kind) + "/" + name
resp, err := client.client.R().Get(url)
if resp.IsError() {
return fmt.Errorf("error describing resources %s/%s, got status code: %d:\n %s", kind, name, resp.StatusCode(), string(resp.Body()))
}
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf("error describing resources %s/%s, got status code: %d:\n %s", kind, name, resp.StatusCode(), string(resp.Body()))
}
return printResponseAsYaml(resp.Body())
}

func (client *Client) Delete(kind, name string) error {
url := client.baseUrl + "/" + UpperCamelToKebab(kind) + "/" + name
url := client.publicV1Url() + "/" + utils.UpperCamelToKebab(kind) + "/" + name
resp, err := client.client.R().Delete(url)
if resp.IsError() {
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
} else {
fmt.Printf("%s/%s deleted\n", kind, name)
Expand All @@ -131,26 +134,13 @@ func (client *Client) Delete(kind, name string) error {
return err
}

func UpperCamelToKebab(input string) string {
// Split the input string into words
words := make([]string, 0)
currentWord := ""
for _, char := range input {
if char >= 'A' && char <= 'Z' {
if currentWord != "" {
words = append(words, currentWord)
}
currentWord = string(char)
} else {
currentWord += string(char)
}
}
if currentWord != "" {
words = append(words, currentWord)
func (client *Client) GetOpenApi() ([]byte, error) {
url := client.baseUrl + "/public/docs/docs.yaml"
resp, err := client.client.R().Get(url)
if err != nil {
return nil, err
} else if resp.IsError() {
return nil, fmt.Errorf(resp.String())
}

// Join the words with hyphens
kebabCase := strings.ToLower(strings.Join(words, "-"))

return kebabCase
return resp.Body(), nil
}
6 changes: 2 additions & 4 deletions cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"github.com/conduktor/ctl/client"
"github.com/conduktor/ctl/resource"
"github.com/spf13/cobra"
"os"
Expand All @@ -26,9 +25,8 @@ var applyCmd = &cobra.Command{
}
resources = append(resources, r...)
}
client := client.MakeFromEnv(*debug, *key, *cert)
for _, resource := range resources {
upsertResult, err := client.Apply(&resource, *dryRun)
upsertResult, err := apiClient.Apply(&resource, *dryRun)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not apply resource %s/%s: %s\n", resource.Kind, resource.Name, err)
os.Exit(1)
Expand All @@ -52,7 +50,7 @@ func resourceForPath(path string) ([]resource.Resource, error) {
}
}

func init() {
func initApply() {
rootCmd.AddCommand(applyCmd)

// Here you will define your flags and configuration settings.
Expand Down
6 changes: 2 additions & 4 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"github.com/conduktor/ctl/client"
"github.com/spf13/cobra"
"os"
)
Expand All @@ -14,15 +13,14 @@ var deleteCmd = &cobra.Command{
Long: ``,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
client := client.MakeFromEnv(*debug, *key, *cert)
err := client.Delete(args[0], args[1])
err := apiClient.Delete(args[0], args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
},
}

func init() {
func initDelete() {
rootCmd.AddCommand(deleteCmd)
}
52 changes: 46 additions & 6 deletions cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ package cmd

import (
"fmt"
"github.com/conduktor/ctl/client"
"github.com/spf13/cobra"
"os"
)

// applyCmd represents the apply command
var getCmd = &cobra.Command{
Use: "get",
Short: "get resource of a given kind",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
// Root command does nothing
cmd.Help()
os.Exit(1)
},
}

var getCmdWhenNoSchema = &cobra.Command{
Use: "get kind [name]",
Short: "get resource of a given kind",
Long: `If name not provided it will list all resource. For example:
Expand All @@ -18,12 +27,11 @@ conduktor get application myapp
will describe the application myapp`,
Args: cobra.MatchAll(cobra.MinimumNArgs(1), cobra.MaximumNArgs(2)),
Run: func(cmd *cobra.Command, args []string) {
client := client.MakeFromEnv(*debug, *key, *cert)
var err error
if len(args) == 1 {
err = client.Get(args[0])
err = apiClient.Get(args[0])
} else if len(args) == 2 {
err = client.Describe(args[0], args[1])
err = apiClient.Describe(args[0], args[1])
}
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
Expand All @@ -32,6 +40,38 @@ will describe the application myapp`,
},
}

func init() {
func initGet() {
if schemaClient == nil {
rootCmd.AddCommand(getCmdWhenNoSchema)
return
}
tags, err := schemaClient.GetKind()
if err != nil {
fmt.Fprintf(os.Stderr, "Could not load kind from openapi: %s\n", err)
rootCmd.AddCommand(getCmdWhenNoSchema)
return
}
rootCmd.AddCommand(getCmd)

for _, tag := range tags {
tagCmd := &cobra.Command{
Use: fmt.Sprintf("%s [name]", tag),
Short: "get resource of kind " + tag,
Args: cobra.MatchAll(cobra.MaximumNArgs(1)),
Long: `If name not provided it will list all resource`,
Run: func(cmd *cobra.Command, args []string) {
var err error
if len(args) == 0 {
err = apiClient.Get(tag)
} else if len(args) == 1 {
err = apiClient.Describe(tag, args[1])
}
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
},
}
getCmd.AddCommand(tagCmd)
}
}
26 changes: 26 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,35 @@ Copyright © 2024 NAME HERE <EMAIL ADDRESS>
package cmd

import (
"fmt"
"github.com/conduktor/ctl/client"
"github.com/conduktor/ctl/schema"
"github.com/spf13/cobra"
"os"
)

var debug *bool
var key *string
var cert *string
var apiClient *client.Client
var schemaClient *schema.Schema = nil

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "conduktor",
Short: "command line tools for conduktor",
Long: `You need to define the CDK_TOKEN and CDK_BASE_URL environment variables to use this tool.
You can also use the CDK_KEY,CDK_CERT instead of --key and --cert flags to use a certificate for tls authentication.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if *debug {
apiClient.ActivateDebug()
}
},
Run: func(cmd *cobra.Command, args []string) {
// Root command does nothing
cmd.Help()
os.Exit(1)
},
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
Expand All @@ -34,7 +49,18 @@ func Execute() {
}

func init() {
apiClient = client.MakeFromEnv()
openApi, err := apiClient.GetOpenApi()
if err == nil {
schemaClient, err = schema.New(openApi)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Could not load server openapi: %s\n", err)
}
debug = rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Show more information for debugging")
key = rootCmd.PersistentFlags().String("key", "", "Set pem key for certificate authentication (useful for teleport)")
cert = rootCmd.PersistentFlags().String("cert", "", "Set pem cert for certificate authentication (useful for teleport)")
initGet()
initDelete()
initApply()
}
11 changes: 10 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ go 1.22.0
require (
github.com/ghodss/yaml v1.0.0
github.com/go-resty/resty/v2 v2.11.0
github.com/jarcoal/httpmock v1.3.1
github.com/pb33f/libopenapi v0.15.14
github.com/spf13/cobra v1.8.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jarcoal/httpmock v1.3.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.6.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Loading
Loading