| package config | |
| import ( | |
| "fmt" | |
| "io/ioutil" | |
| "log" | |
| "os" | |
| "sort" | |
| "strings" | |
| "gopkg.in/yaml.v2" | |
| "github.com/bazelbuild/continuous-integration/pipegen/proxy" | |
| ) | |
| type Config struct { | |
| Pipelines []Pipeline `yaml:"pipelines"` | |
| } | |
| type Pipeline struct { | |
| Name string `yaml:"name"` | |
| Slug string `yaml:"slug"` | |
| Description string `yaml:"description,omitempty"` | |
| Configuration string `yaml:"configuration,omitempty"` | |
| Public bool `yaml:"public"` | |
| Steps []Step `yaml:"steps,omitempty"` | |
| Teams []TeamAccess `yaml:"teams,omitempty"` | |
| Schedules []Schedule `yaml:"schedules,omitempty"` | |
| BuildSkipping *BuildSkipping `yaml:"build_skipping,omitempty"` | |
| Repository *Repository `yaml:"repository,omitempty"` | |
| Provider *Provider `yaml:"provider,omitempty"` | |
| } | |
| type Step struct { | |
| Name string `yaml:"name"` | |
| Type string `yaml:"type"` | |
| Command string `yaml:"command,omitempty"` | |
| Agents []string `yaml:"agents,omitempty"` | |
| Branches string `yaml:"branches,omitempty"` | |
| ArtifactPaths string `yaml:"artifact_paths,omitempty"` | |
| Timeout int `yaml:"timeout,omitempty"` | |
| Concurrency int `yaml:"concurrency,omitempty"` | |
| Parallelism int `yaml:"parallelism,omitempty"` | |
| EnvironmentVariables map[string]string `yaml:"env,omitempty"` | |
| } | |
| type BuildSkipping struct { | |
| SkipIntermediateBuilds bool `yaml:"skip_queued_branch_builds,omitempty"` | |
| SkipBranches string `yaml:"skip_queued_branch_builds_filter,omitempty"` | |
| CancelIntermediateBuilds bool `yaml:"cancel_running_branch_builds,omitempty"` | |
| CancelBranches string `yaml:"cancel_running_branch_builds_filter,omitempty"` | |
| } | |
| type Repository struct { | |
| RepositoryUrl string `yaml:"repository,omitempty"` | |
| DefaultBranch string `yaml:"default_branch,omitempty"` | |
| BranchLimiting string `yaml:"branch_limiting,omitempty"` | |
| } | |
| type TeamAccess struct { | |
| Slug string `yaml:"slug"` | |
| AccessLevel string `yaml:"access_level"` | |
| } | |
| type Schedule struct { | |
| CronInterval string `yaml:"cron_interval,omitempty"` | |
| Description string `yaml:"description,omitempty"` | |
| BuildMessage string `yaml:"build_message,omitempty"` | |
| Commit string `yaml:"commit,omitempty"` | |
| Branch string `yaml:"branch,omitempty"` | |
| EnvironmentVariables []string `yaml:"env,omitempty"` | |
| Enabled bool `yaml:"enabled"` | |
| } | |
| // TODO(fwe): Get url and name from GraphQL? | |
| type Provider struct { | |
| WebhookUrl string `yaml:"webhook_url"` | |
| Settings map[string]interface{} `yaml:"settings"` | |
| } | |
| func ReadConfig(path string) (*Config, error) { | |
| file, err := os.Open(path) | |
| if err != nil { | |
| return nil, fmt.Errorf("Failed to open config '%s': %v", path, err) | |
| } | |
| defer file.Close() | |
| content, err := ioutil.ReadAll(file) | |
| if err != nil { | |
| return nil, fmt.Errorf("Failed to read config '%s': %v", path, err) | |
| } | |
| c := Config{} | |
| err = yaml.Unmarshal(content, &c) | |
| if err != nil { | |
| return nil, fmt.Errorf("Config '%s' is not valid YAML: %v", path, err) | |
| } | |
| c.sortPipelines() | |
| return &c, nil | |
| } | |
| func ReadFromBuildkite(org, apiToken string, debug bool) (*Config, error) { | |
| p, err := proxy.CreateProxy(org, apiToken, debug) | |
| if err != nil { | |
| log.Fatalf("Cannot connect to Buildkite: %s", err) | |
| } | |
| allPipelines, err := p.GetPipelines() | |
| if err != nil { | |
| return nil, fmt.Errorf("Cannot retrieve pipelines: %s", err) | |
| } | |
| c := Config{make([]Pipeline, len(allPipelines))} | |
| for ip, pipeline := range allPipelines { | |
| c.Pipelines[ip] = *convertPipeline(pipeline) | |
| } | |
| c.sortPipelines() | |
| return &c, nil | |
| } | |
| func (config *Config) sortPipelines() { | |
| sort.Slice(config.Pipelines, func(i, j int) bool { return config.Pipelines[i].Slug < config.Pipelines[j].Slug }) | |
| } | |
| func convertPipeline(pipeline *proxy.Pipeline) *Pipeline { | |
| steps := make([]Step, len(pipeline.Steps)) | |
| for is, step := range pipeline.Steps { | |
| steps[is] = Step{Name: getStringValue(step.Name), | |
| Type: getStringValue(step.Type), | |
| Command: getStringValue(step.Command), | |
| Agents: step.AgentQueryRules, | |
| Branches: getStringValue(step.BranchConfiguration), | |
| ArtifactPaths: getStringValue(step.ArtifactPaths), | |
| Timeout: getIntValue(step.TimeoutInMinutes), | |
| Concurrency: getIntValue(step.Concurrency), | |
| Parallelism: getIntValue(step.Parallelism), | |
| EnvironmentVariables: step.Env, | |
| } | |
| } | |
| teams := make([]TeamAccess, len(pipeline.Details.Access)) | |
| for i, t := range pipeline.Details.Access { | |
| teams[i] = TeamAccess{ | |
| Slug: t.TeamSlug, | |
| AccessLevel: t.AccessLevel} | |
| } | |
| schedules := make([]Schedule, len(pipeline.Details.Schedules)) | |
| for i, s := range pipeline.Details.Schedules { | |
| schedules[i] = Schedule{ | |
| Description: s.Label, | |
| CronInterval: s.Cronline, | |
| BuildMessage: s.Message, | |
| Commit: s.Commit, | |
| Branch: s.Branch, | |
| EnvironmentVariables: s.Env, | |
| Enabled: s.Enabled} | |
| } | |
| repo := Repository{ | |
| RepositoryUrl: getStringValue(pipeline.Repository), | |
| DefaultBranch: getStringValue(pipeline.DefaultBranch), | |
| BranchLimiting: getStringValue(pipeline.BranchConfiguration)} | |
| provider := Provider{ | |
| WebhookUrl: getStringValue(pipeline.Provider.WebhookURL), | |
| Settings: pipeline.Provider.Settings} | |
| skip := BuildSkipping{ | |
| SkipIntermediateBuilds: pipeline.SkipQueuedBranchBuilds, | |
| SkipBranches: getStringValue(pipeline.SkipBueuedBranchBuildsFilter), | |
| CancelIntermediateBuilds: pipeline.CancelRunningBranchBuilds, | |
| CancelBranches: getStringValue(pipeline.CancelRunningBranchBuildsFilter)} | |
| return &Pipeline{Name: getStringValue(pipeline.Name), | |
| Slug: getStringValue(pipeline.Slug), | |
| Description: getStringValue(pipeline.Description), | |
| Public: pipeline.Details.Public, | |
| Steps: steps, | |
| Configuration: getStringValue(pipeline.Configuration), | |
| Teams: teams, | |
| Schedules: schedules, | |
| BuildSkipping: &skip, | |
| Repository: &repo, | |
| Provider: &provider} | |
| } | |
| func getStringValue(ptr *string) string { | |
| if ptr == nil { | |
| return "" | |
| } | |
| return *ptr | |
| } | |
| func getIntValue(ptr *int) int { | |
| if ptr == nil { | |
| return 0 | |
| } | |
| return *ptr | |
| } | |
| func (config *Config) Yaml(pipelines ...string) (string, error) { | |
| toExport, err := config.filterPipelines(pipelines) | |
| if err != nil { | |
| return "", fmt.Errorf("Cannot filter pipelines: %v", err) | |
| } | |
| s, err := yaml.Marshal(toExport) | |
| if err != nil { | |
| return "", fmt.Errorf("Cannot convert configuration to YAML: %v", err) | |
| } | |
| return string(s), nil | |
| } | |
| func (config *Config) filterPipelines(pipelines []string) ([]Pipeline, error) { | |
| if len(pipelines) == 0 { | |
| return config.Pipelines, nil | |
| } | |
| filter := make(map[string]bool) | |
| for _, p := range pipelines { | |
| filter[p] = false | |
| } | |
| result := make([]Pipeline, 0, len(pipelines)) | |
| for _, p := range config.Pipelines { | |
| if _, ok := filter[p.Slug]; ok { | |
| result = append(result, p) | |
| delete(filter, p.Slug) | |
| } | |
| } | |
| if len(filter) > 0 { | |
| unknown := make([]string, 0, len(filter)) | |
| for key, _ := range filter { | |
| unknown = append(unknown, key) | |
| } | |
| format := func(p []string) string { | |
| return strings.Join(p, "\n\t- ") | |
| } | |
| return nil, fmt.Errorf("Unknown pipelines:\n\t- %s.\nAvailable pipelines:\n\t- %s", format(unknown), format(config.getSlugs())) | |
| } | |
| return result, nil | |
| } | |
| func (config *Config) getSlugs() []string { | |
| return getSlugs(config.Pipelines) | |
| } | |
| func getSlugs(pipelines []Pipeline) []string { | |
| slugs := make([]string, len(pipelines)) | |
| for i, p := range pipelines { | |
| slugs[i] = p.Slug | |
| } | |
| return slugs | |
| } | |
| func (config *Config) String() string { | |
| return fmt.Sprintf("Found %d pipelines:\n\t- %s", len(config.Pipelines), strings.Join(config.getSlugs(), "\n\t- ")) | |
| } | |
| func (config *Config) Compare(other *Config) (string, error) { | |
| if other == nil { | |
| return "", fmt.Errorf("Cannot compare to a nil config.") | |
| } else if config == other { | |
| return "", nil | |
| } | |
| var c, o int | |
| var added, missing, same []Pipeline | |
| for c < len(config.Pipelines) && o < len(other.Pipelines) { | |
| configPipe := config.Pipelines[c] | |
| otherPipe := other.Pipelines[o] | |
| log.Print(configPipe) | |
| if configPipe.Slug < otherPipe.Slug { | |
| added = append(added, configPipe) | |
| c += 1 | |
| } else if configPipe.Slug == otherPipe.Slug { | |
| same = append(same, configPipe) | |
| c += 1 | |
| o += 1 | |
| } else { | |
| missing = append(missing, otherPipe) | |
| o += 1 | |
| } | |
| } | |
| formatter := func(pipelines []Pipeline) string { return strings.Join(getSlugs(pipelines), ", ") } | |
| // TODO(fweikert): actually print added, missing and same. | |
| result := fmt.Sprintf("Additional pipelines: %s\nMissing pipelines: %s\nSame pipelines: %s\n", formatter(added), formatter(missing), formatter(same)) | |
| return result, nil | |
| } |