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

Basic conduit init and conduit pipelines init commands #1927

Merged
merged 47 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fea1e3b
v1
hariso Oct 23, 2024
56536a2
linter
hariso Oct 23, 2024
def5f51
refactor, error messages
hariso Oct 24, 2024
5e6e664
linter
hariso Oct 24, 2024
4c20953
changes
hariso Oct 24, 2024
eda2e12
Merge branch 'main' into haris/conduit-init-simpler
hariso Oct 24, 2024
b1476a3
using cobra
hariso Oct 25, 2024
dd2af35
specify pipeline name
hariso Oct 25, 2024
0750b12
explanations
hariso Oct 25, 2024
2b3269f
Merge branch 'haris/conduit-init-simpler' of github.com:ConduitIO/con…
hariso Oct 25, 2024
ebe434e
explanations
hariso Oct 25, 2024
4f9ae9a
revert
hariso Oct 25, 2024
9dff8f4
check unknown commands and flags
hariso Oct 25, 2024
191e78b
linter
hariso Oct 25, 2024
5cbafe0
Merge branch 'main' into haris/conduit-init-simpler
hariso Oct 25, 2024
feec841
fix command check
hariso Oct 25, 2024
84f22c1
print help
hariso Oct 25, 2024
63123ce
refactor
hariso Oct 25, 2024
d4701d0
refactor
hariso Oct 25, 2024
d6dc686
refactoring part 1
hariso Oct 28, 2024
936ec75
Revert "refactoring part 1"
hariso Oct 28, 2024
4e864b3
refactor
hariso Oct 28, 2024
74f5bea
cleanup
hariso Oct 28, 2024
37421c9
linter
hariso Oct 28, 2024
3c4f1a4
linter
hariso Oct 28, 2024
cd26061
refactor, linter
hariso Oct 28, 2024
f87d2c9
single quotes for values
hariso Oct 29, 2024
8f93a5c
path flag
hariso Oct 29, 2024
856c283
revert change
hariso Oct 29, 2024
68533b2
fixes
hariso Oct 29, 2024
275a1f8
pipelines group
hariso Oct 29, 2024
b855862
minor refactoringt
hariso Oct 29, 2024
49a9491
handle defaults better
hariso Oct 29, 2024
caa5db8
flag name
hariso Oct 29, 2024
50195de
Merge branch 'main' into haris/conduit-init-simpler
hariso Oct 29, 2024
c846de3
comments, revert change
hariso Oct 29, 2024
7604c54
config base path
hariso Oct 30, 2024
974d1b9
change default path for conduit pipelines init
hariso Oct 30, 2024
de20e60
change log to file
hariso Nov 4, 2024
c37f42c
fix validation
hariso Nov 5, 2024
d4dd6d0
change demo pipeline
hariso Nov 5, 2024
e13c389
Merge branch 'main' into haris/conduit-init-simpler
hariso Nov 5, 2024
5edbca7
post pipeline init hints
hariso Nov 6, 2024
b18e90b
Merge branch 'haris/conduit-init-simpler' of github.com:ConduitIO/con…
hariso Nov 6, 2024
7472b60
Merge branch 'main' into haris/conduit-init-simpler
hariso Nov 6, 2024
e2e247a
ignore golangci report
hariso Nov 6, 2024
b7acd0c
Update cmd/cli/pipelines_init.go
hariso Nov 6, 2024
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
147 changes: 147 additions & 0 deletions cmd/cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright © 2024 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 cli

import (
"fmt"
"os"

"github.com/conduitio/conduit/pkg/conduit"
"github.com/spf13/cobra"
)

var (
initArgs InitArgs
pipelinesInitArgs PipelinesInitArgs
)

type Instance struct {
rootCmd *cobra.Command
}

// New creates a new CLI Instance.
func New() *Instance {
return &Instance{
rootCmd: buildRootCmd(),
}
}

func (i *Instance) Run() {
if err := i.rootCmd.Execute(); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}

func buildRootCmd() *cobra.Command {
cfg := conduit.DefaultConfig()

cmd := &cobra.Command{
Use: "conduit",
Short: "Conduit CLI",
Long: "Conduit CLI is a command-line that helps you interact with and manage Conduit.",
Version: conduit.Version(true),
Run: func(cmd *cobra.Command, args []string) {
e := &conduit.Entrypoint{}
e.Serve(cfg)
},
}
cmd.CompletionOptions.DisableDefaultCmd = true
conduit.Flags(&cfg).VisitAll(cmd.Flags().AddGoFlag)

// init
cmd.AddCommand(buildInitCmd())

// pipelines
cmd.AddGroup(&cobra.Group{
ID: "pipelines",
Title: "Pipelines",
})
cmd.AddCommand(buildPipelinesCmd())

return cmd
}

func buildInitCmd() *cobra.Command {
initCmd := &cobra.Command{
Use: "init",
Short: "Initialize Conduit with a configuration file and directories.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return NewConduitInit(initArgs).Run()
},
}
initCmd.Flags().StringVar(
&initArgs.Path,
"config.path",
"",
"path where Conduit will be initialized",
)

return initCmd
}

func buildPipelinesCmd() *cobra.Command {
pipelinesCmd := &cobra.Command{
Use: "pipelines",
Short: "Initialize and manage pipelines",
Args: cobra.NoArgs,
GroupID: "pipelines",
}

pipelinesCmd.AddCommand(buildPipelinesInitCmd())

return pipelinesCmd
}

func buildPipelinesInitCmd() *cobra.Command {
pipelinesInitCmd := &cobra.Command{
Use: "init [pipeline-name]",
Short: "Initialize an example pipeline.",
Long: `Initialize a pipeline configuration file, with all of parameters for source and destination connectors
initialized and described. The source and destination connector can be chosen via flags. If no connectors are chosen, then
a simple and runnable generator-to-log pipeline is configured.`,
Args: cobra.MaximumNArgs(1),
Example: " conduit pipelines init awesome-pipeline-name --source postgres --destination kafka --path pipelines/pg-to-kafka.yaml",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
pipelinesInitArgs.Name = args[0]
}
return NewPipelinesInit(pipelinesInitArgs).Run()
},
}

// Add flags to pipelines init command
pipelinesInitCmd.Flags().StringVar(
&pipelinesInitArgs.Source,
"source",
"",
"Source connector (any of the built-in connectors).",
)
pipelinesInitCmd.Flags().StringVar(
&pipelinesInitArgs.Destination,
"destination",
"",
"Destination connector (any of the built-in connectors).",
)
pipelinesInitCmd.Flags().StringVar(
&pipelinesInitArgs.Path,
"pipelines.path",
"./pipelines",
"Path where the pipeline will be saved.",
)

return pipelinesInitCmd
}
129 changes: 129 additions & 0 deletions cmd/cli/conduit_init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright © 2024 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 cli

import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/conduitio/conduit/cmd/cli/internal"
"github.com/conduitio/conduit/pkg/conduit"
"github.com/conduitio/conduit/pkg/foundation/cerrors"
"github.com/conduitio/yaml/v3"
)

type InitArgs struct {
Path string
}

type ConduitInit struct {
args InitArgs
}

func NewConduitInit(args InitArgs) *ConduitInit {
return &ConduitInit{args: args}
}

func (i *ConduitInit) Run() error {
err := i.createDirs()
if err != nil {
return err
}

err = i.createConfigYAML()
if err != nil {
return fmt.Errorf("failed to create config YAML: %w", err)
}

fmt.Println(`
Conduit has been initialized!

To quickly create an example pipeline, run 'conduit pipelines init'.
To see how you can customize your first pipeline, run 'conduit pipelines init --help'.`)

return nil
}

func (i *ConduitInit) createConfigYAML() error {
cfgYAML := internal.NewYAMLTree()
i.conduitCfgFlags().VisitAll(func(f *flag.Flag) {
if i.isHiddenFlag(f.Name) {
return // hide flag from output
}
cfgYAML.Insert(f.Name, f.DefValue, f.Usage)
})

yamlData, err := yaml.Marshal(cfgYAML.Root)
if err != nil {
return cerrors.Errorf("error marshaling YAML: %w\n", err)
}

path := filepath.Join(i.path(), "conduit.yaml")
err = os.WriteFile(path, yamlData, 0o600)
if err != nil {
return cerrors.Errorf("error writing conduit.yaml: %w", err)
}
fmt.Printf("Configuration file written to %v\n", path)

return nil
}

func (i *ConduitInit) createDirs() error {
dirs := []string{"processors", "connectors", "pipelines"}

for _, dir := range dirs {
path := filepath.Join(i.path(), dir)

// Attempt to create the directory, skipping if it already exists
if err := os.Mkdir(path, os.ModePerm); err != nil {
if os.IsExist(err) {
fmt.Printf("Directory '%s' already exists, skipping...\n", path)
continue
}
return fmt.Errorf("failed to create directory '%s': %w", path, err)
}

fmt.Printf("Created directory: %s\n", path)
}

return nil
}

func (i *ConduitInit) isHiddenFlag(name string) bool {
return name == "dev" ||
strings.HasPrefix(name, "dev.") ||
conduit.DeprecatedFlags[name]
}

func (i *ConduitInit) conduitCfgFlags() *flag.FlagSet {
cfg := conduit.DefaultConfigWithBasePath(i.path())
return conduit.Flags(&cfg)
}

func (i *ConduitInit) path() string {
if i.args.Path != "" {
return i.args.Path
}

path, err := os.Getwd()
if err != nil {
panic(cerrors.Errorf("failed to get current working directory: %w", err))
}

return path
}
85 changes: 85 additions & 0 deletions cmd/cli/internal/yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright © 2024 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 internal

import (
"strings"

"github.com/conduitio/yaml/v3"
)

// YAMLTree represents a YAML document.
// It makes it possible to insert value nodes with comments.
type YAMLTree struct {
Root *yaml.Node
}

func NewYAMLTree() *YAMLTree {
return &YAMLTree{
Root: &yaml.Node{
Kind: yaml.MappingNode,
},
}
}

// Insert adds a path with a value to the tree
func (t *YAMLTree) Insert(path, value, comment string) {
parts := strings.Split(path, ".")
current := t.Root

// For each part of the path
for i, part := range parts {
// Create key node
keyNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: part,
}

// Find or create value node
var valueNode *yaml.Node
found := false

// Look for existing key in current mapping
for i := 0; i < len(current.Content); i += 2 {
if current.Content[i].Value == part {
valueNode = current.Content[i+1]
found = true
break
}
}

// If not found, create new node
if !found {
// If this is the last part, create scalar value node
if i == len(parts)-1 {
valueNode = &yaml.Node{
Kind: yaml.ScalarNode,
Value: value,
}
keyNode.HeadComment = comment
} else {
// Otherwise create mapping node for nesting
valueNode = &yaml.Node{
Kind: yaml.MappingNode,
}
}
// Add key-value pair to current node's content
current.Content = append(current.Content, keyNode, valueNode)
}

// Move to next level
current = valueNode
}
}
30 changes: 30 additions & 0 deletions cmd/cli/pipeline.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: "2.2"
pipelines:
- id: example-pipeline
status: running
name: "{{ .Name }}"
connectors:
- id: example-source
type: source
plugin: "{{ .SourceSpec.Name }}"
{{ if gt (len .SourceSpec.Params) 0 -}}
settings:
{{- range $name, $param := .SourceSpec.Params }}
{{ formatParameterDescriptionYAML $param.Description }}
# Type: {{ $param.Type }}
# {{ formatParameterRequired $param }}
{{ $name }}: {{ formatParameterValueYAML $param.Default }}
{{- end }}
{{- end }}
- id: example-destination
type: destination
plugin: "{{ .DestinationSpec.Name }}"
{{ if gt (len .DestinationSpec.Params) 0 -}}
settings:
{{- range $name, $param := .DestinationSpec.Params }}
{{ formatParameterDescriptionYAML $param.Description }}
# Type: {{ $param.Type }}
# {{ formatParameterRequired $param }}
{{ $name }}: {{ formatParameterValueYAML $param.Default }}
{{- end }}
{{- end }}
Loading