Skip to content

Commit 79fb4fd

Browse files
authored
cli: make main command easier to call. (#71)
1 parent 86f8b0c commit 79fb4fd

File tree

9 files changed

+158
-144
lines changed

9 files changed

+158
-144
lines changed

cmd/conduit/main.go

Lines changed: 4 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,21 @@
11
package main
22

33
import (
4-
"context"
5-
_ "embed"
64
"fmt"
75
"os"
8-
"strings"
96

10-
log "github.com/sirupsen/logrus"
11-
"github.com/spf13/cobra"
127
"github.com/spf13/cobra/doc"
138

14-
"github.com/algorand/conduit/cmd/conduit/internal/initialize"
15-
"github.com/algorand/conduit/cmd/conduit/internal/list"
16-
"github.com/algorand/conduit/conduit/data"
17-
"github.com/algorand/conduit/conduit/loggers"
18-
"github.com/algorand/conduit/conduit/pipeline"
9+
"github.com/algorand/conduit/pkg/cli"
10+
1911
_ "github.com/algorand/conduit/conduit/plugins/exporters/all"
2012
_ "github.com/algorand/conduit/conduit/plugins/importers/all"
2113
_ "github.com/algorand/conduit/conduit/plugins/processors/all"
22-
"github.com/algorand/conduit/version"
23-
)
24-
25-
var (
26-
logger *log.Logger
27-
conduitCmd = makeConduitCmd()
28-
//go:embed banner.txt
29-
banner string
30-
)
31-
32-
const (
33-
conduitEnvVar = "CONDUIT_DATA_DIR"
3414
)
3515

36-
// init() function for main package
37-
func init() {
38-
conduitCmd.AddCommand(initialize.InitCommand)
39-
conduitCmd.AddCommand(list.Command)
40-
}
41-
42-
// runConduitCmdWithConfig run the main logic with a supplied conduit config
43-
func runConduitCmdWithConfig(args *data.Args) error {
44-
defer pipeline.HandlePanic(logger)
45-
46-
if args.ConduitDataDir == "" {
47-
args.ConduitDataDir = os.Getenv(conduitEnvVar)
48-
}
49-
50-
if args.ConduitDataDir == "" {
51-
return fmt.Errorf("the data directory is required and must be provided with a command line option or the '%s' environment variable", conduitEnvVar)
52-
}
53-
54-
pCfg, err := data.MakePipelineConfig(args)
55-
if err != nil {
56-
return err
57-
}
58-
59-
// Initialize logger
60-
level, err := log.ParseLevel(pCfg.LogLevel)
61-
if err != nil {
62-
var levels []string
63-
for _, l := range log.AllLevels {
64-
levels = append(levels, l.String())
65-
}
66-
return fmt.Errorf("invalid configuration: '%s' is not a valid log level, valid levels: %s", pCfg.LogLevel, strings.Join(levels, ", "))
67-
}
68-
69-
logger, err = loggers.MakeThreadSafeLogger(level, pCfg.LogFile)
70-
if err != nil {
71-
return fmt.Errorf("failed to create logger: %w", err)
72-
}
73-
74-
logger.Infof("Starting Conduit %s", version.LongVersion())
75-
logger.Infof("Using data directory: %s", args.ConduitDataDir)
76-
logger.Info("Conduit configuration is valid")
77-
78-
if !pCfg.HideBanner {
79-
fmt.Print(banner)
80-
}
81-
82-
if pCfg.LogFile != "" {
83-
fmt.Printf("Writing logs to file: %s\n", pCfg.LogFile)
84-
} else {
85-
fmt.Println("Writing logs to console.")
86-
}
87-
88-
ctx := context.Background()
89-
pipeline, err := pipeline.MakePipeline(ctx, pCfg, logger)
90-
if err != nil {
91-
err = fmt.Errorf("pipeline creation error: %w", err)
92-
93-
// Suppress log, it is about to be printed to stderr.
94-
if pCfg.LogFile != "" {
95-
logger.Error(err)
96-
}
97-
return err
98-
}
99-
100-
err = pipeline.Init()
101-
if err != nil {
102-
// Suppress log, it is about to be printed to stderr.
103-
if pCfg.LogFile != "" {
104-
logger.Error(err)
105-
}
106-
return fmt.Errorf("pipeline init error: %w", err)
107-
}
108-
pipeline.Start()
109-
defer pipeline.Stop()
110-
pipeline.Wait()
111-
return pipeline.Error()
112-
}
113-
114-
// makeConduitCmd creates the main cobra command, initializes flags
115-
func makeConduitCmd() *cobra.Command {
116-
cfg := &data.Args{}
117-
var vFlag bool
118-
cmd := &cobra.Command{
119-
Use: "conduit",
120-
Short: "Run the Conduit framework.",
121-
Long: `Conduit is a framework for ingesting blocks from the Algorand blockchain
122-
into external applications. It is designed as a modular plugin system that
123-
allows users to configure their own data pipelines.
124-
125-
You must provide a data directory containing a file named conduit.yml. The
126-
file configures pipeline and all enabled plugins.
127-
128-
See other subcommands for further built in utilities and information.
129-
130-
Detailed documentation is online: https://github.com/algorand/conduit`,
131-
Args: cobra.NoArgs,
132-
Run: func(cmd *cobra.Command, args []string) {
133-
err := runConduitCmdWithConfig(cfg)
134-
if err != nil {
135-
fmt.Fprintf(os.Stderr, "\nExiting with error:\n%s.\n", err)
136-
os.Exit(1)
137-
}
138-
},
139-
PersistentPreRun: func(cmd *cobra.Command, args []string) {
140-
if vFlag {
141-
fmt.Printf("%s\n", version.LongVersion())
142-
os.Exit(0)
143-
}
144-
},
145-
}
146-
cmd.Flags().StringVarP(&cfg.ConduitDataDir, "data-dir", "d", "", "Set the Conduit data directory. If not set the CONDUIT_DATA_DIR environment variable is used.")
147-
cmd.Flags().Uint64VarP(&cfg.NextRoundOverride, "next-round-override", "r", 0, "Set the starting round. Overrides next-round in metadata.json. Some exporters do not support overriding the starting round.")
148-
cmd.Flags().BoolVarP(&vFlag, "version", "v", false, "Print the Conduit version.")
149-
// No need for shell completions.
150-
cmd.CompletionOptions.DisableDefaultCmd = true
151-
152-
return cmd
153-
}
154-
15516
func main() {
17+
conduitCmd := cli.MakeConduitCmdWithUtilities()
18+
15619
// Hidden command to generate docs in a given directory
15720
// conduit generate-docs [path]
15821
if len(os.Args) == 3 && os.Args[1] == "generate-docs" {

pkg/cli/cli.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
_ "embed"
6+
"fmt"
7+
"os"
8+
"strings"
9+
10+
log "github.com/sirupsen/logrus"
11+
"github.com/spf13/cobra"
12+
13+
"github.com/algorand/conduit/conduit/data"
14+
"github.com/algorand/conduit/conduit/loggers"
15+
"github.com/algorand/conduit/conduit/pipeline"
16+
"github.com/algorand/conduit/pkg/cli/internal/initialize"
17+
"github.com/algorand/conduit/pkg/cli/internal/list"
18+
"github.com/algorand/conduit/version"
19+
)
20+
21+
var (
22+
logger *log.Logger
23+
conduitCmd = MakeConduitCmd()
24+
25+
//go:embed banner.txt
26+
Banner string
27+
)
28+
29+
const (
30+
conduitEnvVar = "CONDUIT_DATA_DIR"
31+
)
32+
33+
// runConduitCmdWithConfig run the main logic with a supplied conduit config
34+
func runConduitCmdWithConfig(args *data.Args) error {
35+
defer pipeline.HandlePanic(logger)
36+
37+
if args.ConduitDataDir == "" {
38+
args.ConduitDataDir = os.Getenv(conduitEnvVar)
39+
}
40+
41+
if args.ConduitDataDir == "" {
42+
return fmt.Errorf("the data directory is required and must be provided with a command line option or the '%s' environment variable", conduitEnvVar)
43+
}
44+
45+
pCfg, err := data.MakePipelineConfig(args)
46+
if err != nil {
47+
return err
48+
}
49+
50+
// Initialize logger
51+
level, err := log.ParseLevel(pCfg.LogLevel)
52+
if err != nil {
53+
var levels []string
54+
for _, l := range log.AllLevels {
55+
levels = append(levels, l.String())
56+
}
57+
return fmt.Errorf("invalid configuration: '%s' is not a valid log level, valid levels: %s", pCfg.LogLevel, strings.Join(levels, ", "))
58+
}
59+
60+
logger, err = loggers.MakeThreadSafeLogger(level, pCfg.LogFile)
61+
if err != nil {
62+
return fmt.Errorf("failed to create logger: %w", err)
63+
}
64+
65+
logger.Infof("Starting Conduit %s", version.LongVersion())
66+
logger.Infof("Using data directory: %s", args.ConduitDataDir)
67+
logger.Info("Conduit configuration is valid")
68+
69+
if !pCfg.HideBanner {
70+
fmt.Print(Banner)
71+
}
72+
73+
if pCfg.LogFile != "" {
74+
fmt.Printf("Writing logs to file: %s\n", pCfg.LogFile)
75+
} else {
76+
fmt.Println("Writing logs to console.")
77+
}
78+
79+
ctx := context.Background()
80+
pipeline, err := pipeline.MakePipeline(ctx, pCfg, logger)
81+
if err != nil {
82+
err = fmt.Errorf("pipeline creation error: %w", err)
83+
84+
// Suppress log, it is about to be printed to stderr.
85+
if pCfg.LogFile != "" {
86+
logger.Error(err)
87+
}
88+
return err
89+
}
90+
91+
err = pipeline.Init()
92+
if err != nil {
93+
// Suppress log, it is about to be printed to stderr.
94+
if pCfg.LogFile != "" {
95+
logger.Error(err)
96+
}
97+
return fmt.Errorf("pipeline init error: %w", err)
98+
}
99+
pipeline.Start()
100+
defer pipeline.Stop()
101+
pipeline.Wait()
102+
return pipeline.Error()
103+
}
104+
105+
func MakeConduitCmdWithUtilities() *cobra.Command {
106+
cmd := MakeConduitCmd()
107+
cmd.AddCommand(initialize.InitCommand)
108+
cmd.AddCommand(list.Command)
109+
return cmd
110+
}
111+
112+
// MakeConduitCmd creates the main cobra command, initializes flags
113+
func MakeConduitCmd() *cobra.Command {
114+
cfg := &data.Args{}
115+
var vFlag bool
116+
cmd := &cobra.Command{
117+
Use: "conduit",
118+
Short: "Run the Conduit framework.",
119+
Long: `Conduit is a framework for ingesting blocks from the Algorand blockchain
120+
into external applications. It is designed as a modular plugin system that
121+
allows users to configure their own data pipelines.
122+
123+
You must provide a data directory containing a file named conduit.yml. The
124+
file configures pipeline and all enabled plugins.
125+
126+
See other subcommands for further built in utilities and information.
127+
128+
Detailed documentation is online: https://github.com/algorand/conduit`,
129+
Args: cobra.NoArgs,
130+
Run: func(cmd *cobra.Command, args []string) {
131+
err := runConduitCmdWithConfig(cfg)
132+
if err != nil {
133+
fmt.Fprintf(os.Stderr, "\nExiting with error:\n%s.\n", err)
134+
os.Exit(1)
135+
}
136+
},
137+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
138+
if vFlag {
139+
fmt.Printf("%s\n", version.LongVersion())
140+
os.Exit(0)
141+
}
142+
},
143+
}
144+
cmd.Flags().StringVarP(&cfg.ConduitDataDir, "data-dir", "d", "", "Set the Conduit data directory. If not set the CONDUIT_DATA_DIR environment variable is used.")
145+
cmd.Flags().Uint64VarP(&cfg.NextRoundOverride, "next-round-override", "r", 0, "Set the starting round. Overrides next-round in metadata.json. Some exporters do not support overriding the starting round.")
146+
cmd.Flags().BoolVarP(&vFlag, "version", "v", false, "Print the Conduit version.")
147+
// No need for shell completions.
148+
cmd.CompletionOptions.DisableDefaultCmd = true
149+
150+
return cmd
151+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package cli
22

33
import (
44
_ "embed"
@@ -52,9 +52,9 @@ func TestBanner(t *testing.T) {
5252
require.NoError(t, err)
5353

5454
if hideBanner {
55-
assert.NotContains(t, string(data), banner)
55+
assert.NotContains(t, string(data), Banner)
5656
} else {
57-
assert.Contains(t, string(data), banner)
57+
assert.Contains(t, string(data), Banner)
5858
}
5959
}
6060

cmd/conduit/internal/initialize/conduit.test.init.default.yml renamed to pkg/cli/internal/initialize/conduit.test.init.default.yml

File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)