diff --git a/mage/goreleaser/build/doc.go b/mage/goreleaser/build/doc.go new file mode 100644 index 0000000..e7101f7 --- /dev/null +++ b/mage/goreleaser/build/doc.go @@ -0,0 +1,2 @@ +// Package build provides a simple interface to the goreleaser build. +package build diff --git a/mage/goreleaser/build/mage.go b/mage/goreleaser/build/mage.go new file mode 100644 index 0000000..0b4b122 --- /dev/null +++ b/mage/goreleaser/build/mage.go @@ -0,0 +1,181 @@ +package build + +import ( + "context" + "fmt" + "os" + "strconv" + "time" + + "github.com/magefile/mage/mg" + + "github.com/mesosphere/daggers/mage/goreleaser/cli" +) + +const ( + goreleaserConfigEnvVar = "GORELEASER_BUILD_CONFIG" + goreleaserIDEnvVar = "GORELEASER_BUILD_ID" + goreleaserOutputEnvVar = "GORELEASER_BUILD_OUTPUT" + goreleaserParallelismEnvVar = "GORELEASER_BUILD_PARALLELISM" + goreleaserRmDistEnvVar = "GORELEASER_BUILD_RM_DIST" + goreleaserSingleTargetEnvVar = "GORELEASER_BUILD_SINGLE_TARGET" + goreleaserSkipPostCommitHooksEnvVar = "GORELEASER_BUILD_SKIP_POST_COMMIT_HOOKS" + goreleaserSkipValidateEnvVar = "GORELEASER_BUILD_SKIP_VALIDATE" + goreleaserSnapshotEnvVar = "GORELEASER_BUILD_SNAPSHOT" + goreleaserTimeoutEnvVar = "GORELEASER_BUILD_TIMEOUT" +) + +// Build runs goreleaser build with --rm-dist and --single-target flags. +func Build(_ context.Context) error { + result, err := BuildWithOptions( + WithRmDist(true), + WithSingleTarget(true), + ) + if err != nil { + return err + } + + fmt.Printf( + "Build is successful for project: %s and version: %s\n", + result.Metadata.ProjectName, + result.Metadata.Version, + ) + + return nil +} + +// BuildSnapshot runs goreleaser build with --snapshot, --rm-dist and --single-target flags. +// +//nolint:revive // Disable stuttering check. +func BuildSnapshot(_ context.Context) error { + result, err := BuildWithOptions( + WithRmDist(true), + WithSingleTarget(true), + WithSnapshot(true), + ) + if err != nil { + return err + } + + fmt.Printf( + "Build snapshot is successful for project: %s and version: %s\n", + result.Metadata.ProjectName, + result.Metadata.Version, + ) + + return nil +} + +// BuildWithOptions runs goreleaser build with specific options. +// +//nolint:revive // Disable stuttering check. +func BuildWithOptions(opts ...Option) (*cli.Result, error) { + debug := mg.Debug() || mg.Verbose() + + envOpts, err := loadOptionsFromEnv() + if err != nil { + return nil, err + } + + // Combine options from environment variables and options passed to this function. Environment variables + // take precedence to allow overriding from the arguments passed to this function. + var combinedOpts []Option + + combinedOpts = append(combinedOpts, envOpts...) + combinedOpts = append(combinedOpts, opts...) + + options := config{} + for _, opt := range combinedOpts { + options = opt(options) + } + + return cli.Run(cli.CommandBuild, debug, options.env, options.toArgs()) +} + +func loadOptionsFromEnv() ([]Option, error) { + var opts []Option + + opts = append(stringOptFromEnvVar(goreleaserConfigEnvVar, WithConfig), opts...) + opts = append(stringOptFromEnvVar(goreleaserIDEnvVar, WithID), opts...) + opts = append(stringOptFromEnvVar(goreleaserOutputEnvVar, WithOutput), opts...) + opts = append(stringOptFromEnvVar(goreleaserParallelismEnvVar, WithParallelism), opts...) + + boolOpts, err := boolOptFromEnvVar(goreleaserRmDistEnvVar, WithRmDist) + if err != nil { + return nil, err + } + opts = append(boolOpts, opts...) + + boolOpts, err = boolOptFromEnvVar(goreleaserSingleTargetEnvVar, WithSingleTarget) + if err != nil { + return nil, err + } + opts = append(boolOpts, opts...) + + boolOpts, err = boolOptFromEnvVar(goreleaserSkipPostCommitHooksEnvVar, SkipPostCommitHooks) + if err != nil { + return nil, err + } + opts = append(boolOpts, opts...) + + boolOpts, err = boolOptFromEnvVar(goreleaserSkipValidateEnvVar, SkipValidate) + if err != nil { + return nil, err + } + opts = append(boolOpts, opts...) + + boolOpts, err = boolOptFromEnvVar(goreleaserSnapshotEnvVar, WithSnapshot) + if err != nil { + return nil, err + } + opts = append(boolOpts, opts...) + + durationOpts, err := durationOptFromEnvVar(goreleaserTimeoutEnvVar, WithTimeout) + if err != nil { + return nil, err + } + opts = append(durationOpts, opts...) + + return opts, nil +} + +// stringOptFromEnvVar returns a slice containing a single option if the specified environment variable is set. +// This function returns a slice to make it easier to chain calls in optsFromEnvVars. +// Returns a nil slice if the env var is not set. +func stringOptFromEnvVar(envVarName string, optFn func(string) Option) []Option { + if envVarValue, ok := os.LookupEnv(envVarName); ok { + return []Option{optFn(envVarValue)} + } + + return nil +} + +// boolOptFromEnvVar returns a slice containing a single option if the specified environment variable is set. +// This function returns a slice to make it easier to chain calls in optsFromEnvVars. +// Returns a nil slice if the env var is not set. +func boolOptFromEnvVar(envVarName string, optFn func(bool) Option) ([]Option, error) { + if envVarValue, ok := os.LookupEnv(envVarName); ok { + boolVal, err := strconv.ParseBool(envVarValue) + if err != nil { + return nil, fmt.Errorf("failed to parse %q as a boolean: %w", envVarName, err) + } + return []Option{optFn(boolVal)}, nil + } + + return nil, nil +} + +// durationOptFromEnvVar returns a slice containing a single option if the specified environment variable is set. +// This function returns a slice to make it easier to chain calls in optsFromEnvVars. +// Returns a nil slice if the env var is not set. +func durationOptFromEnvVar(envVarName string, optFn func(duration time.Duration) Option) ([]Option, error) { + if envVarValue, ok := os.LookupEnv(envVarName); ok { + durationVal, err := time.ParseDuration(envVarValue) + if err != nil { + return nil, fmt.Errorf("failed to parse %q as a duration: %w", envVarName, err) + } + return []Option{optFn(durationVal)}, nil + } + + return nil, nil +} diff --git a/mage/goreleaser/build/option.go b/mage/goreleaser/build/option.go new file mode 100644 index 0000000..09987fa --- /dev/null +++ b/mage/goreleaser/build/option.go @@ -0,0 +1,145 @@ +package build + +import ( + "time" +) + +type config struct { + env map[string]string + config string + id string + output string + parallelism string + rmDist bool + singleTarget bool + skipPostCommitHooks bool + skipValidate bool + snapshot bool + timeout string +} + +// Option is a function that configures the goreleaser build options. +type Option func(config config) config + +// WithEnv append extra env variables to goreleaser build process. +func WithEnv(env map[string]string) Option { + return func(config config) config { + config.env = env + return config + } +} + +// WithConfig sets --config flag. +func WithConfig(configPath string) Option { + return func(config config) config { + config.config = configPath + return config + } +} + +// WithID sets --id flag. +func WithID(id string) Option { + return func(config config) config { + config.id = id + return config + } +} + +// WithOutput sets --output. +func WithOutput(output string) Option { + return func(config config) config { + config.output = output + return config + } +} + +// WithParallelism sets --parallelism. +func WithParallelism(parallelism string) Option { + return func(config config) config { + config.parallelism = parallelism + return config + } +} + +// WithRmDist sets --rm-dist. +func WithRmDist(rmDist bool) Option { + return func(config config) config { + config.rmDist = rmDist + return config + } +} + +// WithSingleTarget sets --single-target. +func WithSingleTarget(singleTarget bool) Option { + return func(config config) config { + config.singleTarget = singleTarget + return config + } +} + +// SkipPostCommitHooks sets--skip-post-hooks. +func SkipPostCommitHooks(skipPostCommitHooks bool) Option { + return func(config config) config { + config.skipPostCommitHooks = skipPostCommitHooks + return config + } +} + +// SkipValidate sets --skip-validate. +func SkipValidate(skipValidate bool) Option { + return func(config config) config { + config.skipValidate = skipValidate + return config + } +} + +// WithSnapshot sets --snapshot. +func WithSnapshot(snapshot bool) Option { + return func(config config) config { + config.snapshot = snapshot + return config + } +} + +// WithTimeout sets --timeout duration. +func WithTimeout(timeout time.Duration) Option { + return func(config config) config { + config.timeout = timeout.String() + return config + } +} + +func (c *config) toArgs() []string { + var args []string + + args = appendNonEmptyStringVal(args, "--config", c.config) + args = appendNonEmptyStringVal(args, "--id", c.id) + args = appendNonEmptyStringVal(args, "--output", c.output) + args = appendNonEmptyStringVal(args, "--parallelism", c.parallelism) + args = appendBoolVal(args, "--rm-dist", c.rmDist) + args = appendBoolVal(args, "--single-target", c.singleTarget) + args = appendBoolVal(args, "--skip-post-hooks", c.skipPostCommitHooks) + args = appendBoolVal(args, "--skip-validate", c.skipValidate) + args = appendBoolVal(args, "--snapshot", c.snapshot) + args = appendNonEmptyStringVal(args, "--timeout", c.timeout) + + return args +} + +func appendNonEmptyStringVal(slice []string, flag, val string) []string { + // if val is empty return slice as it's + if val == "" { + return slice + } + + return append(slice, flag, val) +} + +func appendBoolVal(slice []string, flag string, val bool) []string { + // if val is false, no need to append the flag + if !val { + return slice + } + + return append(slice, flag) +} diff --git a/mage/goreleaser/cli/cli.go b/mage/goreleaser/cli/cli.go new file mode 100644 index 0000000..8668f7b --- /dev/null +++ b/mage/goreleaser/cli/cli.go @@ -0,0 +1,103 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + "time" + + "github.com/magefile/mage/sh" +) + +// Command a simple wrapper type for goreleaser commands to execute. +type Command string + +const ( + // CommandBuild is a custom type for `build` command. + CommandBuild Command = "build" + + // CommandRelease is a custom type for `release` command. + CommandRelease Command = "release" +) + +// Artifact is a goreleaser type defines a single artifact. +// Copied from https://github.com/goreleaser/goreleaser/blob/main/internal/artifact/artifact.go#L159 +type Artifact struct { + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` + Goos string `json:"goos,omitempty"` + Goarch string `json:"goarch,omitempty"` + Goarm string `json:"goarm,omitempty"` + Gomips string `json:"gomips,omitempty"` + Goamd64 string `json:"goamd64,omitempty"` + Type string `json:"type,omitempty"` + Extra map[string]any `json:"extra,omitempty"` +} + +// Metadata is a goreleaser type defines metadata.json +// Copied from https://github.com/goreleaser/goreleaser/blob/main/internal/pipe/metadata/metadata.go#L62 +type Metadata struct { + ProjectName string `json:"project_name"` + Tag string `json:"tag"` + PreviousTag string `json:"previous_tag"` + Version string `json:"version"` + Commit string `json:"commit"` + Date time.Time `json:"date"` + Runtime MetaRuntime `json:"runtime"` +} + +// MetaRuntime is a goreleaser type defines runtime info in metadata.json +// Copied from https://github.com/goreleaser/goreleaser/blob/main/internal/pipe/metadata/metadata.go#L72 +type MetaRuntime struct { + Goos string `json:"goos"` + Goarch string `json:"goarch"` +} + +// Result represents is goreleaser combined output for metadata.json and artifacts.json. +type Result struct { + Metadata Metadata + Artifacts []Artifact +} + +// Run executes goreleaser with given command and arguments and return results info about command. +func Run(cmd Command, debug bool, env map[string]string, args []string) (*Result, error) { + var cliArgs []string + + if debug { + cliArgs = append(cliArgs, "--debug") + } + + cliArgs = append(cliArgs, string(cmd)) + cliArgs = append(cliArgs, args...) + + fmt.Printf("Running goreleaser with args: %v env: %v", cliArgs, env) + + _, err := sh.Exec(env, os.Stdout, os.Stderr, "goreleaser", cliArgs...) + if err != nil { + return nil, err + } + + metadataBytes, err := os.ReadFile("dist/metadata.json") + if err != nil { + return nil, err + } + + var metadata Metadata + err = json.Unmarshal(metadataBytes, &metadata) + if err != nil { + return nil, err + } + + artifactsBytes, err := os.ReadFile("dist/artifacts.json") + if err != nil { + return nil, err + } + + var artifacts []Artifact + err = json.Unmarshal(artifactsBytes, &artifacts) + if err != nil { + return nil, err + } + + return &Result{Metadata: metadata, Artifacts: artifacts}, nil +} diff --git a/mage/goreleaser/cli/doc.go b/mage/goreleaser/cli/doc.go new file mode 100644 index 0000000..a101e32 --- /dev/null +++ b/mage/goreleaser/cli/doc.go @@ -0,0 +1,6 @@ +// Package cli provides a wrapper around goreleaser cli to execute goreleaser commands using mage/sh +// +// Our goreleaser flow is contains docker image build and push and this is not possible to do with dagger at the +// moment. We will need to add this feature to dagger after https://github.com/dagger/dagger/issues/3712 resolved. +// Currently, we are using pure mage to execute goreleaser commands. +package cli diff --git a/mage/goreleaser/release/doc.go b/mage/goreleaser/release/doc.go new file mode 100644 index 0000000..a6bfbde --- /dev/null +++ b/mage/goreleaser/release/doc.go @@ -0,0 +1,2 @@ +// Package release provides a simple interface to the goreleaser release. +package release diff --git a/mage/goreleaser/release/mage.go b/mage/goreleaser/release/mage.go new file mode 100644 index 0000000..de67b7f --- /dev/null +++ b/mage/goreleaser/release/mage.go @@ -0,0 +1,210 @@ +package release + +import ( + "context" + "fmt" + "os" + "strconv" + "time" + + "github.com/magefile/mage/mg" + + "github.com/mesosphere/daggers/mage/goreleaser/cli" +) + +const ( + goreleaserConfigEnvVar = "GORELEASER_RELEASE_CONFIG" + goreleaserAutoSnapshotEnvVar = "GORELEASER_RELEASE_AUTO_SNAPSHOT" + goreleaserParallelismEnvVar = "GORELEASER_RELEASE_PARALLELISM" + goreleaserRmDistEnvVar = "GORELEASER_RELEASE_RM_DIST" + goreleaserReleaserFooterEnvVar = "GORELEASER_RELEASE_FOOTER" + goreleaserReleaserFooterTmplEnvVar = "GORELEASER_RELEASE_FOOTER_TMPL" + goreleaserReleaserHeaderEnvVar = "GORELEASER_RELEASE_HEADER" + goreleaserReleaserHeaderTmplEnvVar = "GORELEASER_RELEASE_HEADER_TMPL" + goreleaserReleaserNotesEnvVar = "GORELEASER_RELEASE_NOTES" + goreleaserReleaseNotesTmplEnvVar = "GORELEASER_RELEASE_NOTES_TMPL" + goreleaserSkipAnnounceEnvVar = "GORELEASER_RELEASE_SKIP_ANNOUNCE" + goreleaserSkipPublishEnvVar = "GORELEASER_RELEASE_SKIP_PUBLISH" + goreleaserSkipSbomEnvVar = "GORELEASER_RELEASE_SKIP_SBOM" + goreleaserSkipSignEnvVar = "GORELEASER_RELEASE_SKIP_SIGN" + goreleaserSkipValidateEnvVar = "GORELEASER_RELEASE_SKIP_VALIDATE" + goreleaserSnapshotEnvVar = "GORELEASER_RELEASE_SNAPSHOT" + goreleaserTimeoutEnvVar = "GORELEASER_RELEASE_TIMEOUT" +) + +// Release runs goreleaser release with --rm-dist flags. +func Release(_ context.Context) error { + result, err := ReleaseWithOptions(WithRmDist(true)) + if err != nil { + return err + } + + fmt.Printf( + "Release is successful for project: %s and version: %s\n", + result.Metadata.ProjectName, + result.Metadata.Version, + ) + + return nil +} + +// ReleaseSnapshot runs goreleaser release with --snapshot, --rm-dist and --skip-publish flags. +// +//nolint:revive // Disable stuttering check. +func ReleaseSnapshot(_ context.Context) error { + result, err := ReleaseWithOptions( + WithRmDist(true), + SkipPublish(true), + WithSnapshot(true), + ) + if err != nil { + return err + } + + fmt.Printf( + "Release snapshot is successful for project: %s and version: %s\n", + result.Metadata.ProjectName, + result.Metadata.Version, + ) + + return nil +} + +// ReleaseWithOptions runs goreleaser release with specific options. +// +//nolint:revive // Disable stuttering check. +func ReleaseWithOptions(opts ...Option) (*cli.Result, error) { + debug := mg.Debug() || mg.Verbose() + + envOpts, err := loadOptionsFromEnv() + if err != nil { + return nil, err + } + + // Combine options from environment variables and options passed to this function. Environment variables + // take precedence to allow overriding from the arguments passed to this function. + var combinedOpts []Option + + combinedOpts = append(combinedOpts, envOpts...) + combinedOpts = append(combinedOpts, opts...) + + options := config{} + for _, opt := range combinedOpts { + options = opt(options) + } + + return cli.Run(cli.CommandRelease, debug, options.env, options.toArgs()) +} + +// TODO: make this more readable or come-up with more generic solution +// +//nolint:revive // Disable cognitive-complexity check. +func loadOptionsFromEnv() ([]Option, error) { + var opts []Option + + opts = append(stringOptFromEnvVar(goreleaserConfigEnvVar, WithConfig), opts...) + opts = append(stringOptFromEnvVar(goreleaserParallelismEnvVar, WithParallelism), opts...) + opts = append(stringOptFromEnvVar(goreleaserReleaserFooterEnvVar, WithReleaseFooter), opts...) + opts = append(stringOptFromEnvVar(goreleaserReleaserFooterTmplEnvVar, WithReleaseFooterTmpl), opts...) + opts = append(stringOptFromEnvVar(goreleaserReleaserHeaderEnvVar, WithReleaseHeader), opts...) + opts = append(stringOptFromEnvVar(goreleaserReleaserHeaderTmplEnvVar, WithReleaseHeaderTmpl), opts...) + opts = append(stringOptFromEnvVar(goreleaserReleaserNotesEnvVar, WithReleaseNotes), opts...) + opts = append(stringOptFromEnvVar(goreleaserReleaseNotesTmplEnvVar, WithReleaseNotesTmpl), opts...) + + autoSnapshotOpts, err := boolOptFromEnvVar(goreleaserAutoSnapshotEnvVar, WithAutoSnapshot) + if err != nil { + return nil, err + } + opts = append(autoSnapshotOpts, opts...) + + rmDistOpts, err := boolOptFromEnvVar(goreleaserRmDistEnvVar, WithRmDist) + if err != nil { + return nil, err + } + opts = append(rmDistOpts, opts...) + + skipAnnounceOpts, err := boolOptFromEnvVar(goreleaserSkipAnnounceEnvVar, SkipAnnounce) + if err != nil { + return nil, err + } + opts = append(skipAnnounceOpts, opts...) + + skipPublishOpts, err := boolOptFromEnvVar(goreleaserSkipPublishEnvVar, SkipPublish) + if err != nil { + return nil, err + } + opts = append(skipPublishOpts, opts...) + + skipSbomOpts, err := boolOptFromEnvVar(goreleaserSkipSbomEnvVar, SkipSbom) + if err != nil { + return nil, err + } + opts = append(skipSbomOpts, opts...) + + skipSignOpts, err := boolOptFromEnvVar(goreleaserSkipSignEnvVar, SkipSign) + if err != nil { + return nil, err + } + opts = append(skipSignOpts, opts...) + + boolOpts, err := boolOptFromEnvVar(goreleaserSkipValidateEnvVar, SkipValidate) + if err != nil { + return nil, err + } + opts = append(boolOpts, opts...) + + boolOpts, err = boolOptFromEnvVar(goreleaserSnapshotEnvVar, WithSnapshot) + if err != nil { + return nil, err + } + opts = append(boolOpts, opts...) + + durationOpts, err := durationOptFromEnvVar(goreleaserTimeoutEnvVar, WithTimeout) + if err != nil { + return nil, err + } + opts = append(durationOpts, opts...) + + return opts, nil +} + +// stringOptFromEnvVar returns a slice containing a single option if the specified environment variable is set. +// This function returns a slice to make it easier to chain calls in optsFromEnvVars. +// Returns a nil slice if the env var is not set. +func stringOptFromEnvVar(envVarName string, optFn func(string) Option) []Option { + if envVarValue, ok := os.LookupEnv(envVarName); ok { + return []Option{optFn(envVarValue)} + } + + return nil +} + +// boolOptFromEnvVar returns a slice containing a single option if the specified environment variable is set. +// This function returns a slice to make it easier to chain calls in optsFromEnvVars. +// Returns a nil slice if the env var is not set. +func boolOptFromEnvVar(envVarName string, optFn func(bool) Option) ([]Option, error) { + if envVarValue, ok := os.LookupEnv(envVarName); ok { + boolVal, err := strconv.ParseBool(envVarValue) + if err != nil { + return nil, fmt.Errorf("failed to parse %q as a boolean: %w", envVarName, err) + } + return []Option{optFn(boolVal)}, nil + } + + return nil, nil +} + +// durationOptFromEnvVar returns a slice containing a single option if the specified environment variable is set. +// This function returns a slice to make it easier to chain calls in optsFromEnvVars. +// Returns a nil slice if the env var is not set. +func durationOptFromEnvVar(envVarName string, optFn func(duration time.Duration) Option) ([]Option, error) { + if envVarValue, ok := os.LookupEnv(envVarName); ok { + durationVal, err := time.ParseDuration(envVarValue) + if err != nil { + return nil, fmt.Errorf("failed to parse %q as a duration: %w", envVarName, err) + } + return []Option{optFn(durationVal)}, nil + } + + return nil, nil +} diff --git a/mage/goreleaser/release/option.go b/mage/goreleaser/release/option.go new file mode 100644 index 0000000..4bf68f2 --- /dev/null +++ b/mage/goreleaser/release/option.go @@ -0,0 +1,215 @@ +package release + +import ( + "time" +) + +type config struct { + env map[string]string + autoSnapshot bool + config string + parallelism string + rmDist bool + releaseFooter string + releaseFooterTmpl string + releaseHeader string + releaseHeaderTmpl string + releaseNotes string + releaseNotesTmpl string + skipAnnounce bool + skipPublish bool + skipSbom bool + skipSign bool + skipValidate bool + snapshot bool + timeout string +} + +// Option is a function that configures the goreleaser release options. +type Option func(config config) config + +// WithEnv append extra env variables to goreleaser build process. +func WithEnv(env map[string]string) Option { + return func(config config) config { + config.env = env + return config + } +} + +// WithAutoSnapshot sets --snapshot flag. +func WithAutoSnapshot(autoSnapshot bool) Option { + return func(config config) config { + config.autoSnapshot = autoSnapshot + return config + } +} + +// WithConfig sets --config flag. +func WithConfig(configPath string) Option { + return func(config config) config { + config.config = configPath + return config + } +} + +// WithParallelism sets --parallelism flag. +func WithParallelism(parallelism string) Option { + return func(config config) config { + config.parallelism = parallelism + return config + } +} + +// WithRmDist sets --rm-dist flag. +func WithRmDist(rmDist bool) Option { + return func(config config) config { + config.rmDist = rmDist + return config + } +} + +// WithReleaseFooter sets --release-footer flag. +func WithReleaseFooter(releaseFooter string) Option { + return func(config config) config { + config.releaseFooter = releaseFooter + return config + } +} + +// WithReleaseFooterTmpl sets --release-footer-tmpl flag. +func WithReleaseFooterTmpl(releaseFooterTmpl string) Option { + return func(config config) config { + config.releaseFooterTmpl = releaseFooterTmpl + return config + } +} + +// WithReleaseHeader sets --release-header flag. +func WithReleaseHeader(releaseHeader string) Option { + return func(config config) config { + config.releaseHeader = releaseHeader + return config + } +} + +// WithReleaseHeaderTmpl sets --release-header-tmpl flag. +func WithReleaseHeaderTmpl(releaseHeaderTmpl string) Option { + return func(config config) config { + config.releaseHeaderTmpl = releaseHeaderTmpl + return config + } +} + +// WithReleaseNotes sets --release-notes flag. +func WithReleaseNotes(releaseNotes string) Option { + return func(config config) config { + config.releaseNotes = releaseNotes + return config + } +} + +// WithReleaseNotesTmpl sets --release-notes-tmpl flag. +func WithReleaseNotesTmpl(releaseNotesTmpl string) Option { + return func(config config) config { + config.releaseNotesTmpl = releaseNotesTmpl + return config + } +} + +// SkipAnnounce sets --skip-announce flag. +func SkipAnnounce(skipAnnounce bool) Option { + return func(config config) config { + config.skipAnnounce = skipAnnounce + return config + } +} + +// SkipPublish sets --skip-publish flag. +func SkipPublish(skipPublish bool) Option { + return func(config config) config { + config.skipPublish = skipPublish + return config + } +} + +// SkipSbom sets --skip-sbom flag. +func SkipSbom(skipSbom bool) Option { + return func(config config) config { + config.skipSbom = skipSbom + return config + } +} + +// SkipSign sets --skip-sign flag. +func SkipSign(skipSign bool) Option { + return func(config config) config { + config.skipSign = skipSign + return config + } +} + +// SkipValidate sets --skip-validate flag. +func SkipValidate(skipValidate bool) Option { + return func(config config) config { + config.skipValidate = skipValidate + return config + } +} + +// WithSnapshot sets --snapshot flag. +func WithSnapshot(snapshot bool) Option { + return func(config config) config { + config.snapshot = snapshot + return config + } +} + +// WithTimeout sets --timeout flag. +func WithTimeout(timeout time.Duration) Option { + return func(config config) config { + config.timeout = timeout.String() + return config + } +} + +func (c *config) toArgs() []string { + var args []string + + args = appendNonEmptyStringVal(args, "--config", c.config) + args = appendBoolVal(args, "--snapshot", c.autoSnapshot) + args = appendNonEmptyStringVal(args, "--parallelism", c.parallelism) + args = appendBoolVal(args, "--rm-dist", c.rmDist) + args = appendNonEmptyStringVal(args, "--release-footer", c.releaseFooter) + args = appendNonEmptyStringVal(args, "--release-footer-tmpl", c.releaseFooterTmpl) + args = appendNonEmptyStringVal(args, "--release-header", c.releaseHeader) + args = appendNonEmptyStringVal(args, "--release-header-tmpl", c.releaseHeaderTmpl) + args = appendNonEmptyStringVal(args, "--release-notes", c.releaseNotes) + args = appendNonEmptyStringVal(args, "--release-notes-tmpl", c.releaseNotesTmpl) + args = appendBoolVal(args, "--skip-announce", c.skipAnnounce) + args = appendBoolVal(args, "--skip-publish", c.skipPublish) + args = appendBoolVal(args, "--skip-sbom", c.skipSbom) + args = appendBoolVal(args, "--skip-sign", c.skipSign) + args = appendBoolVal(args, "--skip-validate", c.skipValidate) + args = appendBoolVal(args, "--snapshot", c.snapshot) + args = appendNonEmptyStringVal(args, "--timeout", c.timeout) + + return args +} + +func appendNonEmptyStringVal(slice []string, flag, val string) []string { + // if val is empty return slice as it's + if val == "" { + return slice + } + + return append(slice, flag, val) +} + +func appendBoolVal(slice []string, flag string, val bool) []string { + // if val is false, no need to append the flag + if !val { + return slice + } + + return append(slice, flag) +}