Skip to content

Commit

Permalink
Add command line tool to query / upload to terrarium (#62)
Browse files Browse the repository at this point in the history
* Initial commit of CLI

* Delete cli

* Unit tests

* Add code to publish a module

* Add better error handling and improve container deps output

* Add module-deps command

* Add publish command

* Update module_publish.go

* Delete LICENSE

* Tidy up

* Ensure cli is build on PR

* Update pr.yml

* Update pr.yml

* Update module_publish.go

* Remove copyright comment
  • Loading branch information
adcharre committed Dec 16, 2023
1 parent c7e4b6e commit e54feae
Show file tree
Hide file tree
Showing 14 changed files with 848 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,16 @@ jobs:
- name: Build
run: |
go build
- name: Test CLI Code
run: |
export CI=true
cd tools/cli
go test './...'
echo "CLI Tests passed successfully."
- name: Build CLI
run: |
cd tools/cli
go build
1 change: 1 addition & 0 deletions tools/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cli
34 changes: 34 additions & 0 deletions tools/cli/cmd/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cmd

import (
"github.com/spf13/cobra"
"github.com/terrariumcloud/terrarium/pkg/terrarium/module"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

// moduleCmd represents the module command
var moduleCmd = &cobra.Command{
Use: "module",
Short: "Commands for managing modules",
}

func init() {
rootCmd.AddCommand(moduleCmd)
}

func getModulePublisherClient() (*grpc.ClientConn, module.PublisherClient, error) {
conn, err := grpc.Dial(terrariumEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, nil, err
}
return conn, module.NewPublisherClient(conn), nil
}

func getModuleConsumerClient() (*grpc.ClientConn, module.ConsumerClient, error) {
conn, err := grpc.Dial(terrariumEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, nil, err
}
return conn, module.NewConsumerClient(conn), nil
}
55 changes: 55 additions & 0 deletions tools/cli/cmd/module_containerDeps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cmd

import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/terrariumcloud/terrarium/pkg/terrarium/module"
"io"
)

// containerDepsCmd represents the containerDeps command
var containerDepsCmd = &cobra.Command{
Use: "container-deps",
Short: "List the container dependencies for a module",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
conn, client, err := getModuleConsumerClient()
if err != nil {
printErrorAndExit("Failed to connect to terrarium", err, 1)
}
defer func() { _ = conn.Close() }()
req := module.RetrieveContainerDependenciesRequestV2{
Module: &module.Module{
Name: args[0],
Version: args[1],
},
}
responseClient, err := client.RetrieveContainerDependenciesV2(context.Background(), &req)
if err != nil {
printErrorAndExit("Failed to retrieve container dependencies", err, 1)
}
for {
response, err := responseClient.Recv()
if err == io.EOF {
break
}
if err != nil {
printErrorAndExit("Retrieving dependencies failed", err, 1)
}
fmt.Printf("%s:%s:\n", response.Module.Name, response.Module.Version)
for name, containerDetails := range response.Dependencies {
fmt.Printf(" %s/%s:%s:\n", containerDetails.Namespace, name, containerDetails.Tag)

for _, details := range containerDetails.Images {
fmt.Printf(" - arch: %s\n", details.Arch)
fmt.Printf(" image: %s\n", details.Image)
}
}
}
},
}

func init() {
moduleCmd.AddCommand(containerDepsCmd)
}
50 changes: 50 additions & 0 deletions tools/cli/cmd/module_moduleDeps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cmd

import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/terrariumcloud/terrarium/pkg/terrarium/module"
"io"
)

// moduleDepsCmd represents the moduleDeps command
var moduleDepsCmd = &cobra.Command{
Use: "module-deps",
Short: "List the module dependencies of the specified module.",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
conn, client, err := getModuleConsumerClient()
if err != nil {
printErrorAndExit("Failed to connect to terrarium", err, 1)
}
defer func() { _ = conn.Close() }()
req := module.RetrieveModuleDependenciesRequest{
Module: &module.Module{
Name: args[0],
Version: args[1],
},
}
responseClient, err := client.RetrieveModuleDependencies(context.Background(), &req)
if err != nil {
printErrorAndExit("Failed to retrieve module dependencies", err, 1)
}
for {
response, err := responseClient.Recv()
if err == io.EOF {
break
}
if err != nil {
printErrorAndExit("Retrieving dependencies failed", err, 1)
}
fmt.Printf("%s:%s:\n", response.Module.Name, response.Module.Version)
for _, module := range response.Dependencies {
fmt.Printf(" - %s:%s\n", module.Name, module.Version)
}
}
},
}

func init() {
moduleCmd.AddCommand(moduleDepsCmd)
}
95 changes: 95 additions & 0 deletions tools/cli/cmd/module_publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
"fmt"
module2 "github.com/terrariumcloud/terrarium/pkg/terrarium/module"
"github.com/terrariumcloud/terrarium/tools/cli/pkg/module"
"os"
"strings"

"github.com/spf13/cobra"
)

type maturityValue struct {
maturity module2.Maturity
}

var (
moduleMetadata = module.Metadata{
Name: "",
Version: "",
Description: "",
Source: "",
Maturity: module2.Maturity_STABLE,
}
maturityVar = maturityValue{maturity: module2.Maturity_STABLE}
)

// modulePublishCmd represents the publish command
var modulePublishCmd = &cobra.Command{
Use: "publish [module source.zip]",
Short: "Publishes a zip file as a module version in terrarium.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
conn, client, err := getModulePublisherClient()
if err != nil {
printErrorAndExit("Failed to connect to terrarium", err, 1)
}
defer func() { _ = conn.Close() }()
if source, err := os.Open(args[0]); err != nil {
printErrorAndExit(fmt.Sprintf("Failed to open %s", args[0]), err, 1)
} else {
moduleMetadata.Maturity = maturityVar.maturity
err := module.Publish(client, source, moduleMetadata)
if err != nil {
printErrorAndExit("Publishing module failed", err, 1)
} else {
fmt.Println("Module published.")
}
}
},
}

func init() {
moduleCmd.AddCommand(modulePublishCmd)

modulePublishCmd.Flags().StringVar(&moduleMetadata.Name, "name", "", "Name of the module in the form \"<organisation>/<name>/<provider>\".")
_ = modulePublishCmd.MarkFlagRequired("name")
modulePublishCmd.Flags().StringVar(&moduleMetadata.Version, "version", "", "Semantic version of the module to publish.")
_ = modulePublishCmd.MarkFlagRequired("version")
modulePublishCmd.Flags().StringVar(&moduleMetadata.Description, "description", "", "Description of the module.")
modulePublishCmd.Flags().StringVar(&moduleMetadata.Source, "source", "", "URL containing the module source.")
modulePublishCmd.Flags().Var(&maturityVar, "maturity", "The maturity of the module, one of IDEA, PLANNING, DEVELOPING, ALPHA, BETA, STABLE, DEPRECATED or END-OF-LIFE.")
}

var maturityToString = map[module2.Maturity]string{
module2.Maturity_IDEA: "IDEA",
module2.Maturity_PLANNING: "PLANNING",
module2.Maturity_DEVELOPING: "DEVELOPING",
module2.Maturity_ALPHA: "ALPHA",
module2.Maturity_BETA: "BETA",
module2.Maturity_STABLE: "STABLE",
module2.Maturity_DEPRECATED: "DEPRECATED",
module2.Maturity_END_OF_LIFE: "END-OF-LIFE",
}

func (m maturityValue) String() string {
if s, ok := maturityToString[m.maturity]; ok {
return s
}
return "UNKNOWN"
}

func (m *maturityValue) Set(s string) error {
for maturity, maturityStr := range maturityToString {
if strings.ToUpper(s) == maturityStr {
m.maturity = maturity
return nil
}
}
return fmt.Errorf("unknown maturity: %s", s)
}

func (m maturityValue) Type() string {
return "maturity"
}
27 changes: 27 additions & 0 deletions tools/cli/cmd/release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cmd

import (
"github.com/terrariumcloud/terrarium/pkg/terrarium/release"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

"github.com/spf13/cobra"
)

// releaseCmd represents the release command
var releaseCmd = &cobra.Command{
Use: "release",
Short: "Commands for managing releases",
}

func init() {
rootCmd.AddCommand(releaseCmd)
}

func getReleasePublisherClient() (*grpc.ClientConn, release.ReleasePublisherClient, error) {
conn, err := grpc.Dial(terrariumEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, nil, err
}
return conn, release.NewReleasePublisherClient(conn), nil
}
67 changes: 67 additions & 0 deletions tools/cli/cmd/release_publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package cmd

import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/terrariumcloud/terrarium/pkg/terrarium/release"
"strings"
)

var (
releaseToPublish = release.PublishRequest{
Type: "",
Organization: "",
Name: "",
Version: "",
Description: "",
Links: nil,
}
releaseLinks []string
)

// releasePublishCmd represents the publish command
var releasePublishCmd = &cobra.Command{
Use: "publish",
Short: "Publish details of a new release.",
Run: func(cmd *cobra.Command, args []string) {
conn, client, err := getReleasePublisherClient()
if err != nil {
printErrorAndExit("Failed to connect to terrarium", err, 1)
}
defer func() { _ = conn.Close() }()

for _, link := range releaseLinks {
if parts := strings.SplitN(link, "=", 2); len(parts) == 1 {
releaseToPublish.Links = append(releaseToPublish.Links, &release.Link{
Title: "",
Url: parts[0],
})
} else {
releaseToPublish.Links = append(releaseToPublish.Links, &release.Link{
Title: parts[0],
Url: parts[1],
})
}
}

if _, err := client.Publish(context.Background(), &releaseToPublish); err != nil {
printErrorAndExit("Failed to publish release", err, 1)
}
fmt.Println("Release published.")
},
}

func init() {
releaseCmd.AddCommand(releasePublishCmd)
releasePublishCmd.Flags().StringVarP(&releaseToPublish.Name, "name", "n", "", "Name of the release.")
releasePublishCmd.MarkFlagRequired("name")
releasePublishCmd.Flags().StringVarP(&releaseToPublish.Version, "version", "v", "", "Version of the release.")
releasePublishCmd.MarkFlagRequired("version")
releasePublishCmd.Flags().StringVarP(&releaseToPublish.Organization, "org", "o", "", "Organization to which the release belongs.")
releasePublishCmd.MarkFlagRequired("org")
releasePublishCmd.Flags().StringVarP(&releaseToPublish.Type, "type", "t", "", "Type of release.")
releasePublishCmd.MarkFlagRequired("type")
releasePublishCmd.Flags().StringVarP(&releaseToPublish.Description, "description", "d", "", "Description of the release being published.")
releasePublishCmd.Flags().StringSliceVarP(&releaseLinks, "link", "l", []string{}, `Links and optional titles eg.: --link "title=url" or --link "url"`)
}
45 changes: 45 additions & 0 deletions tools/cli/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

var terrariumEndpoint = "localhost:3001"

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "cli",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

func init() {
rootCmd.PersistentFlags().StringVar(&terrariumEndpoint, "endpoint", terrariumEndpoint, "GRPC Endpoint for Terrarium.")
}

func printErrorAndExit(msg string, err error, exitCode int) {
fmt.Fprintf(os.Stderr, "ERROR: %s", msg)
if err != nil {
fmt.Fprintf(os.Stderr, ": %s\n", err)
} else {
fmt.Fprintln(os.Stderr, "")
}
os.Exit(exitCode)
}
Loading

0 comments on commit e54feae

Please sign in to comment.