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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
// This list exists to ensure that this mutator is updated when new resource is added.
// These resources are there because they use grants, not permissions:
var unsupportedResources = []string{
"app_spaces",
"catalogs",
"external_locations",
"volumes",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ func mockBundle(mode config.Mode) *bundle.Bundle {
},
},
},
AppSpaces: map[string]*resources.AppSpace{
"app_space1": {
Space: apps.Space{
Name: "app-space-1",
},
},
},
SecretScopes: map[string]*resources.SecretScope{
"secretScope1": {
Name: "secretScope1",
Expand Down Expand Up @@ -430,7 +437,7 @@ func TestAllNonUcResourcesAreRenamed(t *testing.T) {

// Skip resources that are not renamed (either because they don't have a user-facing Name field,
// or because their Name is server-generated rather than user-specified)
if resourceType == "Apps" || resourceType == "SecretScopes" || resourceType == "DatabaseInstances" || resourceType == "DatabaseCatalogs" || resourceType == "SyncedDatabaseTables" || resourceType == "PostgresProjects" || resourceType == "PostgresBranches" || resourceType == "PostgresEndpoints" {
if resourceType == "Apps" || resourceType == "AppSpaces" || resourceType == "SecretScopes" || resourceType == "DatabaseInstances" || resourceType == "DatabaseCatalogs" || resourceType == "SyncedDatabaseTables" || resourceType == "PostgresProjects" || resourceType == "PostgresBranches" || resourceType == "PostgresEndpoints" {
continue
}

Expand Down
45 changes: 45 additions & 0 deletions bundle/config/mutator/resourcemutator/merge_app_spaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package resourcemutator

import (
"context"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/dyn"
"github.com/databricks/cli/libs/dyn/merge"
)

type mergeAppSpaces struct{}

func MergeAppSpaces() bundle.Mutator {
return &mergeAppSpaces{}
}

func (m *mergeAppSpaces) Name() string {
return "MergeAppSpaces"
}

func (m *mergeAppSpaces) resourceName(v dyn.Value) string {
switch v.Kind() {
case dyn.KindInvalid, dyn.KindNil:
return ""
case dyn.KindString:
return v.MustString()
default:
panic("app space resource name must be a string")
}
}

func (m *mergeAppSpaces) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics {
err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) {
if v.Kind() == dyn.KindNil {
return v, nil
}

return dyn.Map(v, "resources.app_spaces", dyn.Foreach(func(_ dyn.Path, space dyn.Value) (dyn.Value, error) {
return dyn.Map(space, "resources", merge.ElementsByKeyWithOverride("name", m.resourceName))
}))
})

return diag.FromErr(err)
}
4 changes: 4 additions & 0 deletions bundle/config/mutator/resourcemutator/resource_mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ func applyNormalizeMutators(ctx context.Context, b *bundle.Bundle) {
// Updates (dynamic): resources.apps.*.resources (merges app resources with the same name)
MergeApps(),

// Reads (dynamic): resources.app_spaces.*.resources (reads app space resources to merge)
// Updates (dynamic): resources.app_spaces.*.resources (merges app space resources with the same name)
MergeAppSpaces(),

// Reads (dynamic): resources.{catalogs,schemas,external_locations,volumes,registered_models}.*.grants
// Updates (dynamic): same paths — merges grant entries by principal and deduplicates privileges
MergeGrants(),
Expand Down
10 changes: 10 additions & 0 deletions bundle/config/mutator/resourcemutator/run_as.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ func validateRunAs(b *bundle.Bundle) diag.Diagnostics {
))
}

// App spaces do not support run_as in the API.
if len(b.Config.Resources.AppSpaces) > 0 {
diags = diags.Extend(reportRunAsNotSupported(
"app_spaces",
b.Config.GetLocation("resources.app_spaces"),
b.Config.Workspace.CurrentUser.UserName,
identity,
))
}

return diags
}

Expand Down
1 change: 1 addition & 0 deletions bundle/config/mutator/resourcemutator/run_as_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func allResourceTypes(t *testing.T) []string {
// also update this check when adding a new resource
require.Equal(t, []string{
"alerts",
"app_spaces",
"apps",
"catalogs",
"clusters",
Expand Down
3 changes: 3 additions & 0 deletions bundle/config/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Resources struct {
Clusters map[string]*resources.Cluster `json:"clusters,omitempty"`
Dashboards map[string]*resources.Dashboard `json:"dashboards,omitempty"`
Apps map[string]*resources.App `json:"apps,omitempty"`
AppSpaces map[string]*resources.AppSpace `json:"app_spaces,omitempty"`
SecretScopes map[string]*resources.SecretScope `json:"secret_scopes,omitempty"`
Alerts map[string]*resources.Alert `json:"alerts,omitempty"`
SqlWarehouses map[string]*resources.SqlWarehouse `json:"sql_warehouses,omitempty"`
Expand Down Expand Up @@ -102,6 +103,7 @@ func (r *Resources) AllResources() []ResourceGroup {
collectResourceMap(descriptions["dashboards"], r.Dashboards),
collectResourceMap(descriptions["volumes"], r.Volumes),
collectResourceMap(descriptions["apps"], r.Apps),
collectResourceMap(descriptions["app_spaces"], r.AppSpaces),
collectResourceMap(descriptions["alerts"], r.Alerts),
collectResourceMap(descriptions["secret_scopes"], r.SecretScopes),
collectResourceMap(descriptions["sql_warehouses"], r.SqlWarehouses),
Expand Down Expand Up @@ -156,6 +158,7 @@ func SupportedResources() map[string]resources.ResourceDescription {
"dashboards": (&resources.Dashboard{}).ResourceDescription(),
"volumes": (&resources.Volume{}).ResourceDescription(),
"apps": (&resources.App{}).ResourceDescription(),
"app_spaces": (&resources.AppSpace{}).ResourceDescription(),
"secret_scopes": (&resources.SecretScope{}).ResourceDescription(),
"alerts": (&resources.Alert{}).ResourceDescription(),
"sql_warehouses": (&resources.SqlWarehouse{}).ResourceDescription(),
Expand Down
61 changes: 61 additions & 0 deletions bundle/config/resources/app_spaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package resources

import (
"context"
"net/url"

"github.com/databricks/cli/libs/log"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/marshal"
"github.com/databricks/databricks-sdk-go/service/apps"
)

type AppSpace struct {
BaseResource
apps.Space

Check failure on line 15 in bundle/config/resources/app_spaces.go

View workflow job for this annotation

GitHub Actions / lint

structtag: struct field Id repeats json tag "id" also at ../../../../../../../../work/cli/cli/bundle/config/resources/base.go:5 (govet)
}

func (s *AppSpace) UnmarshalJSON(b []byte) error {
return marshal.Unmarshal(b, s)
}

func (s AppSpace) MarshalJSON() ([]byte, error) {
return marshal.Marshal(s)
}

func (s *AppSpace) Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) (bool, error) {
_, err := w.Apps.GetSpace(ctx, apps.GetSpaceRequest{Name: id})
if err != nil {
log.Debugf(ctx, "app space %s does not exist", id)
return false, err
}
return true, nil
}

func (*AppSpace) ResourceDescription() ResourceDescription {
return ResourceDescription{
SingularName: "app_space",
PluralName: "app_spaces",
SingularTitle: "App Space",
PluralTitle: "App Spaces",
}
}

func (s *AppSpace) InitializeURL(baseURL url.URL) {
if s.ModifiedStatus == "" || s.ModifiedStatus == ModifiedStatusCreated {
return
}
baseURL.Path = "apps/spaces/" + s.GetName()
s.URL = baseURL.String()
}

func (s *AppSpace) GetName() string {
if s.ID != "" {
return s.ID
}
return s.Name
}

func (s *AppSpace) GetURL() string {
return s.URL
}
6 changes: 6 additions & 0 deletions bundle/config/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ func TestResourcesBindSupport(t *testing.T) {
App: apps.App{},
},
},
AppSpaces: map[string]*resources.AppSpace{
"my_app_space": {
Space: apps.Space{},
},
},
Alerts: map[string]*resources.Alert{
"my_alert": {
AlertV2: sql.AlertV2{},
Expand Down Expand Up @@ -257,6 +262,7 @@ func TestResourcesBindSupport(t *testing.T) {
m.GetMockLakeviewAPI().EXPECT().Get(mock.Anything, mock.Anything).Return(nil, nil)
m.GetMockVolumesAPI().EXPECT().Read(mock.Anything, mock.Anything).Return(nil, nil)
m.GetMockAppsAPI().EXPECT().GetByName(mock.Anything, mock.Anything).Return(nil, nil)
m.GetMockAppsAPI().EXPECT().GetSpace(mock.Anything, mock.Anything).Return(nil, nil)
m.GetMockAlertsV2API().EXPECT().GetAlertById(mock.Anything, mock.Anything).Return(nil, nil)
m.GetMockQualityMonitorsAPI().EXPECT().Get(mock.Anything, mock.Anything).Return(nil, nil)
m.GetMockServingEndpointsAPI().EXPECT().Get(mock.Anything, mock.Anything).Return(nil, nil)
Expand Down
1 change: 1 addition & 0 deletions bundle/direct/dresources/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var SupportedResources = map[string]any{
"volumes": (*ResourceVolume)(nil),
"models": (*ResourceMlflowModel)(nil),
"apps": (*ResourceApp)(nil),
"app_spaces": (*ResourceAppSpace)(nil),
"sql_warehouses": (*ResourceSqlWarehouse)(nil),
"database_instances": (*ResourceDatabaseInstance)(nil),
"database_catalogs": (*ResourceDatabaseCatalog)(nil),
Expand Down
6 changes: 6 additions & 0 deletions bundle/direct/dresources/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ var testConfig map[string]any = map[string]any{
},
},

"app_spaces": &resources.AppSpace{
Space: apps.Space{
Name: "my-app-space",
},
},

"catalogs": &resources.Catalog{
CreateCatalog: catalog.CreateCatalog{
Name: "mycatalog",
Expand Down
60 changes: 60 additions & 0 deletions bundle/direct/dresources/app_space.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dresources

import (
"context"

"github.com/databricks/cli/bundle/config/resources"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/common/types/fieldmask"
"github.com/databricks/databricks-sdk-go/service/apps"
)

type ResourceAppSpace struct {
client *databricks.WorkspaceClient
}

func (*ResourceAppSpace) New(client *databricks.WorkspaceClient) *ResourceAppSpace {
return &ResourceAppSpace{client: client}
}

func (*ResourceAppSpace) PrepareState(input *resources.AppSpace) *apps.Space {
return &input.Space
}

func (r *ResourceAppSpace) DoRead(ctx context.Context, id string) (*apps.Space, error) {
return r.client.Apps.GetSpace(ctx, apps.GetSpaceRequest{Name: id})
}

func (r *ResourceAppSpace) DoCreate(ctx context.Context, config *apps.Space) (string, *apps.Space, error) {
waiter, err := r.client.Apps.CreateSpace(ctx, apps.CreateSpaceRequest{
Space: *config,
})
if err != nil {
return "", nil, err
}
space, err := waiter.Wait(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a separate WaitAfterCreate / Update method to wait fo this resources to be created

if err != nil {
return "", nil, err
}
return space.Name, space, nil
}

func (r *ResourceAppSpace) DoUpdate(ctx context.Context, id string, config *apps.Space, _ *PlanEntry) (*apps.Space, error) {
waiter, err := r.client.Apps.UpdateSpace(ctx, apps.UpdateSpaceRequest{
Name: id,
Space: *config,
UpdateMask: fieldmask.FieldMask{Paths: []string{"description", "resources", "user_api_scopes", "usage_policy_id"}},
})
if err != nil {
return nil, err
}
return waiter.Wait(ctx)
}

func (r *ResourceAppSpace) DoDelete(ctx context.Context, id string) error {
waiter, err := r.client.Apps.DeleteSpace(ctx, apps.DeleteSpaceRequest{Name: id})
if err != nil {
return err
}
return waiter.Wait(ctx)
}
5 changes: 5 additions & 0 deletions bundle/direct/dresources/resources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ resources:
- field: dataset_schema
reason: input_only

app_spaces:
recreate_on_changes:
- field: name
reason: immutable

apps:
recreate_on_changes:
- field: name
Expand Down
Loading
Loading