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

describe connector plugins command #2086

Merged
merged 3 commits into from
Jan 22, 2025
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
125 changes: 125 additions & 0 deletions cmd/conduit/internal/print_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"fmt"
"strings"

"github.com/alexeyco/simpletable"
configv1 "github.com/conduitio/conduit-commons/proto/config/v1"
apiv1 "github.com/conduitio/conduit/proto/api/v1"
"google.golang.org/protobuf/types/known/timestamppb"
)
Expand All @@ -44,6 +46,11 @@ func PrintTime(ts *timestamppb.Timestamp) string {
return ts.AsTime().Format("2006-01-02T15:04:05Z")
}

// IsEmpty checks if a string is empty
func IsEmpty(s string) bool {
return strings.TrimSpace(s) == ""
}

// DisplayProcessors prints the processors in a human-readable format
func DisplayProcessors(processors []*apiv1.Processor, indent int) {
if len(processors) == 0 {
Expand All @@ -70,3 +77,121 @@ func DisplayProcessors(processors []*apiv1.Processor, indent int) {
fmt.Printf("%sUpdated At: %s\n", Indentation(indent+2), PrintTime(p.UpdatedAt))
}
}

// FormatLongString splits a string into multiple lines depending on the maxLineLength.
func FormatLongString(paragraph string, maxLineLength int) string {
if len(paragraph) <= maxLineLength {
return paragraph
}

var result strings.Builder
var currentLine strings.Builder
words := strings.Fields(paragraph)
for _, word := range words {
// check if adding the next word would exceed the line length
if currentLine.Len()+len(word)+1 > maxLineLength {
result.WriteString(currentLine.String() + "\n")
currentLine.Reset()
}
if currentLine.Len() > 0 {
currentLine.WriteString(" ")
}
currentLine.WriteString(word)
}

// add the last line if it's not empty
if currentLine.Len() > 0 {
result.WriteString(currentLine.String())
}

return result.String()
}

func DisplayConfigParams(cfg map[string]*configv1.Parameter) {
table := simpletable.New()

table.Header = &simpletable.Header{
Cells: []*simpletable.Cell{
{Align: simpletable.AlignCenter, Text: "NAME"},
{Align: simpletable.AlignCenter, Text: "TYPE"},
{Align: simpletable.AlignCenter, Text: "DESCRIPTION"},
{Align: simpletable.AlignCenter, Text: "DEFAULT"},
{Align: simpletable.AlignCenter, Text: "VALIDATIONS"},
},
}

// create slices for ordered parameters, needed to keep the name
var requiredParams, otherParams, sdkParams []struct {
name string
param *configv1.Parameter
}

// separate parameters into three groups for ordering purposes
for name, param := range cfg {
switch {
case strings.HasPrefix(name, "sdk"):
sdkParams = append(sdkParams, struct {
name string
param *configv1.Parameter
}{name: name, param: param})
case isRequired(param.Validations):
requiredParams = append(requiredParams, struct {
name string
param *configv1.Parameter
}{name: name, param: param})
default:
otherParams = append(otherParams, struct {
name string
param *configv1.Parameter
}{name: name, param: param})
}
}

// combine ordered parameters
orderedParams := append(requiredParams, otherParams...) //nolint:gocritic // intentional
orderedParams = append(orderedParams, sdkParams...)

for _, item := range orderedParams {
r := []*simpletable.Cell{
{Align: simpletable.AlignLeft, Text: item.name},
{Align: simpletable.AlignLeft, Text: formatType(item.param.GetType().String())},
{Align: simpletable.AlignLeft, Text: FormatLongString(item.param.Description, 100)},
{Align: simpletable.AlignLeft, Text: item.param.Default},
{Align: simpletable.AlignLeft, Text: formatValidations(item.param.Validations)},
}
table.Body.Cells = append(table.Body.Cells, r)
}

table.SetStyle(simpletable.StyleDefault)
raulb marked this conversation as resolved.
Show resolved Hide resolved
fmt.Println(table.String())
}

func formatType(input string) string {
return strings.TrimPrefix(strings.ToLower(input), "type_")
}

func isRequired(validations []*configv1.Validation) bool {
for _, validation := range validations {
if strings.ToUpper(validation.GetType().String()) == configv1.Validation_TYPE_REQUIRED.String() {
return true
}
}
return false
}

func formatValidations(v []*configv1.Validation) string {
var result strings.Builder
for _, validation := range v {
if result.Len() > 0 {
result.WriteString(", ")
}
formattedType := formatType(validation.GetType().String())
value := validation.GetValue()
if value == "" {
result.WriteString(fmt.Sprintf("[%s]", formattedType))
} else {
result.WriteString(fmt.Sprintf("[%s=%s]", formattedType, value))
}
}
return result.String()
}
1 change: 1 addition & 0 deletions cmd/conduit/root/connectorplugins/connector_plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (c *ConnectorPluginsCommand) Aliases() []string { return []string{"connecto
func (c *ConnectorPluginsCommand) SubCommands() []ecdysis.Command {
return []ecdysis.Command{
&ListCommand{},
&DescribeCommand{},
}
}

Expand Down
112 changes: 112 additions & 0 deletions cmd/conduit/root/connectorplugins/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright © 2025 Meroxa, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package connectorplugins

import (
"context"
"fmt"

"github.com/conduitio/conduit/cmd/conduit/api"
"github.com/conduitio/conduit/cmd/conduit/cecdysis"
"github.com/conduitio/conduit/cmd/conduit/internal"
"github.com/conduitio/conduit/pkg/foundation/cerrors"
apiv1 "github.com/conduitio/conduit/proto/api/v1"
"github.com/conduitio/ecdysis"
)

var (
_ cecdysis.CommandWithExecuteWithClient = (*DescribeCommand)(nil)
_ ecdysis.CommandWithAliases = (*DescribeCommand)(nil)
_ ecdysis.CommandWithDocs = (*DescribeCommand)(nil)
_ ecdysis.CommandWithArgs = (*DescribeCommand)(nil)
)

type DescribeArgs struct {
ConnectorPluginID string
}

type DescribeCommand struct {
args DescribeArgs
}

func (c *DescribeCommand) Usage() string { return "describe" }

func (c *DescribeCommand) Docs() ecdysis.Docs {
return ecdysis.Docs{
Short: "Describe an existing connector plugin",
Long: `This command requires Conduit to be already running since it will describe a connector plugin that
could be added to your pipelines. You can list existing connector plugins with the 'conduit connector-plugins list' command.`,
Example: "conduit connector-plugins describe builtin:[email protected]\n" +
"conduit connector-plugins desc standalone:[email protected]",
}
}

func (c *DescribeCommand) Aliases() []string { return []string{"desc"} }

func (c *DescribeCommand) Args(args []string) error {
if len(args) == 0 {
return cerrors.Errorf("requires a connector plugin ID")
}

if len(args) > 1 {
return cerrors.Errorf("too many arguments")
}

c.args.ConnectorPluginID = args[0]
return nil
}

func (c *DescribeCommand) ExecuteWithClient(ctx context.Context, client *api.Client) error {
resp, err := client.ConnectorServiceClient.ListConnectorPlugins(ctx, &apiv1.ListConnectorPluginsRequest{
Name: c.args.ConnectorPluginID,
})
if err != nil {
return fmt.Errorf("failed to list connector plguin: %w", err)
}

if len(resp.Plugins) == 0 {
return nil
}

displayConnectorPluginsDescription(resp.Plugins[0])

return nil
}

func displayConnectorPluginsDescription(c *apiv1.ConnectorPluginSpecifications) {
if !internal.IsEmpty(c.Name) {
fmt.Printf("Name: %s\n", c.Name)
}
if !internal.IsEmpty(c.Summary) {
fmt.Printf("Summary: %s\n", c.Summary)
}
if !internal.IsEmpty(c.Description) {
fmt.Printf("Description: %s\n", c.Description)
}
if !internal.IsEmpty(c.Author) {
fmt.Printf("Author: %s\n", c.Author)
}
if !internal.IsEmpty(c.Version) {
fmt.Printf("Version: %s\n", c.Version)
}
if len(c.SourceParams) > 0 {
fmt.Printf("\nSource Parameters:\n")
internal.DisplayConfigParams(c.SourceParams)
}
if len(c.DestinationParams) > 0 {
fmt.Printf("\nDestination Parameters:\n")
internal.DisplayConfigParams(c.DestinationParams)
}
}
56 changes: 56 additions & 0 deletions cmd/conduit/root/connectorplugins/describe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright © 2025 Meroxa, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package connectorplugins

import (
"testing"

"github.com/matryer/is"
)

func TestDescribeExecutionNoArgs(t *testing.T) {
is := is.New(t)

c := DescribeCommand{}
err := c.Args([]string{})

expected := "requires a connector plugin ID"

is.True(err != nil)
is.Equal(err.Error(), expected)
}

func TestDescribeExecutionMultipleArgs(t *testing.T) {
is := is.New(t)

c := DescribeCommand{}
err := c.Args([]string{"foo", "bar"})

expected := "too many arguments"

is.True(err != nil)
is.Equal(err.Error(), expected)
}

func TestDescribeExecutionCorrectArgs(t *testing.T) {
is := is.New(t)
connectorPluginID := "connector-plugin-id"

c := DescribeCommand{}
err := c.Args([]string{connectorPluginID})

is.NoErr(err)
is.Equal(c.args.ConnectorPluginID, connectorPluginID)
}
2 changes: 1 addition & 1 deletion cmd/conduit/root/connectorplugins/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,6 @@ func displayConnectorPlugins(connectorPlugins []*apiv1.ConnectorPluginSpecificat

table.Body.Cells = append(table.Body.Cells, r)
}
table.SetStyle(simpletable.StyleCompact)
table.SetStyle(simpletable.StyleDefault)
fmt.Println(table.String())
}
2 changes: 1 addition & 1 deletion cmd/conduit/root/connectors/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,6 @@ func displayConnectors(connectors []*apiv1.Connector) {

table.Body.Cells = append(table.Body.Cells, r)
}
table.SetStyle(simpletable.StyleCompact)
table.SetStyle(simpletable.StyleDefault)
fmt.Println(table.String())
}
2 changes: 1 addition & 1 deletion cmd/conduit/root/pipelines/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,6 @@ func displayPipelines(pipelines []*apiv1.Pipeline) {

table.Body.Cells = append(table.Body.Cells, r)
}
table.SetStyle(simpletable.StyleCompact)
table.SetStyle(simpletable.StyleDefault)
fmt.Println(table.String())
}
2 changes: 1 addition & 1 deletion cmd/conduit/root/processorplugins/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,6 @@ func displayProcessorPlugins(processorPlugins []*apiv1.ProcessorPluginSpecificat

table.Body.Cells = append(table.Body.Cells, r)
}
table.SetStyle(simpletable.StyleCompact)
table.SetStyle(simpletable.StyleDefault)
fmt.Println(table.String())
}
2 changes: 1 addition & 1 deletion cmd/conduit/root/processors/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,6 @@ func displayProcessors(processors []*apiv1.Processor) {

table.Body.Cells = append(table.Body.Cells, r)
}
table.SetStyle(simpletable.StyleCompact)
table.SetStyle(simpletable.StyleDefault)
fmt.Println(table.String())
}
Loading