diff --git a/cmd/agola/cmd/agola.go b/cmd/agola/cmd/agola.go new file mode 100644 index 0000000..6d1b01d --- /dev/null +++ b/cmd/agola/cmd/agola.go @@ -0,0 +1,76 @@ +// Copyright 2019 Sorint.lab +// +// 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 cmd + +import ( + "net/url" + + "github.com/sorintlab/agola/cmd" + slog "github.com/sorintlab/agola/internal/log" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var level = zap.NewAtomicLevelAt(zapcore.InfoLevel) +var logger = slog.New(level) +var log = logger.Sugar() + +var cmdAgola = &cobra.Command{ + Use: "agola", + Short: "agola", + Version: cmd.Version, + // just defined to make --version work + PersistentPreRun: func(c *cobra.Command, args []string) { + if err := parseGatewayURL(); err != nil { + log.Fatalf("err: %v", err) + } + + if agolaOpts.debug { + level.SetLevel(zapcore.DebugLevel) + } + }, + Run: func(c *cobra.Command, args []string) { c.Help() }, +} + +type agolaOptions struct { + gatewayURL string + debug bool +} + +var agolaOpts agolaOptions + +func parseGatewayURL() error { + if agolaOpts.gatewayURL != "" { + gatewayURL = agolaOpts.gatewayURL + } + if _, err := url.Parse(gatewayURL); err != nil { + return errors.Errorf("cannot parse exposed gateway URL %q: %v", gatewayURL, err) + } + return nil +} + +func init() { + flags := cmdAgola.PersistentFlags() + + flags.StringVarP(&agolaOpts.gatewayURL, "gateway-url", "u", gatewayURL, "agola gateway exposed url") + flags.BoolVarP(&agolaOpts.debug, "debug", "d", false, "debug") +} + +func Execute() { + cmdAgola.Execute() +} diff --git a/cmd/agola/cmd/serve.go b/cmd/agola/cmd/serve.go new file mode 100644 index 0000000..4f960d5 --- /dev/null +++ b/cmd/agola/cmd/serve.go @@ -0,0 +1,124 @@ +// Copyright 2019 Sorint.lab +// +// 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 cmd + +import ( + "context" + "fmt" + + "github.com/sorintlab/agola/cmd" + "github.com/sorintlab/agola/internal/services/config" + "github.com/sorintlab/agola/internal/services/runservice/executor" + rsscheduler "github.com/sorintlab/agola/internal/services/runservice/scheduler" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "go.etcd.io/etcd/embed" +) + +var ( + // default gatewayURL + gatewayURL = fmt.Sprintf("http://%s:%d", "localhost", 8000) +) + +var CmdServe = &cobra.Command{ + Use: "serve", + Short: "serve", + Version: cmd.Version, + Run: func(cmd *cobra.Command, args []string) { + if err := serve(cmd, args); err != nil { + log.Fatalf("err: %v", err) + } + }, +} + +type serveOptions struct { + config string + embeddedEtcd bool + embeddedEtcdDataDir string +} + +var serveOpts serveOptions + +func init() { + flags := CmdServe.PersistentFlags() + + flags.StringVar(&serveOpts.config, "config", "", "config file path") + flags.BoolVar(&serveOpts.embeddedEtcd, "embedded-etcd", false, "start and use an embedded etcd, only for testing purpose") + flags.StringVar(&serveOpts.embeddedEtcdDataDir, "embedded-etcd-data-dir", "/tmp/agola/etcd", "embedded etcd data dir, only for testing purpose") + + cmdAgola.MarkFlagRequired("config") + + cmdAgola.AddCommand(CmdServe) +} + +func embeddedEtcd(ctx context.Context) error { + cfg := embed.NewConfig() + cfg.Dir = serveOpts.embeddedEtcdDataDir + cfg.Logger = "zap" + cfg.LogOutputs = []string{"stderr"} + + log.Infof("starting embedded etcd server") + e, err := embed.StartEtcd(cfg) + if err != nil { + return err + } + + go func() { + select { + case <-e.Server.ReadyNotify(): + log.Infof("embedded etcd server is ready") + } + select { + case <-ctx.Done(): + log.Infof("stopping embedded etcd server") + e.Close() + } + }() + + return nil +} + +func serve(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + c, err := config.Parse(serveOpts.config) + if err != nil { + return errors.Wrapf(err, "cannot parse config") + } + + if serveOpts.embeddedEtcd { + if err := embeddedEtcd(ctx); err != nil { + return errors.Wrapf(err, "failed to start run service scheduler") + } + } + + rssched1, err := rsscheduler.NewScheduler(ctx, &c.RunServiceScheduler) + if err != nil { + return errors.Wrapf(err, "failed to start run service scheduler") + } + + rsex1, err := executor.NewExecutor(&c.RunServiceExecutor) + if err != nil { + return errors.Wrapf(err, "failed to start run service executor") + } + + errCh := make(chan error) + + go func() { errCh <- rsex1.Run(ctx) }() + go func() { errCh <- rssched1.Run(ctx) }() + + return <-errCh +} diff --git a/cmd/agola/main.go b/cmd/agola/main.go new file mode 100644 index 0000000..f9c6448 --- /dev/null +++ b/cmd/agola/main.go @@ -0,0 +1,23 @@ +// Copyright 2019 Sorint.lab +// +// 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 main + +import ( + "github.com/sorintlab/agola/cmd/agola/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/internal/config/config.go b/internal/config/config.go index c7c8761..9d5017a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -134,7 +134,6 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal(&tt); err != nil { return err } - log.Debugf("tt: %s", util.Dump(tt)) var st tasksteps if err := unmarshal(&st); err != nil { @@ -147,12 +146,10 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { return errors.Errorf("wrong steps description at index %d: more than one step name per list entry", i) } for stepType, stepSpec := range stepEntry { - log.Debugf("s: %s", util.Dump(stepSpec)) o, err := yaml.Marshal(stepSpec) if err != nil { return err } - log.Debugf("o: %s", o) switch stepType { case "clone": var cs CloneStep @@ -190,14 +187,11 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { default: return errors.Errorf("unknown step type: %s", stepType) } - log.Debugf("s: %s", util.Dump(steps[i])) } } - log.Debugf("steps: %s", util.Dump(steps)) t.Steps = steps - log.Debugf("t: %s", util.Dump(t)) return nil } @@ -213,7 +207,6 @@ func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal(&te); err != nil { return err } - log.Debugf("te: %s", util.Dump(te)) e.Name = te.Name e.Task = te.Task @@ -222,7 +215,6 @@ func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error { depends := make([]*Depend, len(te.Depends)) for i, dependEntry := range te.Depends { var depend *Depend - log.Debugf("dependEntry: %v", util.Dump(dependEntry)) switch dependEntry.(type) { case string: depend = &Depend{ @@ -238,7 +230,6 @@ func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := yaml.Unmarshal(o, &dl); err != nil { return err } - log.Debugf("dl: %v", util.Dump(dl)) if len(dl) != 1 { return errors.Errorf("unsupported depend format. Must be a string or a list") } @@ -254,11 +245,9 @@ func (e *Element) UnmarshalYAML(unmarshal func(interface{}) error) error { } depends[i] = depend } - log.Debugf("depends: %s", util.Dump(depends)) e.Depends = depends - log.Debugf("e: %s", util.Dump(e)) return nil } @@ -336,8 +325,6 @@ func ParseConfig(configData []byte) (*Config, error) { } func checkConfig(config *Config) error { - log.Debugf("config: %s", util.Dump(config)) - // check broken dependencies for _, pipeline := range config.Pipelines { // collect all task names @@ -419,7 +406,6 @@ func checkConfig(config *Config) error { return errors.Errorf("runtime %q needed by task %q doesn't exist", t.Runtime, t.Name) } for i, s := range t.Steps { - log.Debugf("s: %s", util.Dump(s)) switch step := s.(type) { // TODO(sgotti) we could use the run step command as step name but when the // command is very long or multi line it doesn't makes sense and will