Skip to content

Commit 418a0ae

Browse files
committed
Align InstallState with spec, clean up source.go
- Rename SkillsRef->Release, LastChecked->LastUpdated (time.Time), simplify Skills to map[string]string, add IncludeExperimental and Scope fields. - Remove unused Clock/RealClock from source.go. - Add trailing newline to SaveState JSON output. - Improve FetchLatestRelease doc comment on error/fallback contract. - Document intentional DATABRICKS_SKILLS_REF duplication. Co-authored-by: Isaac
1 parent 1e7902c commit 418a0ae

File tree

3 files changed

+63
-31
lines changed

3 files changed

+63
-31
lines changed

experimental/aitools/lib/installer/source.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,16 @@ import (
1111
"github.com/databricks/cli/libs/log"
1212
)
1313

14-
// Clock abstracts time for testing.
15-
type Clock interface {
16-
Now() time.Time
17-
}
18-
19-
// RealClock returns the actual current time.
20-
type RealClock struct{}
21-
22-
// Now returns the current time.
23-
func (RealClock) Now() time.Time { return time.Now() }
24-
2514
// ManifestSource abstracts how the skills manifest and release info are fetched.
2615
type ManifestSource interface {
2716
// FetchManifest fetches the skills manifest at the given ref.
2817
FetchManifest(ctx context.Context, ref string) (*Manifest, error)
2918

3019
// FetchLatestRelease returns the latest release tag.
31-
// Falls back to defaultSkillsRepoRef on any error.
20+
// Implementations should fall back to a default ref on network errors rather
21+
// than returning an error. The error return exists for cases where fallback is
22+
// not possible (e.g., mock implementations in tests that want to simulate hard
23+
// failures).
3224
FetchLatestRelease(ctx context.Context) (string, error)
3325
}
3426

@@ -68,6 +60,10 @@ func (s *GitHubManifestSource) FetchManifest(ctx context.Context, ref string) (*
6860
// FetchLatestRelease returns the latest release tag from GitHub.
6961
// If DATABRICKS_SKILLS_REF is set, it is returned immediately.
7062
// On any error (network, non-200, parse), falls back to defaultSkillsRepoRef.
63+
//
64+
// The DATABRICKS_SKILLS_REF check is intentionally duplicated in getSkillsRef()
65+
// because callers may use either the ManifestSource interface directly or the
66+
// convenience FetchManifest wrapper.
7167
func (s *GitHubManifestSource) FetchLatestRelease(ctx context.Context) (string, error) {
7268
if ref := env.Get(ctx, "DATABRICKS_SKILLS_REF"); ref != "" {
7369
return ref, nil

experimental/aitools/lib/installer/state.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"os"
99
"path/filepath"
10+
"time"
1011

1112
"github.com/databricks/cli/libs/env"
1213
)
@@ -16,18 +17,14 @@ const stateFileName = ".state.json"
1617
// ErrNotImplemented indicates that a feature is not yet implemented.
1718
var ErrNotImplemented = errors.New("project scope not yet implemented")
1819

19-
// InstalledSkill records the installed version and timestamp for a single skill.
20-
type InstalledSkill struct {
21-
Version string `json:"version"`
22-
InstalledAt string `json:"installed_at"`
23-
}
24-
2520
// InstallState records the state of all installed skills in a scope directory.
2621
type InstallState struct {
27-
SchemaVersion int `json:"schema_version"`
28-
SkillsRef string `json:"skills_ref"`
29-
LastChecked string `json:"last_checked,omitempty"`
30-
Skills map[string]InstalledSkill `json:"skills"`
22+
SchemaVersion int `json:"schema_version"`
23+
IncludeExperimental bool `json:"include_experimental,omitempty"`
24+
Release string `json:"release"`
25+
LastUpdated time.Time `json:"last_updated"`
26+
Skills map[string]string `json:"skills"`
27+
Scope string `json:"scope,omitempty"`
3128
}
3229

3330
// LoadState reads install state from the given directory.
@@ -59,6 +56,7 @@ func SaveState(dir string, state *InstallState) error {
5956
if err != nil {
6057
return fmt.Errorf("failed to marshal state: %w", err)
6158
}
59+
data = append(data, '\n')
6260

6361
// Atomic write: write to temp file in the same directory, then rename.
6462
tmp, err := os.CreateTemp(dir, ".state-*.tmp")

experimental/aitools/lib/installer/state_test.go

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"os"
55
"path/filepath"
66
"testing"
7+
"time"
78

89
"github.com/databricks/cli/libs/env"
910
"github.com/stretchr/testify/assert"
@@ -20,13 +21,10 @@ func TestSaveAndLoadStateRoundtrip(t *testing.T) {
2021
dir := t.TempDir()
2122
original := &InstallState{
2223
SchemaVersion: 1,
23-
SkillsRef: "v0.2.0",
24-
LastChecked: "2026-03-22T10:00:00Z",
25-
Skills: map[string]InstalledSkill{
26-
"databricks": {
27-
Version: "1.0.0",
28-
InstalledAt: "2026-03-22T09:00:00Z",
29-
},
24+
Release: "v0.2.0",
25+
LastUpdated: time.Date(2026, 3, 22, 10, 0, 0, 0, time.UTC),
26+
Skills: map[string]string{
27+
"databricks": "1.0.0",
3028
},
3129
}
3230

@@ -42,8 +40,9 @@ func TestSaveStateCreatesDirectory(t *testing.T) {
4240
dir := filepath.Join(t.TempDir(), "nested", "path")
4341
state := &InstallState{
4442
SchemaVersion: 1,
45-
SkillsRef: "v0.1.0",
46-
Skills: map[string]InstalledSkill{},
43+
Release: "v0.1.0",
44+
LastUpdated: time.Date(2026, 3, 22, 9, 0, 0, 0, time.UTC),
45+
Skills: map[string]string{},
4746
}
4847

4948
err := SaveState(dir, state)
@@ -54,6 +53,23 @@ func TestSaveStateCreatesDirectory(t *testing.T) {
5453
assert.NoError(t, err)
5554
}
5655

56+
func TestSaveStateTrailingNewline(t *testing.T) {
57+
dir := t.TempDir()
58+
state := &InstallState{
59+
SchemaVersion: 1,
60+
Release: "v0.1.0",
61+
LastUpdated: time.Date(2026, 3, 22, 9, 0, 0, 0, time.UTC),
62+
Skills: map[string]string{},
63+
}
64+
65+
err := SaveState(dir, state)
66+
require.NoError(t, err)
67+
68+
data, err := os.ReadFile(filepath.Join(dir, stateFileName))
69+
require.NoError(t, err)
70+
assert.Equal(t, byte('\n'), data[len(data)-1])
71+
}
72+
5773
func TestLoadStateCorruptJSON(t *testing.T) {
5874
dir := t.TempDir()
5975
require.NoError(t, os.WriteFile(filepath.Join(dir, stateFileName), []byte("{bad json"), 0o644))
@@ -76,3 +92,25 @@ func TestProjectSkillsDirNotImplemented(t *testing.T) {
7692
assert.ErrorIs(t, err, ErrNotImplemented)
7793
assert.Empty(t, dir)
7894
}
95+
96+
func TestSaveAndLoadStateWithOptionalFields(t *testing.T) {
97+
dir := t.TempDir()
98+
original := &InstallState{
99+
SchemaVersion: 1,
100+
IncludeExperimental: true,
101+
Release: "v0.3.0",
102+
LastUpdated: time.Date(2026, 3, 22, 12, 30, 0, 0, time.UTC),
103+
Skills: map[string]string{
104+
"databricks": "1.0.0",
105+
"sql-tools": "0.2.0",
106+
},
107+
Scope: "project",
108+
}
109+
110+
err := SaveState(dir, original)
111+
require.NoError(t, err)
112+
113+
loaded, err := LoadState(dir)
114+
require.NoError(t, err)
115+
assert.Equal(t, original, loaded)
116+
}

0 commit comments

Comments
 (0)