diff --git a/cli/.gitignore b/cli/.gitignore index 16280b971..1feacc659 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -1,2 +1,3 @@ **/digger !/pkg/digger +!/cmd/digger diff --git a/cli/cmd/digger/default.go b/cli/cmd/digger/default.go new file mode 100644 index 000000000..7ff3a8d81 --- /dev/null +++ b/cli/cmd/digger/default.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "github.com/diggerhq/digger/cli/pkg/azure" + "github.com/diggerhq/digger/cli/pkg/digger" + "github.com/diggerhq/digger/cli/pkg/usage" + "github.com/spf13/cobra" + "log" + "os" + "runtime/debug" +) + +var defaultCmd = &cobra.Command{ + Use: "default", + Run: func(cmd *cobra.Command, args []string) { + var logLeader = "Unknown CI" + ci := digger.DetectCI() + + switch ci { + case digger.GitHub: + logLeader = os.Getenv("GITHUB_ACTOR") + gitHubCI(lock, PolicyChecker, BackendApi, ReportStrategy) + case digger.GitLab: + logLeader = os.Getenv("CI_PROJECT_NAME") + gitLabCI(lock, PolicyChecker, BackendApi, ReportStrategy) + case digger.Azure: + // This should be refactored in the future because in this way the parsing + // is done twice, both here and inside azureCI, a better solution might be + // to encapsulate it into a method on the azure package and then grab the + // value here and pass it into the azureCI call. + azureContext := os.Getenv("AZURE_CONTEXT") + parsedAzureContext, _ := azure.GetAzureReposContext(azureContext) + logLeader = parsedAzureContext.BaseUrl + azureCI(lock, PolicyChecker, BackendApi, ReportStrategy) + case digger.BitBucket: + logLeader = os.Getenv("BITBUCKET_STEP_TRIGGERER_UUID") + bitbucketCI(lock, PolicyChecker, BackendApi, ReportStrategy) + case digger.None: + print("No CI detected.") + os.Exit(10) + } + + defer func() { + if r := recover(); r != nil { + log.Println(fmt.Sprintf("stacktrace from panic: \n" + string(debug.Stack()))) + err := usage.SendLogRecord(logLeader, fmt.Sprintf("Panic occurred. %s", r)) + if err != nil { + log.Printf("Failed to send log record. %s\n", err) + } + os.Exit(1) + } + }() + }, +} + +func init() { + rootCmd.AddCommand(defaultCmd) +} diff --git a/cli/cmd/digger/plan.go b/cli/cmd/digger/plan.go new file mode 100644 index 000000000..5bc7b812f --- /dev/null +++ b/cli/cmd/digger/plan.go @@ -0,0 +1,83 @@ +package main + +import ( + core_backend "github.com/diggerhq/digger/cli/pkg/core/backend" + core_locking "github.com/diggerhq/digger/cli/pkg/core/locking" + core_policy "github.com/diggerhq/digger/cli/pkg/core/policy" + core_reporting "github.com/diggerhq/digger/cli/pkg/core/reporting" + github_pkg "github.com/diggerhq/digger/cli/pkg/github" + "github.com/diggerhq/digger/cli/pkg/reporting" + "github.com/diggerhq/digger/libs/orchestrator" + orchestrator_github "github.com/diggerhq/digger/libs/orchestrator/github" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "strings" +) + +var vip *viper.Viper + +func plan(actor string, projectName string, repoNamespace string, lock core_locking.Lock, policyChecker core_policy.Checker, reporter core_reporting.Reporter, prService orchestrator.PullRequestService, orgService orchestrator.OrgService, backendApi core_backend.Api) { + exec(actor, projectName, repoNamespace, "digger plan", lock, policyChecker, prService, orgService, reporter, backendApi) +} + +var planCmd = &cobra.Command{ + Use: "plan project_name [flags]", + Short: "Plan a project, if no project specified it will plan for all projects", + Long: `Plan a project, if no project specified it will plan for all projects`, + Run: func(cmd *cobra.Command, args []string) { + var runConfig RunConfig + vip.Unmarshal(&runConfig) + var prService orchestrator.PullRequestService + var orgService orchestrator.OrgService + var reporter core_reporting.Reporter + + switch runConfig.Reporter { + case "github": + splitRepositoryName := strings.Split(runConfig.RepoNamespace, "/") + repoOwner, repositoryName := splitRepositoryName[0], splitRepositoryName[1] + prService = orchestrator_github.NewGitHubService(runConfig.GithubToken, repositoryName, repoOwner) + orgService = orchestrator_github.NewGitHubService(runConfig.GithubToken, runConfig.RepoNamespace, runConfig.Actor) + reporter = &reporting.CiReporter{ + CiService: prService, + ReportStrategy: ReportStrategy, + PrNumber: runConfig.PRNumber, + IsSupportMarkdown: true, + } + case "stdout": + print("Using Stdout.") + reporter = &reporting.StdoutReporter{ + ReportStrategy: ReportStrategy, + IsSupportMarkdown: true, + } + prService = github_pkg.MockCiService{} + orgService = github_pkg.MockCiService{} + + } + + plan(runConfig.Actor, args[0], runConfig.RepoNamespace, lock, PolicyChecker, reporter, prService, orgService, BackendApi) + }, +} + +func init() { + flags := []pflag.Flag{ + {Name: "github-token", Usage: "The namespace of this repo"}, + {Name: "repo-namespace", Usage: "The namespace of this repo"}, + {Name: "actor", Usage: "The actor of this command"}, + {Name: "reporter", Usage: "The reporter to use (defaults to stdout)"}, + {Name: "pr-number", Usage: "The PR number for reporting"}, + {Name: "comment-id", Usage: "The PR comment for reporting"}, + } + + vip = viper.New() + vip.SetEnvPrefix("DIGGER") + vip.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + vip.AutomaticEnv() + + for _, flag := range flags { + planCmd.Flags().String(flag.Name, "", flag.Usage) + vip.BindPFlag(flag.Name, planCmd.Flags().Lookup(flag.Name)) + } + + rootCmd.AddCommand(planCmd) +} diff --git a/cli/cmd/digger/root.go b/cli/cmd/digger/root.go new file mode 100644 index 000000000..a07064ce2 --- /dev/null +++ b/cli/cmd/digger/root.go @@ -0,0 +1,83 @@ +package main + +import ( + "github.com/diggerhq/digger/cli/pkg/backend" + core_backend "github.com/diggerhq/digger/cli/pkg/core/backend" + core_locking "github.com/diggerhq/digger/cli/pkg/core/locking" + core_policy "github.com/diggerhq/digger/cli/pkg/core/policy" + "github.com/diggerhq/digger/cli/pkg/locking" + "github.com/diggerhq/digger/cli/pkg/policy" + "github.com/diggerhq/digger/cli/pkg/reporting" + "github.com/spf13/cobra" + "log" + "net/http" + "os" + "time" +) + +type RunConfig struct { + RepoNamespace string `mapstructure:"repo-namespace"` + Reporter string `mapstructure:"reporter"` + PRNumber int `mapstructure:"pr-number"` + CommentID string `mapstructure:"comment-id"` + Actor string `mapstructure:"actor"` + GithubToken string `mapstructure:"github-token"` +} + +var PolicyChecker core_policy.Checker +var BackendApi core_backend.Api +var ReportStrategy reporting.ReportStrategy +var lock core_locking.Lock + +func PreRun(cmd *cobra.Command, args []string) { + + if os.Getenv("NO_BACKEND") == "true" { + log.Println("WARNING: running in 'backendless' mode. Features that require backend will not be available.") + PolicyChecker = policy.NoOpPolicyChecker{} + BackendApi = backend.NoopApi{} + } else if os.Getenv("DIGGER_TOKEN") != "" { + if os.Getenv("DIGGER_ORGANISATION") == "" { + log.Fatalf("Token specified but missing organisation: DIGGER_ORGANISATION. Please set this value in action digger_config.") + } + PolicyChecker = policy.DiggerPolicyChecker{ + PolicyProvider: &policy.DiggerHttpPolicyProvider{ + DiggerHost: os.Getenv("DIGGER_HOSTNAME"), + DiggerOrganisation: os.Getenv("DIGGER_ORGANISATION"), + AuthToken: os.Getenv("DIGGER_TOKEN"), + HttpClient: http.DefaultClient, + }} + BackendApi = backend.DiggerApi{ + DiggerHost: os.Getenv("DIGGER_HOSTNAME"), + AuthToken: os.Getenv("DIGGER_TOKEN"), + HttpClient: http.DefaultClient, + } + } else { + reportErrorAndExit("", "DIGGER_TOKEN not specified. You can get one at https://cloud.digger.dev, or self-manage a backend of Digger Community Edition (change DIGGER_HOSTNAME). You can also pass 'no-backend: true' option; in this case some of the features may not be available.", 1) + } + + if os.Getenv("REPORTING_STRATEGY") == "comments_per_run" || os.Getenv("ACCUMULATE_PLANS") == "true" { + ReportStrategy = &reporting.CommentPerRunStrategy{ + TimeOfRun: time.Now(), + } + } else if os.Getenv("REPORTING_STRATEGY") == "latest_run_comment" { + ReportStrategy = &reporting.LatestRunCommentStrategy{ + TimeOfRun: time.Now(), + } + } else { + ReportStrategy = &reporting.MultipleCommentsStrategy{} + } + + var err error + lock, err = locking.GetLock() + if err != nil { + log.Printf("Failed to create lock provider. %s\n", err) + os.Exit(2) + } + log.Println("Lock provider has been created successfully") +} + +var rootCmd = &cobra.Command{ + Use: "digger", + Short: "An open source IaC orchestration tool", + PersistentPreRun: PreRun, +}