Skip to content

Commit 643a331

Browse files
authored
describe connector plugins command (#2086)
* describe connector plugins command * move displayConfig to utils, needed by processor-plugin * address reviews
1 parent ac8fff0 commit 643a331

File tree

9 files changed

+299
-5
lines changed

9 files changed

+299
-5
lines changed

Diff for: cmd/conduit/internal/print_utils.go

+125
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"fmt"
1919
"strings"
2020

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

49+
// IsEmpty checks if a string is empty
50+
func IsEmpty(s string) bool {
51+
return strings.TrimSpace(s) == ""
52+
}
53+
4754
// DisplayProcessors prints the processors in a human-readable format
4855
func DisplayProcessors(processors []*apiv1.Processor, indent int) {
4956
if len(processors) == 0 {
@@ -70,3 +77,121 @@ func DisplayProcessors(processors []*apiv1.Processor, indent int) {
7077
fmt.Printf("%sUpdated At: %s\n", Indentation(indent+2), PrintTime(p.UpdatedAt))
7178
}
7279
}
80+
81+
// FormatLongString splits a string into multiple lines depending on the maxLineLength.
82+
func FormatLongString(paragraph string, maxLineLength int) string {
83+
if len(paragraph) <= maxLineLength {
84+
return paragraph
85+
}
86+
87+
var result strings.Builder
88+
var currentLine strings.Builder
89+
words := strings.Fields(paragraph)
90+
for _, word := range words {
91+
// check if adding the next word would exceed the line length
92+
if currentLine.Len()+len(word)+1 > maxLineLength {
93+
result.WriteString(currentLine.String() + "\n")
94+
currentLine.Reset()
95+
}
96+
if currentLine.Len() > 0 {
97+
currentLine.WriteString(" ")
98+
}
99+
currentLine.WriteString(word)
100+
}
101+
102+
// add the last line if it's not empty
103+
if currentLine.Len() > 0 {
104+
result.WriteString(currentLine.String())
105+
}
106+
107+
return result.String()
108+
}
109+
110+
func DisplayConfigParams(cfg map[string]*configv1.Parameter) {
111+
table := simpletable.New()
112+
113+
table.Header = &simpletable.Header{
114+
Cells: []*simpletable.Cell{
115+
{Align: simpletable.AlignCenter, Text: "NAME"},
116+
{Align: simpletable.AlignCenter, Text: "TYPE"},
117+
{Align: simpletable.AlignCenter, Text: "DESCRIPTION"},
118+
{Align: simpletable.AlignCenter, Text: "DEFAULT"},
119+
{Align: simpletable.AlignCenter, Text: "VALIDATIONS"},
120+
},
121+
}
122+
123+
// create slices for ordered parameters, needed to keep the name
124+
var requiredParams, otherParams, sdkParams []struct {
125+
name string
126+
param *configv1.Parameter
127+
}
128+
129+
// separate parameters into three groups for ordering purposes
130+
for name, param := range cfg {
131+
switch {
132+
case strings.HasPrefix(name, "sdk"):
133+
sdkParams = append(sdkParams, struct {
134+
name string
135+
param *configv1.Parameter
136+
}{name: name, param: param})
137+
case isRequired(param.Validations):
138+
requiredParams = append(requiredParams, struct {
139+
name string
140+
param *configv1.Parameter
141+
}{name: name, param: param})
142+
default:
143+
otherParams = append(otherParams, struct {
144+
name string
145+
param *configv1.Parameter
146+
}{name: name, param: param})
147+
}
148+
}
149+
150+
// combine ordered parameters
151+
orderedParams := append(requiredParams, otherParams...) //nolint:gocritic // intentional
152+
orderedParams = append(orderedParams, sdkParams...)
153+
154+
for _, item := range orderedParams {
155+
r := []*simpletable.Cell{
156+
{Align: simpletable.AlignLeft, Text: item.name},
157+
{Align: simpletable.AlignLeft, Text: formatType(item.param.GetType().String())},
158+
{Align: simpletable.AlignLeft, Text: FormatLongString(item.param.Description, 100)},
159+
{Align: simpletable.AlignLeft, Text: item.param.Default},
160+
{Align: simpletable.AlignLeft, Text: formatValidations(item.param.Validations)},
161+
}
162+
table.Body.Cells = append(table.Body.Cells, r)
163+
}
164+
165+
table.SetStyle(simpletable.StyleDefault)
166+
fmt.Println(table.String())
167+
}
168+
169+
func formatType(input string) string {
170+
return strings.TrimPrefix(strings.ToLower(input), "type_")
171+
}
172+
173+
func isRequired(validations []*configv1.Validation) bool {
174+
for _, validation := range validations {
175+
if strings.ToUpper(validation.GetType().String()) == configv1.Validation_TYPE_REQUIRED.String() {
176+
return true
177+
}
178+
}
179+
return false
180+
}
181+
182+
func formatValidations(v []*configv1.Validation) string {
183+
var result strings.Builder
184+
for _, validation := range v {
185+
if result.Len() > 0 {
186+
result.WriteString(", ")
187+
}
188+
formattedType := formatType(validation.GetType().String())
189+
value := validation.GetValue()
190+
if value == "" {
191+
result.WriteString(fmt.Sprintf("[%s]", formattedType))
192+
} else {
193+
result.WriteString(fmt.Sprintf("[%s=%s]", formattedType, value))
194+
}
195+
}
196+
return result.String()
197+
}

Diff for: cmd/conduit/root/connectorplugins/connector_plugins.go

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func (c *ConnectorPluginsCommand) Aliases() []string { return []string{"connecto
3131
func (c *ConnectorPluginsCommand) SubCommands() []ecdysis.Command {
3232
return []ecdysis.Command{
3333
&ListCommand{},
34+
&DescribeCommand{},
3435
}
3536
}
3637

Diff for: cmd/conduit/root/connectorplugins/describe.go

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright © 2025 Meroxa, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package connectorplugins
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
"github.com/conduitio/conduit/cmd/conduit/api"
22+
"github.com/conduitio/conduit/cmd/conduit/cecdysis"
23+
"github.com/conduitio/conduit/cmd/conduit/internal"
24+
"github.com/conduitio/conduit/pkg/foundation/cerrors"
25+
apiv1 "github.com/conduitio/conduit/proto/api/v1"
26+
"github.com/conduitio/ecdysis"
27+
)
28+
29+
var (
30+
_ cecdysis.CommandWithExecuteWithClient = (*DescribeCommand)(nil)
31+
_ ecdysis.CommandWithAliases = (*DescribeCommand)(nil)
32+
_ ecdysis.CommandWithDocs = (*DescribeCommand)(nil)
33+
_ ecdysis.CommandWithArgs = (*DescribeCommand)(nil)
34+
)
35+
36+
type DescribeArgs struct {
37+
ConnectorPluginID string
38+
}
39+
40+
type DescribeCommand struct {
41+
args DescribeArgs
42+
}
43+
44+
func (c *DescribeCommand) Usage() string { return "describe" }
45+
46+
func (c *DescribeCommand) Docs() ecdysis.Docs {
47+
return ecdysis.Docs{
48+
Short: "Describe an existing connector plugin",
49+
Long: `This command requires Conduit to be already running since it will describe a connector plugin that
50+
could be added to your pipelines. You can list existing connector plugins with the 'conduit connector-plugins list' command.`,
51+
Example: "conduit connector-plugins describe builtin:[email protected]\n" +
52+
"conduit connector-plugins desc standalone:[email protected]",
53+
}
54+
}
55+
56+
func (c *DescribeCommand) Aliases() []string { return []string{"desc"} }
57+
58+
func (c *DescribeCommand) Args(args []string) error {
59+
if len(args) == 0 {
60+
return cerrors.Errorf("requires a connector plugin ID")
61+
}
62+
63+
if len(args) > 1 {
64+
return cerrors.Errorf("too many arguments")
65+
}
66+
67+
c.args.ConnectorPluginID = args[0]
68+
return nil
69+
}
70+
71+
func (c *DescribeCommand) ExecuteWithClient(ctx context.Context, client *api.Client) error {
72+
resp, err := client.ConnectorServiceClient.ListConnectorPlugins(ctx, &apiv1.ListConnectorPluginsRequest{
73+
Name: c.args.ConnectorPluginID,
74+
})
75+
if err != nil {
76+
return fmt.Errorf("failed to list connector plguin: %w", err)
77+
}
78+
79+
if len(resp.Plugins) == 0 {
80+
return nil
81+
}
82+
83+
displayConnectorPluginsDescription(resp.Plugins[0])
84+
85+
return nil
86+
}
87+
88+
func displayConnectorPluginsDescription(c *apiv1.ConnectorPluginSpecifications) {
89+
if !internal.IsEmpty(c.Name) {
90+
fmt.Printf("Name: %s\n", c.Name)
91+
}
92+
if !internal.IsEmpty(c.Summary) {
93+
fmt.Printf("Summary: %s\n", c.Summary)
94+
}
95+
if !internal.IsEmpty(c.Description) {
96+
fmt.Printf("Description: %s\n", c.Description)
97+
}
98+
if !internal.IsEmpty(c.Author) {
99+
fmt.Printf("Author: %s\n", c.Author)
100+
}
101+
if !internal.IsEmpty(c.Version) {
102+
fmt.Printf("Version: %s\n", c.Version)
103+
}
104+
if len(c.SourceParams) > 0 {
105+
fmt.Printf("\nSource Parameters:\n")
106+
internal.DisplayConfigParams(c.SourceParams)
107+
}
108+
if len(c.DestinationParams) > 0 {
109+
fmt.Printf("\nDestination Parameters:\n")
110+
internal.DisplayConfigParams(c.DestinationParams)
111+
}
112+
}

Diff for: cmd/conduit/root/connectorplugins/describe_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright © 2025 Meroxa, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package connectorplugins
16+
17+
import (
18+
"testing"
19+
20+
"github.com/matryer/is"
21+
)
22+
23+
func TestDescribeExecutionNoArgs(t *testing.T) {
24+
is := is.New(t)
25+
26+
c := DescribeCommand{}
27+
err := c.Args([]string{})
28+
29+
expected := "requires a connector plugin ID"
30+
31+
is.True(err != nil)
32+
is.Equal(err.Error(), expected)
33+
}
34+
35+
func TestDescribeExecutionMultipleArgs(t *testing.T) {
36+
is := is.New(t)
37+
38+
c := DescribeCommand{}
39+
err := c.Args([]string{"foo", "bar"})
40+
41+
expected := "too many arguments"
42+
43+
is.True(err != nil)
44+
is.Equal(err.Error(), expected)
45+
}
46+
47+
func TestDescribeExecutionCorrectArgs(t *testing.T) {
48+
is := is.New(t)
49+
connectorPluginID := "connector-plugin-id"
50+
51+
c := DescribeCommand{}
52+
err := c.Args([]string{connectorPluginID})
53+
54+
is.NoErr(err)
55+
is.Equal(c.args.ConnectorPluginID, connectorPluginID)
56+
}

Diff for: cmd/conduit/root/connectorplugins/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,6 @@ func displayConnectorPlugins(connectorPlugins []*apiv1.ConnectorPluginSpecificat
9898

9999
table.Body.Cells = append(table.Body.Cells, r)
100100
}
101-
table.SetStyle(simpletable.StyleCompact)
101+
table.SetStyle(simpletable.StyleDefault)
102102
fmt.Println(table.String())
103103
}

Diff for: cmd/conduit/root/connectors/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,6 @@ func displayConnectors(connectors []*apiv1.Connector) {
104104

105105
table.Body.Cells = append(table.Body.Cells, r)
106106
}
107-
table.SetStyle(simpletable.StyleCompact)
107+
table.SetStyle(simpletable.StyleDefault)
108108
fmt.Println(table.String())
109109
}

Diff for: cmd/conduit/root/pipelines/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,6 @@ func displayPipelines(pipelines []*apiv1.Pipeline) {
9090

9191
table.Body.Cells = append(table.Body.Cells, r)
9292
}
93-
table.SetStyle(simpletable.StyleCompact)
93+
table.SetStyle(simpletable.StyleDefault)
9494
fmt.Println(table.String())
9595
}

Diff for: cmd/conduit/root/processorplugins/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,6 @@ func displayProcessorPlugins(processorPlugins []*apiv1.ProcessorPluginSpecificat
9898

9999
table.Body.Cells = append(table.Body.Cells, r)
100100
}
101-
table.SetStyle(simpletable.StyleCompact)
101+
table.SetStyle(simpletable.StyleDefault)
102102
fmt.Println(table.String())
103103
}

Diff for: cmd/conduit/root/processors/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,6 @@ func displayProcessors(processors []*apiv1.Processor) {
9191

9292
table.Body.Cells = append(table.Body.Cells, r)
9393
}
94-
table.SetStyle(simpletable.StyleCompact)
94+
table.SetStyle(simpletable.StyleDefault)
9595
fmt.Println(table.String())
9696
}

0 commit comments

Comments
 (0)