Skip to content

Commit

Permalink
add "release exists" command
Browse files Browse the repository at this point in the history
Add the command "release exists". It checks if a release with give name
exists.

It can be used without a baur repository by setting the
BAUR_POSTGRESQL_URL environment variable.
  • Loading branch information
fho committed May 30, 2024
1 parent 2857849 commit 940cfb1
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 37 deletions.
4 changes: 2 additions & 2 deletions internal/command/diff_inputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func (c *diffInputsCmd) getTaskRunInputs(repo *baur.Repository, argDetails *diff

taskRun := getTaskRun(repo, argDetails)

psql := mustNewCompatibleStorage(repo)
psql := mustNewCompatibleStorageRepo(repo)
defer psql.Close()

storageInputs, err := psql.Inputs(ctx, taskRun.ID)
Expand All @@ -236,7 +236,7 @@ func (c *diffInputsCmd) getTaskRunInputs(repo *baur.Repository, argDetails *diff
}

func getTaskRun(repo *baur.Repository, argDetails *diffInputArgDetails) *storage.TaskRunWithID {
psql := mustNewCompatibleStorage(repo)
psql := mustNewCompatibleStorageRepo(repo)
defer psql.Close()

if strings.Contains(argDetails.runID, "^") {
Expand Down
1 change: 1 addition & 0 deletions internal/command/exitcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ const (
exitCodeError = 1
exitCodeAlreadyExist = 2
exitCodeTaskRunIsPending = 3
exitCodeNotExist = 4
)
86 changes: 63 additions & 23 deletions internal/command/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ type Formatter interface {
Flush() error
}

var ErrPSQLURIMissing = errors.New(
"PostgreSQL connection information is missing.\n" +
"- set postgres_url in your repository config or\n" +
"- set the " + envVarPSQLURL + "environment variable",
)

var targetHelp = fmt.Sprintf(`%s is in the format %s
Examples:
- 'shop' matches all tasks of the app named shop
Expand Down Expand Up @@ -111,13 +117,8 @@ func mustArgToApp(repo *baur.Repository, arg string) *baur.App {
func newStorageClient(psqlURI string) (storage.Storer, error) {
uri := psqlURI

if envURI := os.Getenv(envVarPSQLURL); len(envURI) != 0 {
log.Debugf("using postgresql connection URL from $%s environment variable",
envVarPSQLURL)

if envURI := getPSQLURIEnv(); envURI != "" {
uri = envURI
} else {
log.Debugf("environment variable $%s not set", envVarPSQLURL)
}

var logger postgres.Logger
Expand All @@ -129,32 +130,71 @@ func newStorageClient(psqlURI string) (storage.Storer, error) {
}

// mustGetPSQLURI returns if it's set the URI from the environment variable
// envVarPSQLURL, otherwise if it's set the psql uri from the repository config,
// if it's also not empty prints an error and exits.
// [envVarPSQLURL], otherwise the psql uri from the repository
// config, if both are empty, an error is printed and the application
// terminated.
func mustGetPSQLURI(cfg *cfg.Repository) string {
uri := getPSQLURI(cfg)
if uri == "" {
stderr.Printf("PostgreSQL connection information is missing.\n"+
"- set postgres_url in your repository config or\n"+
"- set the $%s environment variable", envVarPSQLURL)
exitFunc(exitCodeError)
if url := getPSQLURIEnv(); url != "" {
return url
}

return uri
if cfg.Database.PGSQLURL != "" {
return cfg.Database.PGSQLURL
}

stderr.ErrPrintln(ErrPSQLURIMissing)
exitFunc(exitCodeError)
return ""
}

func getPSQLURI(cfg *cfg.Repository) string {
if url := os.Getenv(envVarPSQLURL); url != "" {
return url
func getPSQLURIEnv() string {
if envURI := os.Getenv(envVarPSQLURL); len(envURI) != 0 {
log.Debugf("using postgresql connection URL from %s environment variable",
envVarPSQLURL)

return envURI
}

return cfg.Database.PGSQLURL
log.Debugf("environment variable %s not set", envVarPSQLURL)
return ""
}

// postgresqlURL returns the value of the environment variable [envVarPSQLURL],
// if is set.
// Otherwise it searches for a baur repository and returns the postgresql url
// from the repository config.
// If the repository object is needed, use [mustNewCompatibleStorageRepo]
// instead, to prevent that the repository is discovered + it's config parsed
// multiple times.
func postgresqlURL() (string, error) {
if url := getPSQLURIEnv(); url != "" {
return url, nil
}

repo, err := findRepository()
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("can not locate postgresql database\n"+
"- the environment variable %s is not set\n"+
"- a baur repository was not found: %s", envVarPSQLURL, err,
)
}
return "", err
}

if repo.Cfg.Database.PGSQLURL == "" {
return "", ErrPSQLURIMissing
}

return repo.Cfg.Database.PGSQLURL, nil
}

func mustNewCompatibleStorageRepo(r *baur.Repository) storage.Storer {
return mustNewCompatibleStorage(mustGetPSQLURI(r.Cfg))
}

// mustNewCompatibleStorage initializes a new postgresql storage client.
// The function ensures that the storage is compatible.
func mustNewCompatibleStorage(r *baur.Repository) storage.Storer {
clt, err := newStorageClient(mustGetPSQLURI(r.Cfg))
func mustNewCompatibleStorage(uri string) storage.Storer {
clt, err := newStorageClient(uri)
exitOnErr(err, "creating postgresql storage client failed")

if err := clt.IsCompatible(ctx); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/command/ls_inputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (c *lsInputsCmd) run(_ *cobra.Command, args []string) {
func (c *lsInputsCmd) mustGetTaskRunInputs(taskRunID int) *baur.Inputs {
repo := mustFindRepository()

storageClt := mustNewCompatibleStorage(repo)
storageClt := mustNewCompatibleStorageRepo(repo)
defer storageClt.Close()

inputs, err := storageClt.Inputs(ctx, taskRunID)
Expand Down
2 changes: 1 addition & 1 deletion internal/command/ls_outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (c *lsOutputsCmd) run(_ *cobra.Command, args []string) {
}

repo := mustFindRepository()
pgClient := mustNewCompatibleStorage(repo)
pgClient := mustNewCompatibleStorageRepo(repo)
defer pgClient.Close()

_, err = pgClient.TaskRun(ctx, taskRunID)
Expand Down
2 changes: 1 addition & 1 deletion internal/command/ls_runs.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func (c *lsRunsCmd) run(_ *cobra.Command, args []string) {
c.app, c.task = parseSpec(args[0])

repo := mustFindRepository()
psql := mustNewCompatibleStorage(repo)
psql := mustNewCompatibleStorageRepo(repo)
defer psql.Close()

var formatter Formatter
Expand Down
2 changes: 1 addition & 1 deletion internal/command/release_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (c *releaseCreateCmd) run(cmd *cobra.Command, args []string) {
fatal("could not find any tasks in the baur repository")
}

storageClt := mustNewCompatibleStorage(repo)
storageClt := mustNewCompatibleStorageRepo(repo)
runIDs := c.mustFetchTaskIDs(ctx, repo, storageClt, tasks)

var metadataReader io.Reader
Expand Down
80 changes: 80 additions & 0 deletions internal/command/release_exists.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package command

import (
"fmt"
"strings"

"github.com/simplesurance/baur/v3/internal/command/term"

"github.com/spf13/cobra"
)

var releaseExistsLongHelp = fmt.Sprintf(`
Check if a release with a given name exists.
The command can be run without access to the baur repository, by specifying
the PostgreSQL URI via the environment variable %s.
Exit Codes:
%d - Success, release exists
%d - Error
%d - Release does not exist
`,
term.Highlight(envVarPSQLURL),
exitCodeSuccess,
exitCodeError,
exitCodeNotExist,
)

type releaseExistsCmd struct {
cobra.Command
quiet bool
}

func init() {
releaseCmd.AddCommand(&newReleaseExistsCmd().Command)
}

func newReleaseExistsCmd() *releaseExistsCmd {
cmd := releaseExistsCmd{
Command: cobra.Command{
Use: "exists NAME",
Short: "check if a release exists",
Long: strings.TrimSpace(releaseExistsLongHelp),
Args: cobra.ExactArgs(1),
ValidArgsFunction: cobra.NoFileCompletions,
},
}

cmd.Run = cmd.run
cmd.Flags().BoolVarP(
&cmd.quiet,
"quiet", "q",
false,
"suppress printing a result message",
)

return &cmd
}

func (c *releaseExistsCmd) run(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
releaseName := args[0]
psqlURL, err := postgresqlURL()
exitOnErr(err)

storageClt := mustNewCompatibleStorage(psqlURL)

exists, err := storageClt.ReleaseExists(ctx, releaseName)
exitOnErr(err)
if !exists {
if !c.quiet {
stdout.Printf("release %s does not exist\n", term.Highlight(args[0]))
}
exitFunc(exitCodeNotExist)
}

if !c.quiet {
stdout.Printf("release %s exists\n", term.Highlight(args[0]))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,28 @@ package command
import (
"testing"

"github.com/simplesurance/baur/v3/internal/testutils/ostest"
"github.com/simplesurance/baur/v3/internal/testutils/repotest"

"github.com/stretchr/testify/require"
)

func TestCreateRelease(t *testing.T) {
func TestRelease(t *testing.T) {
initTest(t)

r := repotest.CreateBaurRepository(t, repotest.WithNewDB())
r.CreateSimpleApp(t)
runInitDb(t)

t.Run("failsWhenTaskRunsArePending", func(t *testing.T) {
t.Run("ExistsNonExistingRelease", func(t *testing.T) {
initTest(t)

releaseExistsCmd := newReleaseExistsCmd()
releaseExistsCmd.SetArgs([]string{"abc"})
execCheck(t, releaseExistsCmd, exitCodeNotExist)
})

t.Run("CreatefailsWhenTaskRunsArePending", func(t *testing.T) {
initTest(t)
releaseCmd := newReleaseCreateCmd()
releaseCmd.SetArgs([]string{"all"})
Expand All @@ -31,15 +40,19 @@ func TestCreateRelease(t *testing.T) {
// TODO: extend the testcases to verify the data of the created
// release, when "release show" was implemented

t.Run("allTasks", func(t *testing.T) {
t.Run("CreateAllTasksAndExistsSucceeds", func(t *testing.T) {
initTest(t)
releaseCmd := newReleaseCreateCmd()

releaseCmd.SetArgs([]string{"all"})
require.NotPanics(t, func() { require.NoError(t, releaseCmd.Execute()) })

releaseExistsCmd := newReleaseExistsCmd()
releaseExistsCmd.SetArgs([]string{"all"})
require.NotPanics(t, func() { require.NoError(t, releaseExistsCmd.Execute()) })
})

t.Run("releaseAlreadyExistsErr", func(t *testing.T) {
t.Run("CreateFailsWithAlreadyExists", func(t *testing.T) {
initTest(t)

releaseCmd := newReleaseCreateCmd()
Expand All @@ -48,7 +61,7 @@ func TestCreateRelease(t *testing.T) {
execCheck(t, releaseCmd, exitCodeAlreadyExist)
})

t.Run("metadataAndMultipleIncludes", func(t *testing.T) {
t.Run("CreateWithMetadataAndMultipleIncludes", func(t *testing.T) {
initTest(t)
releaseCmd := newReleaseCreateCmd()

Expand All @@ -59,4 +72,15 @@ func TestCreateRelease(t *testing.T) {
require.NotPanics(t, func() { require.NoError(t, releaseCmd.Execute()) })
})

t.Run("ExistsWithPsqlURIviaEnv", func(t *testing.T) {
initTest(t)

ostest.Chdir(t, t.TempDir())

t.Setenv(envVarPSQLURL, r.Cfg.Database.PGSQLURL)

releaseExistsCmd := newReleaseExistsCmd()
releaseExistsCmd.SetArgs([]string{"buildCheck"})
require.NotPanics(t, func() { require.NoError(t, releaseExistsCmd.Execute()) })
})
}
2 changes: 1 addition & 1 deletion internal/command/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (c *runCmd) run(_ *cobra.Command, args []string) {

mustUntrackedFilesNotExist(c.requireCleanGitWorktree, c.gitRepo)

c.storage = mustNewCompatibleStorage(repo)
c.storage = mustNewCompatibleStorageRepo(repo)
defer c.storage.Close()

inputResolver := baur.NewInputResolver(
Expand Down
2 changes: 1 addition & 1 deletion internal/command/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ func vcsStr(v *storage.TaskRun) string {

func (*showCmd) showBuild(taskRunID int) {
repo := mustFindRepository()
storageClt := mustNewCompatibleStorage(repo)
storageClt := mustNewCompatibleStorageRepo(repo)
defer storageClt.Close()

taskRun, err := storageClt.TaskRun(ctx, taskRunID)
Expand Down
2 changes: 1 addition & 1 deletion internal/command/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func (c *statusCmd) run(_ *cobra.Command, args []string) {
storageQueryNeeded := c.storageQueryIsNeeded()

if storageQueryNeeded {
storageClt = mustNewCompatibleStorage(repo)
storageClt = mustNewCompatibleStorageRepo(repo)
defer storageClt.Close()
}

Expand Down
2 changes: 2 additions & 0 deletions internal/testutils/repotest/repotest.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ func (r *Repo) WriteAdditionalFileContents(t *testing.T, appName, fileName, cont
}

type Repo struct {
Cfg *cfg.Repository
AppCfgs []*cfg.App
Dir string
FilecopyArtifactDir string
Expand Down Expand Up @@ -338,6 +339,7 @@ func CreateBaurRepository(t *testing.T, opts ...Opt) *Repo {
t.Logf("changed working directory to baur repository: %q", tempDir)

return &Repo{
Cfg: &cfgR,
Dir: tempDir,
FilecopyArtifactDir: artifactDir,
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/storage/postgres/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,19 @@ func (c *Client) TaskRuns(

return nil
}

func (c *Client) ReleaseExists(ctx context.Context, name string) (bool, error) {
const query = `
SELECT COUNT(id)
FROM release
WHERE name = $1
LIMIT 1
`
var count int
err := c.db.QueryRow(ctx, query, name).Scan(&count)
if err != nil {
return false, newQueryError(query, err, name)
}

return count == 1, nil
}
Loading

0 comments on commit 940cfb1

Please sign in to comment.