Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions cmd/workspace/apps/git_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package apps

import (
"fmt"

"github.com/databricks/databricks-sdk-go/service/apps"
"github.com/spf13/cobra"
)

// The apps create/update/deploy commands accept a git repository and a git
// deployment source, but the code generator emits these nested objects as
// `// TODO: complex arg` and only exposes them via --json. These overrides add
// ergonomic top-level flags for the GA git fields so users can point an app at
// a repo and deploy a specific ref without hand-writing JSON.
//
// The SDK models GitRepository and GitSource as optional pointers on the
// request. We leave them nil unless the user sets a git flag; allocating them
// unconditionally would send an empty object on every non-git create/deploy and
// change the request the server sees.

// gitRepositoryFlags binds --git-url/--git-provider onto an *apps.GitRepository
// pointer field (App.GitRepository), used by both create and update. It returns
// a PreRunE that allocates the struct only when a flag was set.
func gitRepositoryFlags(cmd *cobra.Command, target **apps.GitRepository) func(*cobra.Command, []string) error {
var url, provider string
cmd.Flags().StringVar(&url, "git-url", "", "URL of the Git repository the app deploys from.")
cmd.Flags().StringVar(&provider, "git-provider", "", "Git provider. Case insensitive. Supported values: gitHub, gitHubEnterprise, bitbucketCloud, bitbucketServer, azureDevOpsServices, gitLab, gitLabEnterpriseEdition, awsCodeCommit.")

return func(cmd *cobra.Command, args []string) error {
urlSet := cmd.Flags().Changed("git-url")
providerSet := cmd.Flags().Changed("git-provider")
if !urlSet && !providerSet {
return nil
}
// The server requires both url and provider together, so fail early
// rather than shipping a half-populated repository it will reject.
if urlSet != providerSet {
return fmt.Errorf("--git-url and --git-provider must be set together")

Check failure on line 38 in cmd/workspace/apps/git_flags.go

View workflow job for this annotation

GitHub Actions / lint

error-format: fmt.Errorf can be replaced with errors.New (perfsprint)
}
*target = &apps.GitRepository{Url: url, Provider: provider}
return nil
}
}

// gitSourceFlags binds the deploy-time git source flags onto an *apps.GitSource
// pointer field (AppDeployment.GitSource). It returns a PreRunE that allocates
// the struct only when a flag was set.
func gitSourceFlags(cmd *cobra.Command, target **apps.GitSource) func(*cobra.Command, []string) error {
var branch, tag, commit, sourceCodePath string
cmd.Flags().StringVar(&branch, "git-branch", "", "Git branch to deploy from.")
cmd.Flags().StringVar(&tag, "git-tag", "", "Git tag to deploy from.")
cmd.Flags().StringVar(&commit, "git-commit", "", "Git commit SHA to deploy from.")
cmd.Flags().StringVar(&sourceCodePath, "git-source-code-path", "", "Relative path to the app source code within the Git repository. Defaults to the repository root.")

// branch, tag, and commit are a proto oneof (a single git reference) — the
// server accepts at most one.
cmd.MarkFlagsMutuallyExclusive("git-branch", "git-tag", "git-commit")

return func(cmd *cobra.Command, args []string) error {
refSet := cmd.Flags().Changed("git-branch") ||
cmd.Flags().Changed("git-tag") ||
cmd.Flags().Changed("git-commit")
pathSet := cmd.Flags().Changed("git-source-code-path")
if !refSet && !pathSet {
return nil
}
// A source-code path without a reference has no repository to resolve
// against — the reference is what selects the code to deploy.
if pathSet && !refSet {
return fmt.Errorf("--git-source-code-path requires one of --git-branch, --git-tag, or --git-commit")

Check failure on line 70 in cmd/workspace/apps/git_flags.go

View workflow job for this annotation

GitHub Actions / lint

error-format: fmt.Errorf can be replaced with errors.New (perfsprint)
}
*target = &apps.GitSource{
Branch: branch,
Tag: tag,
Commit: commit,
SourceCodePath: sourceCodePath,
}
return nil
}
}

// chainPreRunE runs fn after any PreRunE already set on the command, preserving
// the generated cmd.PreRunE (e.g. root.MustWorkspaceClient) rather than
// replacing it.
func chainPreRunE(cmd *cobra.Command, fn func(*cobra.Command, []string) error) {
prev := cmd.PreRunE
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
if prev != nil {
if err := prev(cmd, args); err != nil {
return err
}
}
return fn(cmd, args)
}
}

func gitCreateOverride(createCmd *cobra.Command, createReq *apps.CreateAppRequest) {
chainPreRunE(createCmd, gitRepositoryFlags(createCmd, &createReq.App.GitRepository))
}

func gitUpdateOverride(updateCmd *cobra.Command, updateReq *apps.UpdateAppRequest) {
chainPreRunE(updateCmd, gitRepositoryFlags(updateCmd, &updateReq.App.GitRepository))
}

func gitDeployOverride(deployCmd *cobra.Command, deployReq *apps.CreateAppDeploymentRequest) {
chainPreRunE(deployCmd, gitSourceFlags(deployCmd, &deployReq.AppDeployment.GitSource))
}

func init() {
createOverrides = append(createOverrides, gitCreateOverride)
updateOverrides = append(updateOverrides, gitUpdateOverride)
deployOverrides = append(deployOverrides, gitDeployOverride)
}
95 changes: 95 additions & 0 deletions cmd/workspace/apps/git_flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package apps

import (
"testing"

"github.com/databricks/databricks-sdk-go/service/apps"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// runGitRepositoryFlags wires gitRepositoryFlags onto a fresh command, sets the
// given flags, runs the PreRunE, and returns the resulting pointer + error.
func runGitRepositoryFlags(t *testing.T, argv []string) (*apps.GitRepository, error) {
t.Helper()
cmd := &cobra.Command{}
var target *apps.GitRepository
pre := gitRepositoryFlags(cmd, &target)
require.NoError(t, cmd.ParseFlags(argv))
return target, pre(cmd, nil)
}

func runGitSourceFlags(t *testing.T, argv []string) (*apps.GitSource, error) {
t.Helper()
cmd := &cobra.Command{}
var target *apps.GitSource
pre := gitSourceFlags(cmd, &target)
require.NoError(t, cmd.ParseFlags(argv))
return target, pre(cmd, nil)
}

func TestGitRepositoryFlags(t *testing.T) {
t.Run("no flags leaves target nil", func(t *testing.T) {
target, err := runGitRepositoryFlags(t, nil)
require.NoError(t, err)
assert.Nil(t, target)
})

t.Run("url and provider populate the struct", func(t *testing.T) {
target, err := runGitRepositoryFlags(t, []string{
"--git-url", "https://github.com/databricks/git_app_repo.git",
"--git-provider", "gitHub",
})
require.NoError(t, err)
require.NotNil(t, target)
assert.Equal(t, "https://github.com/databricks/git_app_repo.git", target.Url)
assert.Equal(t, "gitHub", target.Provider)
})

t.Run("url without provider errors", func(t *testing.T) {
_, err := runGitRepositoryFlags(t, []string{"--git-url", "https://github.com/x/y"})
require.Error(t, err)
assert.Contains(t, err.Error(), "must be set together")
})

t.Run("provider without url errors", func(t *testing.T) {
_, err := runGitRepositoryFlags(t, []string{"--git-provider", "gitHub"})
require.Error(t, err)
assert.Contains(t, err.Error(), "must be set together")
})
}

func TestGitSourceFlags(t *testing.T) {
t.Run("no flags leaves target nil", func(t *testing.T) {
target, err := runGitSourceFlags(t, nil)
require.NoError(t, err)
assert.Nil(t, target)
})

t.Run("branch populates the struct", func(t *testing.T) {
target, err := runGitSourceFlags(t, []string{"--git-branch", "main"})
require.NoError(t, err)
require.NotNil(t, target)
assert.Equal(t, "main", target.Branch)
assert.Empty(t, target.Tag)
assert.Empty(t, target.Commit)
})

t.Run("commit with source-code-path populates both", func(t *testing.T) {
target, err := runGitSourceFlags(t, []string{
"--git-commit", "abc123",
"--git-source-code-path", "my-app",
})
require.NoError(t, err)
require.NotNil(t, target)
assert.Equal(t, "abc123", target.Commit)
assert.Equal(t, "my-app", target.SourceCodePath)
})

t.Run("source-code-path without a ref errors", func(t *testing.T) {
_, err := runGitSourceFlags(t, []string{"--git-source-code-path", "my-app"})
require.Error(t, err)
assert.Contains(t, err.Error(), "requires one of")
})
}
Loading