From 76d66f7ad67958de12ea4014d2338956623472de Mon Sep 17 00:00:00 2001 From: qvalentin Date: Fri, 17 May 2024 16:50:10 +0200 Subject: [PATCH 1/6] feat: read in dependency charts --- .gitignore | 2 + internal/adapter/yamlls/diagnostics.go | 2 + internal/adapter/yamlls/yamlls_test.go | 20 ++- internal/charts/chart.go | 131 +++++++++++++++++- internal/charts/chart_for_document.go | 4 +- internal/charts/chart_for_document_test.go | 24 +++- internal/charts/chart_store.go | 11 ++ internal/charts/chart_test.go | 81 ++++++++++- internal/charts/dependency_files.go | 47 +++++++ internal/charts/metadata.go | 9 ++ internal/charts/values_file.go | 30 ++++ internal/charts/values_file_test.go | 8 ++ internal/handler/completion.go | 1 + internal/handler/definition.go | 3 +- internal/handler/definition_chart_test.go | 121 ++++++++++++++++ internal/handler/definition_test.go | 77 +++++++++- internal/handler/initialization.go | 5 +- internal/handler/text_document.go | 32 ++--- internal/handler/text_document_test.go | 9 +- internal/language_features/includes.go | 9 +- .../language_features/template_context.go | 12 +- .../template_context_completion_test.go | 4 +- .../template_context_hover_test.go | 28 ++-- internal/lsp/document.go | 12 +- internal/lsp/document_store.go | 24 ++-- internal/protocol/hover.go | 5 + internal/protocol/hover_test.go | 83 +++++++++++ internal/util/yaml.go | 10 ++ testdata/dependenciesExample/.helmignore | 23 +++ testdata/dependenciesExample/Chart.lock | 9 ++ testdata/dependenciesExample/Chart.yaml | 32 +++++ .../charts/common-2.20.3.tgz | Bin 0 -> 16164 bytes .../charts/subchartexample/.helmignore | 23 +++ .../charts/subchartexample/Chart.yaml | 24 ++++ .../charts/subchartexample/values.yaml | 3 + .../dependenciesExample/templates/NOTES.txt | 22 +++ .../templates/_helpers.tpl | 62 +++++++++ .../templates/deployment.yaml | 71 ++++++++++ testdata/dependenciesExample/values.yaml | 54 ++++++++ 39 files changed, 1053 insertions(+), 74 deletions(-) create mode 100644 internal/charts/dependency_files.go create mode 100644 internal/handler/definition_chart_test.go create mode 100644 internal/protocol/hover_test.go create mode 100644 testdata/dependenciesExample/.helmignore create mode 100644 testdata/dependenciesExample/Chart.lock create mode 100644 testdata/dependenciesExample/Chart.yaml create mode 100644 testdata/dependenciesExample/charts/common-2.20.3.tgz create mode 100644 testdata/dependenciesExample/charts/subchartexample/.helmignore create mode 100644 testdata/dependenciesExample/charts/subchartexample/Chart.yaml create mode 100644 testdata/dependenciesExample/charts/subchartexample/values.yaml create mode 100644 testdata/dependenciesExample/templates/NOTES.txt create mode 100644 testdata/dependenciesExample/templates/_helpers.tpl create mode 100644 testdata/dependenciesExample/templates/deployment.yaml create mode 100644 testdata/dependenciesExample/values.yaml diff --git a/.gitignore b/.gitignore index a66957a3..f306bf19 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ __debug_bin* .vscode .coverage + +testdata/dependenciesExample/charts/.helm_ls_cache/ diff --git a/internal/adapter/yamlls/diagnostics.go b/internal/adapter/yamlls/diagnostics.go index f7fa48cb..7a600a44 100644 --- a/internal/adapter/yamlls/diagnostics.go +++ b/internal/adapter/yamlls/diagnostics.go @@ -2,6 +2,7 @@ package yamlls import ( "context" + "fmt" "runtime" "strings" @@ -15,6 +16,7 @@ func (c Connector) PublishDiagnostics(ctx context.Context, params *protocol.Publ doc, ok := c.documents.Get(params.URI) if !ok { logger.Println("Error handling diagnostic. Could not get document: " + params.URI.Filename()) + return fmt.Errorf("Could not get document: %s", params.URI.Filename()) } doc.DiagnosticsCache.SetYamlDiagnostics(filterDiagnostics(params.Diagnostics, doc.Ast.Copy(), doc.Content)) diff --git a/internal/adapter/yamlls/yamlls_test.go b/internal/adapter/yamlls/yamlls_test.go index af0ae636..b1a42d16 100644 --- a/internal/adapter/yamlls/yamlls_test.go +++ b/internal/adapter/yamlls/yamlls_test.go @@ -1,6 +1,7 @@ package yamlls import ( + "os" "testing" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" @@ -17,13 +18,20 @@ func TestIsRelevantFile(t *testing.T) { } connector.documents = &lsplocal.DocumentStore{} - yamlFile := uri.File("../../../testdata/example/templates/deployment.yaml") - nonYamlFile := uri.File("../../../testdata/example/templates/_helpers.tpl") - connector.documents.Store(yamlFile, util.DefaultConfig) - connector.documents.Store(nonYamlFile, util.DefaultConfig) + yamlFile := "../../../testdata/example/templates/deployment.yaml" + nonYamlFile := "../../../testdata/example/templates/_helpers.tpl" - assert.True(t, connector.isRelevantFile(yamlFile)) - assert.False(t, connector.isRelevantFile(nonYamlFile)) + yamlFileContent, err := os.ReadFile(yamlFile) + assert.NoError(t, err) + + nonYamlFileContent, err := os.ReadFile(nonYamlFile) + assert.NoError(t, err) + + connector.documents.Store(yamlFile, yamlFileContent, util.DefaultConfig) + connector.documents.Store(nonYamlFile, nonYamlFileContent, util.DefaultConfig) + + assert.True(t, connector.isRelevantFile(uri.File(yamlFile))) + assert.False(t, connector.isRelevantFile(uri.File(nonYamlFile))) } func TestShouldRun(t *testing.T) { diff --git a/internal/charts/chart.go b/internal/charts/chart.go index 2ca97c9f..a894a513 100644 --- a/internal/charts/chart.go +++ b/internal/charts/chart.go @@ -1,12 +1,17 @@ package charts import ( + "fmt" + "os" + "path/filepath" "strings" "github.com/mrjosh/helm-ls/internal/log" "github.com/mrjosh/helm-ls/internal/util" lsp "go.lsp.dev/protocol" "go.lsp.dev/uri" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" ) var logger = log.GetLogger() @@ -16,9 +21,12 @@ type Chart struct { ChartMetadata *ChartMetadata RootURI uri.URI ParentChart ParentChart + HelmChart *chart.Chart } func NewChart(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *Chart { + helmChart := loadHelmChart(rootURI) + return &Chart{ ValuesFiles: NewValuesFiles(rootURI, valuesFilesConfig.MainValuesFileName, @@ -27,9 +35,54 @@ func NewChart(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *Chart ChartMetadata: NewChartMetadata(rootURI), RootURI: rootURI, ParentChart: newParentChart(rootURI), + HelmChart: helmChart, + } +} + +func NewChartFromHelmChart(helmChart *chart.Chart, rootURI uri.URI) *Chart { + valuesFile := NewValuesFileFromValues(uri.File(filepath.Join(rootURI.Filename(), "values.yaml")), helmChart.Values) + + return &Chart{ + ValuesFiles: &ValuesFiles{ + MainValuesFile: valuesFile, + OverlayValuesFile: &ValuesFile{}, + AdditionalValuesFiles: []*ValuesFile{}, + }, + ChartMetadata: NewChartMetadataForDependencyChart(helmChart.Metadata, rootURI), + RootURI: rootURI, + ParentChart: ParentChart{}, + HelmChart: helmChart, } } +func (c *Chart) GetDependecyURI(dependencyName string) uri.URI { + unpackedPath := filepath.Join(c.RootURI.Filename(), "charts", dependencyName) + fileInfo, err := os.Stat(unpackedPath) + + if err == nil && fileInfo.IsDir() { + return uri.File(unpackedPath) + } + + return uri.File(filepath.Join(c.RootURI.Filename(), "charts", DependencyCacheFolder, dependencyName)) +} + +func loadHelmChart(rootURI uri.URI) *chart.Chart { + var helmChart *chart.Chart + + loader, err := loader.Loader(rootURI.Filename()) + if err != nil { + logger.Error(fmt.Sprintf("Error loading chart %s: %s", rootURI.Filename(), err.Error())) + return nil + } + + helmChart, err = loader.Load() + if err != nil { + logger.Error(fmt.Sprintf("Error loading chart %s: %s", rootURI.Filename(), err.Error())) + } + + return helmChart +} + type QueriedValuesFiles struct { Selector []string ValuesFiles *ValuesFiles @@ -38,27 +91,73 @@ type QueriedValuesFiles struct { // ResolveValueFiles returns a list of all values files in the chart // and all parent charts if the query tries to access global values func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) []*QueriedValuesFiles { - ownResult := []*QueriedValuesFiles{{Selector: query, ValuesFiles: c.ValuesFiles}} + if c == nil { + logger.Error("Could not resolve values files for nil chart") + return []*QueriedValuesFiles{} + } + // logger.Debug(fmt.Sprintf("Resolving values files for %s with query %s", c.HelmChart.Name(), query)) + result := []*QueriedValuesFiles{{Selector: query, ValuesFiles: c.ValuesFiles}} + if len(query) == 0 { - return ownResult + return result } - parentChart := c.ParentChart.GetParentChart(chartStore) - if parentChart == nil { - return ownResult + if c.HelmChart != nil { + result = c.resolveValuesFilesOfDependencies(query, chartStore, result) } + parentChart := c.ParentChart.GetParentChart(chartStore) + if query[0] == "global" { - return append(ownResult, + if parentChart == nil { + return result + } + + return append(result, parentChart.ResolveValueFiles(query, chartStore)...) } + if parentChart == nil { + return result + } + chartName := c.ChartMetadata.Metadata.Name extendedQuery := append([]string{chartName}, query...) - return append(ownResult, + + return append(result, parentChart.ResolveValueFiles(extendedQuery, chartStore)...) } +func (c *Chart) resolveValuesFilesOfDependencies(query []string, chartStore *ChartStore, result []*QueriedValuesFiles) []*QueriedValuesFiles { + for _, dependency := range c.HelmChart.Dependencies() { + logger.Debug(fmt.Sprintf("Resolving dependency %s with query %s", dependency.Name(), query)) + + if dependency.Name() == query[0] || query[0] == "global" { + subQuery := query + + if dependency.Name() == query[0] { + if len(query) > 1 { + subQuery = query[1:] + } + } + + dependencyChart := chartStore.Charts[c.GetDependecyURI(dependency.Name())] + if dependencyChart == nil { + logger.Error(fmt.Sprintf("Could not find dependency %s", dependency.Name())) + continue + } + + // TODO: why does this cause infinite recursion? + // result = append(result, dependencyChart.ResolveValueFiles(subQuery, chartStore)...) + + result = append(result, + &QueriedValuesFiles{Selector: subQuery, ValuesFiles: dependencyChart.ValuesFiles}) + } + } + + return result +} + func (c *Chart) GetValueLocation(templateContext []string) (lsp.Location, error) { modifyedVar := make([]string, len(templateContext)) // make the first letter lowercase since in the template the first letter is @@ -68,10 +167,28 @@ func (c *Chart) GetValueLocation(templateContext []string) (lsp.Location, error) if (len(value)) > 1 { restOfString = value[1:] } + firstLetterLowercase := strings.ToLower(string(value[0])) + restOfString modifyedVar = append(modifyedVar, firstLetterLowercase) } + position, err := util.GetPositionOfNode(&c.ChartMetadata.YamlNode, modifyedVar) return lsp.Location{URI: c.ChartMetadata.URI, Range: lsp.Range{Start: position}}, err } + +func (c *Chart) GetDependeciesTemplates() []*DependencyTemplateFile { + result := []*DependencyTemplateFile{} + if c.HelmChart == nil { + return result + } + + for _, dependency := range c.HelmChart.Dependencies() { + for _, file := range dependency.Templates { + dependencyTemplate := c.NewDependencyTemplateFile(dependency.Name(), file) + result = append(result, dependencyTemplate) + } + } + + return result +} diff --git a/internal/charts/chart_for_document.go b/internal/charts/chart_for_document.go index de7fd68c..5e8f4c70 100644 --- a/internal/charts/chart_for_document.go +++ b/internal/charts/chart_for_document.go @@ -18,12 +18,14 @@ func (s *ChartStore) GetChartForDoc(uri lsp.DocumentURI) (*Chart, error) { } chart, err := s.getChartFromFilesystemForTemplates(uri.Filename()) - s.Charts[chart.RootURI] = chart if err != nil { return chart, ErrChartNotFound{ URI: uri, } } + s.Charts[chart.RootURI] = chart + s.loadChartDependencies(chart) + return chart, nil } diff --git a/internal/charts/chart_for_document_test.go b/internal/charts/chart_for_document_test.go index d3996338..9aebb325 100644 --- a/internal/charts/chart_for_document_test.go +++ b/internal/charts/chart_for_document_test.go @@ -10,6 +10,7 @@ import ( "github.com/mrjosh/helm-ls/internal/util" "github.com/stretchr/testify/assert" "go.lsp.dev/uri" + "helm.sh/helm/v3/pkg/chart" ) func TestGetChartForDocumentWorksForAlreadyAddedCharts(t *testing.T) { @@ -53,7 +54,8 @@ func TestGetChartForDocumentWorksForNewToAddChart(t *testing.T) { rootDir = t.TempDir() expectedChartDirectory = filepath.Join(rootDir, "chart") expectedChart = &charts.Chart{ - RootURI: uri.File(expectedChartDirectory), + RootURI: uri.File(expectedChartDirectory), + HelmChart: &chart.Chart{}, } newChartFunc = func(_ uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return expectedChart } chartStore = charts.NewChartStore(uri.File(rootDir), newChartFunc) @@ -78,7 +80,8 @@ func TestGetChartForDocumentWorksForNewToAddChartWithNestedFile(t *testing.T) { rootDir = t.TempDir() expectedChartDirectory = filepath.Join(rootDir, "chart") expectedChart = &charts.Chart{ - RootURI: uri.File(expectedChartDirectory), + RootURI: uri.File(expectedChartDirectory), + HelmChart: &chart.Chart{}, } newChartFunc = func(_ uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return expectedChart } chartStore = charts.NewChartStore(uri.File(rootDir), newChartFunc) @@ -135,3 +138,20 @@ func TestGetChartOrParentForDocWorks(t *testing.T) { assert.Error(t, error) assert.Equal(t, &charts.Chart{RootURI: uri.File("/tmp")}, result5) } + +func TestGetChartForDocumentWorksForChartWithDependencies(t *testing.T) { + var ( + rootDir = "../../testdata/dependenciesExample/" + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + ) + + result1, error := chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) + assert.NoError(t, error) + + assert.Len(t, result1.HelmChart.Dependencies(), 2) + assert.Len(t, chartStore.Charts, 3) + + assert.NotNil(t, chartStore.Charts[uri.File(rootDir)]) + assert.NotNil(t, chartStore.Charts[uri.File(filepath.Join(rootDir, "charts", "subchartexample"))]) + assert.NotNil(t, chartStore.Charts[uri.File(filepath.Join(rootDir, "charts", charts.DependencyCacheFolder, "common"))]) +} diff --git a/internal/charts/chart_store.go b/internal/charts/chart_store.go index 8e336592..42ea2391 100644 --- a/internal/charts/chart_store.go +++ b/internal/charts/chart_store.go @@ -64,9 +64,20 @@ func (s *ChartStore) ReloadValuesFile(file uri.URI) { logger.Error("Error reloading values file", file, err) return } + for _, valuesFile := range chart.ValuesFiles.AllValuesFiles() { if valuesFile.URI == file { valuesFile.Reload() } } } + +func (s *ChartStore) loadChartDependencies(chart *Chart) { + for _, dependency := range chart.HelmChart.Dependencies() { + dependencyURI := chart.GetDependecyURI(dependency.Name()) + chart := NewChartFromHelmChart(dependency, dependencyURI) + + s.Charts[dependencyURI] = chart + s.loadChartDependencies(chart) + } +} diff --git a/internal/charts/chart_test.go b/internal/charts/chart_test.go index d2057304..ebfe5366 100644 --- a/internal/charts/chart_test.go +++ b/internal/charts/chart_test.go @@ -9,6 +9,7 @@ import ( "github.com/mrjosh/helm-ls/internal/util" "go.lsp.dev/uri" "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" "github.com/stretchr/testify/assert" ) @@ -76,16 +77,17 @@ func TestResolvesValuesFileOfParent(t *testing.T) { err = os.WriteFile(subChartChartFile, []byte{}, 0o644) assert.NoError(t, err) - chart := charts.NewChart(uri.File(filepath.Join(tempDir, "charts", "subchart")), util.ValuesFilesConfig{}) + sut := charts.NewChart(uri.File(filepath.Join(tempDir, "charts", "subchart")), util.ValuesFilesConfig{}) expectedChart := &charts.Chart{ RootURI: uri.File(tempDir), ChartMetadata: &charts.ChartMetadata{}, + HelmChart: &chart.Chart{}, } newChartFunc := func(_ uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return expectedChart } chartStore := charts.NewChartStore(uri.File(tempDir), newChartFunc) - valueFiles := chart.ResolveValueFiles([]string{"global", "foo"}, chartStore) + valueFiles := sut.ResolveValueFiles([]string{"global", "foo"}, chartStore) assert.Equal(t, 2, len(valueFiles)) } @@ -117,6 +119,7 @@ func TestResolvesValuesFileOfParentByName(t *testing.T) { Name: "parent", }, }, + HelmChart: &chart.Chart{}, } newChartFunc := func(_ uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return expectedChart } chartStore := charts.NewChartStore(uri.File(tempDir), newChartFunc) @@ -126,6 +129,78 @@ func TestResolvesValuesFileOfParentByName(t *testing.T) { parentChart, err := chartStore.GetChartForURI(uri.File(tempDir)) assert.NoError(t, err) - assert.Equal(t, 2, len(valueFiles)) + assert.Len(t, valueFiles, 2) assert.Contains(t, valueFiles, &charts.QueriedValuesFiles{Selector: []string{"subchart", "foo"}, ValuesFiles: parentChart.ValuesFiles}) } + +func TestResolvesValuesFileOfDependencyWithGlobal(t *testing.T) { + var ( + rootDir = "../../testdata/dependenciesExample" + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chart, err = chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) + valueFiles = chart.ResolveValueFiles([]string{"global"}, chartStore) + ) + + assert.NoError(t, err) + assert.Len(t, valueFiles, 3) + + selectors := [][]string{} + for _, valueFile := range valueFiles { + selectors = append(selectors, valueFile.Selector) + } + assert.Equal(t, selectors, [][]string{{"global"}, {"global"}, {"global"}}) +} + +func TestResolvesValuesFileOfDependencyWithChartName(t *testing.T) { + var ( + rootDir = "../../testdata/dependenciesExample" + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chart, err = chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) + valueFiles = chart.ResolveValueFiles([]string{"subchartexample", "foo"}, chartStore) + ) + + assert.NoError(t, err) + assert.Len(t, valueFiles, 2) + + selectors := [][]string{} + for _, valueFile := range valueFiles { + selectors = append(selectors, valueFile.Selector) + } + assert.Contains(t, selectors, []string{"subchartexample", "foo"}) + assert.Contains(t, selectors, []string{"foo"}) +} + +func TestResolvesValuesFileOfDependencyWithChartNameForPackedDependency(t *testing.T) { + var ( + rootDir = "../../testdata/dependenciesExample" + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chart, err = chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) + valueFiles = chart.ResolveValueFiles([]string{"common", "exampleValue"}, chartStore) + ) + + assert.NoError(t, err) + assert.Len(t, valueFiles, 2) + + selectors := [][]string{} + for _, valueFile := range valueFiles { + selectors = append(selectors, valueFile.Selector) + } + assert.Contains(t, selectors, []string{"common", "exampleValue"}) + assert.Contains(t, selectors, []string{"exampleValue"}) + + var commonValueFile *charts.ValuesFiles + for _, valueFile := range valueFiles { + if valueFile.Selector[0] == "exampleValue" { + commonValueFile = valueFile.ValuesFiles + } + } + assert.NotNil(t, commonValueFile) + assert.Equal(t, chartutil.Values{"exampleValue": "common-chart"}, commonValueFile.MainValuesFile.Values) +} + +func TestLoadsHelmChartWithDependecies(t *testing.T) { + chart := charts.NewChart(uri.File("../../testdata/dependenciesExample/"), util.ValuesFilesConfig{}) + + dependecyTemplates := chart.GetDependeciesTemplates() + assert.Len(t, dependecyTemplates, 21) +} diff --git a/internal/charts/dependency_files.go b/internal/charts/dependency_files.go new file mode 100644 index 00000000..8d205195 --- /dev/null +++ b/internal/charts/dependency_files.go @@ -0,0 +1,47 @@ +package charts + +import ( + "os" + "path/filepath" + "strings" + + "helm.sh/helm/v3/pkg/chart" +) + +type DependencyTemplateFile struct { + Content []byte + Path string +} + +var DependencyCacheFolder = ".helm_ls_cache" + +func (c *Chart) NewDependencyTemplateFile(chartName string, file *chart.File) *DependencyTemplateFile { + path := filepath.Join(c.RootURI.Filename(), "charts", DependencyCacheFolder, chartName, file.Name) + + return &DependencyTemplateFile{Content: file.Data, Path: path} +} + +type PossibleDependencyFile interface { + GetContent() string + GetPath() string +} + +// SyncToDisk writes the content of the document to disk if it is a dependency file. +// If it is a dependency file, it was read from a archive, so we need to write it back, +// to be able to open it in a editor when using go-to-definition or go-to-reference. +func SyncToDisk(d PossibleDependencyFile) { + if !IsDependencyFile(d) { + return + } + err := os.MkdirAll(filepath.Dir(d.GetPath()), 0o755) + if err == nil { + err = os.WriteFile(d.GetPath(), []byte(d.GetContent()), 0o444) + } + if err != nil { + logger.Error("Could not write dependency file", d.GetPath(), err) + } +} + +func IsDependencyFile(d PossibleDependencyFile) bool { + return strings.Contains(d.GetPath(), DependencyCacheFolder) +} diff --git a/internal/charts/metadata.go b/internal/charts/metadata.go index f5dd8e11..fe59bee7 100644 --- a/internal/charts/metadata.go +++ b/internal/charts/metadata.go @@ -30,6 +30,15 @@ func NewChartMetadata(rootURI uri.URI) *ChartMetadata { } } +// Create a new ChartMetadata for a dependency chart, omitting the YamlNode since this is +// likely not required for dependency charts +func NewChartMetadataForDependencyChart(metadata *chart.Metadata, URI uri.URI) *ChartMetadata { + return &ChartMetadata{ + Metadata: *metadata, + URI: URI, + } +} + func loadChartMetadata(filePath string) chart.Metadata { chartMetadata, err := chartutil.LoadChartfile(filePath) if err != nil { diff --git a/internal/charts/values_file.go b/internal/charts/values_file.go index a27385eb..1f35e291 100644 --- a/internal/charts/values_file.go +++ b/internal/charts/values_file.go @@ -1,6 +1,8 @@ package charts import ( + "fmt" + "github.com/mrjosh/helm-ls/internal/util" "go.lsp.dev/uri" "helm.sh/helm/v3/pkg/chartutil" @@ -24,6 +26,19 @@ func NewValuesFile(filePath string) *ValuesFile { } } +func NewValuesFileFromValues(uri uri.URI, values chartutil.Values) *ValuesFile { + valueNode, error := util.ValuesToYamlNode(values) + if error != nil { + logger.Error(fmt.Sprintf("Could not load values for file %s", uri.Filename()), error) + return &ValuesFile{} + } + return &ValuesFile{ + ValueNode: valueNode, + Values: values, + URI: uri, + } +} + func (v *ValuesFile) Reload() { vals, valueNodes := readInValuesFile(v.URI.Filename()) @@ -44,3 +59,18 @@ func readInValuesFile(filePath string) (chartutil.Values, yaml.Node) { } return vals, valueNodes } + +// GetContent implements PossibleDependencyFile. +func (d *ValuesFile) GetContent() string { + yaml, err := yaml.Marshal(d.Values) + if err != nil { + logger.Error(fmt.Sprintf("Could not load values for file %s", d.URI.Filename()), err) + return "" + } + return string(yaml) +} + +// GetPath implements PossibleDependencyFile. +func (d *ValuesFile) GetPath() string { + return d.URI.Filename() +} diff --git a/internal/charts/values_file_test.go b/internal/charts/values_file_test.go index 64830f96..2a4cf76d 100644 --- a/internal/charts/values_file_test.go +++ b/internal/charts/values_file_test.go @@ -45,3 +45,11 @@ func TestReload(t *testing.T) { assert.Equal(t, "baz", valuesFile.Values["foo"]) assert.NotEqual(t, yaml.Node{}, valuesFile.ValueNode) } + +func TestGetContent(t *testing.T) { + tempDir := t.TempDir() + valuesContent := "foo: bar" + _ = os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte(valuesContent), 0o644) + valuesFile := charts.NewValuesFile(filepath.Join(tempDir, "values.yaml")) + assert.Equal(t, valuesContent+"\n", valuesFile.GetContent()) +} diff --git a/internal/handler/completion.go b/internal/handler/completion.go index 2a7104ce..93eb4e80 100644 --- a/internal/handler/completion.go +++ b/internal/handler/completion.go @@ -13,6 +13,7 @@ import ( func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionParams) (result *lsp.CompletionList, err error) { logger.Debug("Running completion with params", params) + genericDocumentUseCase, err := h.NewGenericDocumentUseCase(params.TextDocumentPositionParams, lsplocal.NestedNodeAtPositionForCompletion) if err != nil { return nil, err diff --git a/internal/handler/definition.go b/internal/handler/definition.go index c62560d9..7cd2259a 100644 --- a/internal/handler/definition.go +++ b/internal/handler/definition.go @@ -13,11 +13,12 @@ func (h *langHandler) Definition(_ context.Context, params *lsp.DefinitionParams if err != nil { return nil, err } + usecases := []languagefeatures.DefinitionUseCase{ languagefeatures.NewBuiltInObjectsFeature(genericDocumentUseCase), // has to be before template context + languagefeatures.NewVariablesFeature(genericDocumentUseCase), languagefeatures.NewTemplateContextFeature(genericDocumentUseCase), languagefeatures.NewIncludesCallFeature(genericDocumentUseCase), - languagefeatures.NewVariablesFeature(genericDocumentUseCase), } for _, usecase := range usecases { diff --git a/internal/handler/definition_chart_test.go b/internal/handler/definition_chart_test.go new file mode 100644 index 00000000..d060ddc2 --- /dev/null +++ b/internal/handler/definition_chart_test.go @@ -0,0 +1,121 @@ +package handler + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/mrjosh/helm-ls/internal/adapter/yamlls" + "github.com/mrjosh/helm-ls/internal/charts" + lsplocal "github.com/mrjosh/helm-ls/internal/lsp" + "github.com/mrjosh/helm-ls/internal/util" + "github.com/stretchr/testify/assert" + lsp "go.lsp.dev/protocol" + "go.lsp.dev/uri" +) + +var ( + rootUri = uri.File("../../testdata/dependenciesExample/") + fileURI = uri.File("../../testdata/dependenciesExample/templates/deployment.yaml") +) + +type testCase struct { + // Must be content of a line in the file fileURI + templateLineWithMarker string + expectedFile string + expectedStartPosition lsp.Position + expectedError error +} + +func TestDefinitionChart(t *testing.T) { + testCases := []testCase{ + { + `{{ include "common.na^mes.name" . }}`, + "charts/.helm_ls_cache/common/templates/_names.tpl", + lsp.Position{Line: 9, Character: 0}, + nil, + }, + { + `{{- include "dependeciesEx^ample.labels" . | nindent 4 }}`, + "templates/_helpers.tpl", + lsp.Position{Line: 35, Character: 0}, + nil, + }, + { + `{{ .Values.gl^obal.subchart }}`, + "values.yaml", + lsp.Position{Line: 7, Character: 0}, + nil, + }, + } + + fileContent, err := os.ReadFile(fileURI.Filename()) + if err != nil { + t.Fatal(err) + } + lines := strings.Split(string(fileContent), "\n") + for _, tC := range testCases { + t.Run("Definition on "+tC.templateLineWithMarker, func(t *testing.T) { + pos, found := getPosition(tC, lines) + if !found { + t.Fatal(fmt.Sprintf("%s is not in the file %s", tC.templateLineWithMarker, fileURI.Filename())) + } + + documents := lsplocal.NewDocumentStore() + + chart := charts.NewChart(rootUri, util.DefaultConfig.ValuesFilesConfig) + + chartStore := charts.NewChartStore(rootUri, charts.NewChart) + chartStore.Charts = map[uri.URI]*charts.Chart{rootUri: chart} + h := &langHandler{ + chartStore: chartStore, + documents: documents, + yamllsConnector: &yamlls.Connector{}, + helmlsConfig: util.DefaultConfig, + } + + h.LoadDocsOnNewChart(chart) + + locations, err := h.Definition(context.TODO(), &lsp.DefinitionParams{ + TextDocumentPositionParams: lsp.TextDocumentPositionParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: fileURI}, + Position: pos, + }, + }) + + assert.Equal(t, tC.expectedError, err) + assert.Len(t, locations, 1) + if len(locations) > 0 { + assert.Equal(t, filepath.Join(rootUri.Filename(), tC.expectedFile), locations[0].URI.Filename()) + assert.Equal(t, tC.expectedStartPosition, locations[0].Range.Start) + } + + for _, location := range locations { + assert.FileExists(t, location.URI.Filename()) + } + + os.RemoveAll(filepath.Join(rootUri.Filename(), "charts", charts.DependencyCacheFolder)) + }) + } +} + +func getPosition(tC testCase, lines []string) (lsp.Position, bool) { + col := strings.Index(tC.templateLineWithMarker, "^") + buf := strings.Replace(tC.templateLineWithMarker, "^", "", 1) + line := uint32(0) + found := false + + for i, v := range lines { + if strings.Contains(v, buf) { + found = true + line = uint32(i) + col = col + strings.Index(v, buf) + break + } + } + pos := lsp.Position{Line: line, Character: uint32(col)} + return pos, found +} diff --git a/internal/handler/definition_test.go b/internal/handler/definition_test.go index 6169cd92..3a52f7fc 100644 --- a/internal/handler/definition_test.go +++ b/internal/handler/definition_test.go @@ -3,6 +3,7 @@ package handler import ( "context" "reflect" + "strings" "testing" "github.com/mrjosh/helm-ls/internal/adapter/yamlls" @@ -10,9 +11,11 @@ import ( lsplocal "github.com/mrjosh/helm-ls/internal/lsp" "github.com/mrjosh/helm-ls/internal/util" "github.com/stretchr/testify/assert" + "go.lsp.dev/protocol" lsp "go.lsp.dev/protocol" "go.lsp.dev/uri" yamlv3 "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/chart" ) var testFileContent = ` @@ -56,7 +59,7 @@ func genericDefinitionTest(t *testing.T, position lsp.Position, expectedLocation fileURI := testDocumentTemplateURI rootUri := uri.File("/") - chart := &charts.Chart{ + testChart := &charts.Chart{ ChartMetadata: &charts.ChartMetadata{}, ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{ @@ -66,7 +69,8 @@ func genericDefinitionTest(t *testing.T, position lsp.Position, expectedLocation }, AdditionalValuesFiles: []*charts.ValuesFile{}, }, - RootURI: "", + RootURI: "", + HelmChart: &chart.Chart{}, } d := lsp.DidOpenTextDocumentParams{ TextDocument: lsp.TextDocumentItem{ @@ -78,7 +82,7 @@ func genericDefinitionTest(t *testing.T, position lsp.Position, expectedLocation } documents.DidOpen(&d, util.DefaultConfig) chartStore := charts.NewChartStore(rootUri, charts.NewChart) - chartStore.Charts = map[uri.URI]*charts.Chart{rootUri: chart} + chartStore.Charts = map[uri.URI]*charts.Chart{rootUri: testChart} h := &langHandler{ chartStore: chartStore, documents: documents, @@ -327,3 +331,70 @@ func TestDefinitionValueFileMulitpleValues(t *testing.T) { }, }, nil) } + +func TestDefinitionSingleLine(t *testing.T) { + testCases := []struct { + // defines a definition test where ^ is the position where the defintion is triggered + // and §result§ marks the range of the result + templateWithMarks string + }{ + {"{{ §$test := 1§ }} {{ $te^st }}"}, + {"{{ §$test := .Values.test§ }} {{ $te^st.with.selectorexpression }}"}, + {"{{ §$test := $.Values.test§ }} {{ $te^st.with.selectorexpression. }}"}, + {"{{ §$test := .Values.test§ }} {{ $te^st }}"}, + {"{{ range §$test := .Values.test§ }} {{ $te^st }} {{ end }}"}, + {"{{ range §$test := $.Values.test§ }} {{ $te^st.something }} {{ end }}"}, + {"{{ range §$test := $.Values.test§ }} {{ $te^st. }} {{ end }}"}, + {"{{ range §$test := $.Values.test§ }} {{ if not $te^st }} y {{ else }} n {{ end }}"}, + } + for _, tc := range testCases { + t.Run(tc.templateWithMarks, func(t *testing.T) { + col := strings.Index(tc.templateWithMarks, "^") + buf := strings.Replace(tc.templateWithMarks, "^", "", 1) + pos := protocol.Position{Line: 0, Character: uint32(col - 3)} + expectedColStart := strings.Index(buf, "§") + buf = strings.Replace(buf, "§", "", 1) + expectedColEnd := strings.Index(buf, "§") + buf = strings.Replace(buf, "§", "", 1) + + documents := lsplocal.NewDocumentStore() + fileURI := testDocumentTemplateURI + rootUri := uri.File("/") + + d := lsp.DidOpenTextDocumentParams{ + TextDocument: lsp.TextDocumentItem{ + URI: fileURI, + Text: buf, + }, + } + documents.DidOpen(&d, util.DefaultConfig) + h := &langHandler{ + chartStore: charts.NewChartStore(rootUri, charts.NewChart), + documents: documents, + } + + locations, err := h.Definition(context.TODO(), &lsp.DefinitionParams{ + TextDocumentPositionParams: lsp.TextDocumentPositionParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: fileURI}, + Position: pos, + }, + }) + + assert.NoError(t, err) + + assert.Contains(t, locations, lsp.Location{ + URI: testDocumentTemplateURI, + Range: lsp.Range{ + Start: lsp.Position{ + Line: 0, + Character: uint32(expectedColStart), + }, + End: lsp.Position{ + Line: 0, + Character: uint32(expectedColEnd), + }, + }, + }) + }) + } +} diff --git a/internal/handler/initialization.go b/internal/handler/initialization.go index c372a056..b5497d63 100644 --- a/internal/handler/initialization.go +++ b/internal/handler/initialization.go @@ -97,6 +97,7 @@ func configureLogLevel(helmlsConfig util.HelmlsConfiguration) { } func (h *langHandler) NewChartWithInitActions(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *charts.Chart { - go h.LoadDocsOnNewChart(rootURI) - return h.NewChartWithWatchedFiles(rootURI, valuesFilesConfig) + chart := h.NewChartWithWatchedFiles(rootURI, valuesFilesConfig) + go h.LoadDocsOnNewChart(chart) + return chart } diff --git a/internal/handler/text_document.go b/internal/handler/text_document.go index dbde3de9..4ad67bf0 100644 --- a/internal/handler/text_document.go +++ b/internal/handler/text_document.go @@ -3,12 +3,12 @@ package handler import ( "context" "errors" - "io/fs" + "fmt" "path/filepath" + "github.com/mrjosh/helm-ls/internal/charts" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" lsp "go.lsp.dev/protocol" - "go.lsp.dev/uri" ) func (h *langHandler) DidOpen(ctx context.Context, params *lsp.DidOpenTextDocumentParams) (err error) { @@ -57,7 +57,7 @@ func (h *langHandler) DidSave(ctx context.Context, params *lsp.DidSaveTextDocume return nil } -func (h *langHandler) DidChange(ctx context.Context, params *lsp.DidChangeTextDocumentParams) (err error) { +func (h *langHandler) DidChange(_ context.Context, params *lsp.DidChangeTextDocumentParams) (err error) { doc, ok := h.documents.Get(params.TextDocument.URI) if !ok { return errors.New("Could not get document: " + params.TextDocument.URI.Filename()) @@ -102,17 +102,17 @@ func (h *langHandler) DidRenameFiles(ctx context.Context, params *lsp.RenameFile return nil } -// TODO: maybe use the helm implementation of this once https://github.com/mrjosh/helm-ls/pull/77 is resolved -func (h *langHandler) LoadDocsOnNewChart(rootURI uri.URI) { - _ = filepath.WalkDir(filepath.Join(rootURI.Filename(), "templates"), - func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if !d.IsDir() { - return h.documents.Store(uri.File(path), h.helmlsConfig) - } - return nil - }, - ) +func (h *langHandler) LoadDocsOnNewChart(chart *charts.Chart) { + if chart.HelmChart == nil { + return + } + + for _, file := range chart.HelmChart.Templates { + h.documents.Store(filepath.Join(chart.RootURI.Filename(), file.Name), file.Data, h.helmlsConfig) + } + + for _, file := range chart.GetDependeciesTemplates() { + logger.Debug(fmt.Sprintf("Storing dependency %s", file.Path)) + h.documents.Store(file.Path, file.Content, h.helmlsConfig) + } } diff --git a/internal/handler/text_document_test.go b/internal/handler/text_document_test.go index e74c9e90..1a1319e4 100644 --- a/internal/handler/text_document_test.go +++ b/internal/handler/text_document_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/mrjosh/helm-ls/internal/charts" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" "github.com/mrjosh/helm-ls/internal/util" "github.com/stretchr/testify/assert" @@ -35,7 +36,7 @@ func TestLoadDocsOnNewChart(t *testing.T) { helmlsConfig: util.DefaultConfig, } - h.LoadDocsOnNewChart(rootURI) + h.LoadDocsOnNewChart(charts.NewChart(rootURI, util.DefaultConfig.ValuesFilesConfig)) for _, file := range templateFiles { doc, ok := h.documents.Get(uri.File(file)) @@ -70,7 +71,7 @@ func TestLoadDocsOnNewChartDoesNotOverwrite(t *testing.T) { }, }, util.DefaultConfig) - h.LoadDocsOnNewChart(rootURI) + h.LoadDocsOnNewChart(charts.NewChart(rootURI, util.DefaultConfig.ValuesFilesConfig)) doc, ok := h.documents.Get(uri.File(templateFile)) assert.True(t, ok) @@ -89,7 +90,7 @@ func TestLoadDocsOnNewChartWorksForMissingTemplateDir(t *testing.T) { helmlsConfig: util.DefaultConfig, } - h.LoadDocsOnNewChart(rootURI) + h.LoadDocsOnNewChart(charts.NewChart(rootURI, util.DefaultConfig.ValuesFilesConfig)) - h.LoadDocsOnNewChart(uri.File("non-existent-dir/hkjgfdshgkjfd")) + h.LoadDocsOnNewChart(charts.NewChart(uri.File("NonExisting"), util.DefaultConfig.ValuesFilesConfig)) } diff --git a/internal/language_features/includes.go b/internal/language_features/includes.go index 656a59ad..d0a9746e 100644 --- a/internal/language_features/includes.go +++ b/internal/language_features/includes.go @@ -3,6 +3,7 @@ package languagefeatures import ( lsp "go.lsp.dev/protocol" + "github.com/mrjosh/helm-ls/internal/charts" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" "github.com/mrjosh/helm-ls/internal/protocol" "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate" @@ -90,6 +91,9 @@ func (f *IncludesFeature) getReferenceLocations(includeName string) []lsp.Locati for _, referenceRange := range referenceRanges { locations = append(locations, util.RangeToLocation(doc.URI, referenceRange)) } + if len(locations) > 0 { + charts.SyncToDisk(doc) + } } return locations @@ -102,6 +106,9 @@ func (f *IncludesFeature) getDefinitionLocations(includeName string) []lsp.Locat for _, referenceRange := range referenceRanges { locations = append(locations, util.RangeToLocation(doc.URI, referenceRange)) } + if len(locations) > 0 { + charts.SyncToDisk(doc) + } } return locations @@ -117,7 +124,7 @@ func (f *IncludesFeature) getDefinitionsHover(includeName string) protocol.Hover result = append(result, protocol.HoverResultWithFile{ Value: node.Content([]byte(doc.Content)), URI: doc.URI, - }) + }.AsHelmCode()) } } } diff --git a/internal/language_features/template_context.go b/internal/language_features/template_context.go index cf088edf..614fda78 100644 --- a/internal/language_features/template_context.go +++ b/internal/language_features/template_context.go @@ -3,9 +3,11 @@ package languagefeatures import ( "fmt" "reflect" + "strings" lsp "go.lsp.dev/protocol" + "github.com/mrjosh/helm-ls/internal/charts" helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" "github.com/mrjosh/helm-ls/internal/protocol" @@ -69,7 +71,13 @@ func (f *TemplateContextFeature) getDefinitionLocations(templateContext lsplocal switch templateContext[0] { case "Values": for _, value := range f.Chart.ResolveValueFiles(templateContext.Tail(), f.ChartStore) { - locations = append(locations, value.ValuesFiles.GetPositionsForValue(value.Selector)...) + locs := value.ValuesFiles.GetPositionsForValue(value.Selector) + if len(locs) > 0 { + for _, valuesFile := range value.ValuesFiles.AllValuesFiles() { + charts.SyncToDisk(valuesFile) + } + } + locations = append(locations, locs...) } return locations case "Chart": @@ -107,7 +115,9 @@ func (f *TemplateContextFeature) valuesHover(templateContext lsplocal.TemplateCo ) for _, valuesFiles := range valuesFiles { for _, valuesFile := range valuesFiles.ValuesFiles.AllValuesFiles() { + logger.Debug(fmt.Sprintf("Looking for selector: %s in values %v", strings.Join(valuesFiles.Selector, "."), valuesFile.Values)) result, err := util.GetTableOrValueForSelector(valuesFile.Values, valuesFiles.Selector) + if err == nil { hoverResults = append(hoverResults, protocol.HoverResultWithFile{URI: valuesFile.URI, Value: result}) } diff --git a/internal/language_features/template_context_completion_test.go b/internal/language_features/template_context_completion_test.go index 3ee0a594..c85a84b1 100644 --- a/internal/language_features/template_context_completion_test.go +++ b/internal/language_features/template_context_completion_test.go @@ -30,7 +30,7 @@ func TestGetValuesCompletions(t *testing.T) { }, }, }, - RootURI: "", + RootURI: "", HelmChart: &chart.Chart{}, } templateConextFeature := TemplateContextFeature{ @@ -69,7 +69,7 @@ func TestGetValuesCompletionsContainsNoDupliactes(t *testing.T) { }, }, }, - RootURI: "", + RootURI: "", HelmChart: &chart.Chart{}, } templateConextFeature := TemplateContextFeature{ diff --git a/internal/language_features/template_context_hover_test.go b/internal/language_features/template_context_hover_test.go index 2df5f468..02c92e47 100644 --- a/internal/language_features/template_context_hover_test.go +++ b/internal/language_features/template_context_hover_test.go @@ -13,9 +13,9 @@ import ( func Test_langHandler_getValueHover(t *testing.T) { type args struct { - chart *charts.Chart - parentCharts map[uri.URI]*charts.Chart - splittedVar []string + chart *charts.Chart + chartsInStore map[uri.URI]*charts.Chart + splittedVar []string } tests := []struct { name string @@ -36,6 +36,7 @@ func Test_langHandler_getValueHover(t *testing.T) { URI: "file://tmp/values.yaml", }, }, + HelmChart: &chart.Chart{}, }, splittedVar: []string{"key"}, }, @@ -56,6 +57,7 @@ value MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"key": "value"}, URI: "file://tmp/values.yaml"}, AdditionalValuesFiles: []*charts.ValuesFile{{Values: map[string]interface{}{"key": ""}, URI: "file://tmp/values.other.yaml"}}, }, + HelmChart: &chart.Chart{}, }, splittedVar: []string{"key"}, }, @@ -78,6 +80,7 @@ value ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"key": map[string]interface{}{"nested": "value"}}, URI: "file://tmp/values.yaml"}, }, + HelmChart: &chart.Chart{}, }, splittedVar: []string{"key"}, }, @@ -98,6 +101,7 @@ nested: value ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"key": []map[string]interface{}{{"nested": "value"}}}, URI: "file://tmp/values.yaml"}, }, + HelmChart: &chart.Chart{}, }, splittedVar: []string{"key"}, }, @@ -123,13 +127,15 @@ key: ParentChartURI: uri.New("file://tmp/"), HasParent: true, }, + HelmChart: &chart.Chart{}, }, - parentCharts: map[uri.URI]*charts.Chart{ + chartsInStore: map[uri.URI]*charts.Chart{ uri.New("file://tmp/"): { ChartMetadata: &charts.ChartMetadata{}, ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"global": map[string]interface{}{"key": "parentValue"}}, URI: "file://tmp/values.yaml"}, }, + HelmChart: &chart.Chart{}, }, }, splittedVar: []string{"global", "key"}, @@ -161,13 +167,15 @@ value ParentChartURI: uri.New("file://tmp/"), HasParent: true, }, + HelmChart: &chart.Chart{}, }, - parentCharts: map[uri.URI]*charts.Chart{ + chartsInStore: map[uri.URI]*charts.Chart{ uri.New("file://tmp/"): { ChartMetadata: &charts.ChartMetadata{}, ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"subchart": map[string]interface{}{"key": "parentValue"}}, URI: "file://tmp/values.yaml"}, }, + HelmChart: &chart.Chart{}, }, }, splittedVar: []string{"key"}, @@ -197,8 +205,9 @@ value ParentChartURI: uri.New("file://tmp/charts/subchart"), HasParent: true, }, + HelmChart: &chart.Chart{}, }, - parentCharts: map[uri.URI]*charts.Chart{ + chartsInStore: map[uri.URI]*charts.Chart{ uri.New("file://tmp/charts/subchart"): { ChartMetadata: &charts.ChartMetadata{Metadata: chart.Metadata{Name: "subchart"}}, ValuesFiles: &charts.ValuesFiles{ @@ -208,6 +217,7 @@ value ParentChartURI: uri.New("file://tmp/"), HasParent: true, }, + HelmChart: &chart.Chart{}, }, uri.New("file://tmp/"): { ChartMetadata: &charts.ChartMetadata{ @@ -216,6 +226,7 @@ value ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"subchart": map[string]interface{}{"subsubchart": map[string]interface{}{"key": "parentValue"}}}, URI: "file://tmp/values.yaml"}, }, + HelmChart: &chart.Chart{}, }, }, splittedVar: []string{"key"}, @@ -251,6 +262,7 @@ value URI: "file://tmp/values.yaml", }, }, + HelmChart: &chart.Chart{}, }, splittedVar: []string{"key"}, }, @@ -275,6 +287,7 @@ value URI: "file://tmp/values.yaml", }, }, + HelmChart: &chart.Chart{}, }, splittedVar: []string{"key[]"}, }, @@ -293,9 +306,8 @@ hello Chart: tt.args.chart, ChartStore: &charts.ChartStore{ RootURI: uri.New("file://tmp/"), - Charts: tt.args.parentCharts, + Charts: tt.args.chartsInStore, }, - // Node: tt.args.chart.ValuesFiles.MainValuesFile.Node, } valuesFeature := NewTemplateContextFeature(genericDocumentUseCase) got, err := valuesFeature.valuesHover(tt.args.splittedVar) diff --git a/internal/lsp/document.go b/internal/lsp/document.go index f20ffbf3..cf58a273 100644 --- a/internal/lsp/document.go +++ b/internal/lsp/document.go @@ -45,7 +45,7 @@ func (d *Document) ApplyChanges(changes []lsp.TextDocumentContentChangeEvent) { d.Content = string(content) d.ApplyChangesToAst(d.Content) - d.SymbolTable = NewSymbolTable(d.Ast, []byte(d.Content)) + d.SymbolTable = NewSymbolTable(d.Ast, content) d.lines = nil } @@ -78,3 +78,13 @@ func (d *Document) getLines() []string { } return d.lines } + +// GetContent implements PossibleDependencyFile. +func (d *Document) GetContent() string { + return d.Content +} + +// GetPath implements PossibleDependencyFile. +func (d *Document) GetPath() string { + return d.Path +} diff --git a/internal/lsp/document_store.go b/internal/lsp/document_store.go index 46432e77..025ae07b 100644 --- a/internal/lsp/document_store.go +++ b/internal/lsp/document_store.go @@ -2,7 +2,6 @@ package lsp import ( "fmt" - "os" "sync" "github.com/mrjosh/helm-ls/internal/util" @@ -51,32 +50,25 @@ func (s *DocumentStore) DidOpen(params *lsp.DidOpenTextDocumentParams, helmlsCon return doc, nil } -func (s *DocumentStore) Store(uri uri.URI, helmlsConfig util.HelmlsConfiguration) error { - _, ok := s.documents.Load(uri.Filename()) +func (s *DocumentStore) Store(filename string, content []byte, helmlsConfig util.HelmlsConfiguration) { + _, ok := s.documents.Load(filename) if ok { - return nil + return } - - content, err := os.ReadFile(uri.Filename()) - if err != nil { - logger.Error("Could not open file ", uri.Filename(), " ", err) - return err - } - ast := ParseAst(nil, string(content)) - s.documents.Store(uri.Filename(), + fileUri := uri.File(filename) + s.documents.Store(fileUri.Filename(), &Document{ - URI: uri, - Path: uri.Filename(), + URI: fileUri, + Path: filename, Content: string(content), Ast: ast, DiagnosticsCache: NewDiagnosticsCache(helmlsConfig), IsOpen: false, SymbolTable: NewSymbolTable(ast, content), - IsYaml: IsYamlDocument(uri, helmlsConfig.YamllsConfiguration), + IsYaml: IsYamlDocument(fileUri, helmlsConfig.YamllsConfiguration), }, ) - return nil } func (s *DocumentStore) Get(docuri uri.URI) (*Document, bool) { diff --git a/internal/protocol/hover.go b/internal/protocol/hover.go index 3b97ba62..caab89ec 100644 --- a/internal/protocol/hover.go +++ b/internal/protocol/hover.go @@ -14,6 +14,11 @@ type HoverResultWithFile struct { URI uri.URI } +func (h HoverResultWithFile) AsHelmCode() HoverResultWithFile { + h.Value = fmt.Sprintf("```%s\n%s\n```", "helm", h.Value) + return h +} + type HoverResultsWithFiles []HoverResultWithFile func (h HoverResultsWithFiles) Format(rootURI uri.URI) string { diff --git a/internal/protocol/hover_test.go b/internal/protocol/hover_test.go new file mode 100644 index 00000000..f1d1b59d --- /dev/null +++ b/internal/protocol/hover_test.go @@ -0,0 +1,83 @@ +package protocol + +import ( + "fmt" + "path/filepath" + "testing" + + "go.lsp.dev/uri" + + "github.com/stretchr/testify/assert" +) + +func TestHoverResultsWithFiles_Format(t *testing.T) { + rootURI := uri.New("file:///home/user/project") + + results := HoverResultsWithFiles{ + {Value: "value1", URI: uri.New("file:///home/user/project/file1.yaml")}, + {Value: "value2", URI: uri.New("file:///home/user/project/file2.yaml")}, + {Value: "value3", URI: uri.New("file:///home/user/project/file3.yaml")}, + } + + expected := fmt.Sprintf(`### file3.yaml +%s +value3 +%s + +### file2.yaml +%s +value2 +%s + +### file1.yaml +%s +value1 +%s + +`, "```yaml", "```", "```yaml", "```", "```yaml", "```") + + formatted := results.Format(rootURI) + assert.Equal(t, expected, formatted, "The formatted output should match the expected output") +} + +func TestHoverResultsWithFiles_Format_EmptyValue(t *testing.T) { + rootURI := uri.New("file:///home/user/project") + + results := HoverResultsWithFiles{ + {Value: "", URI: uri.New("file:///home/user/project/file1.yaml")}, + } + expected := `### file1.yaml +"" + +` + + formatted := results.Format(rootURI) + assert.Equal(t, expected, formatted, "The formatted output should match the expected output") +} + +func TestHoverResultsWithFiles_Format_DifferenPath(t *testing.T) { + rootURI := uri.New("file:///home/user/project") + + results := HoverResultsWithFiles{ + {Value: "value", URI: uri.New("file:///invalid/uri")}, + } + + expected := fmt.Sprintf(`### %s +%s +value +%s + +`, filepath.Join("..", "..", "..", "invalid", "uri"), "```yaml", "```") + formatted := results.Format(rootURI) + assert.Equal(t, expected, formatted, "The formatted output should match the expected output") +} + +func TestHoverResultWithFile_WithHelmCode(t *testing.T) { + hoverResult := HoverResultWithFile{ + Value: "some helm code", + URI: uri.New("file:///home/user/project/file1.yaml"), + }.AsHelmCode() + + expectedValue := "```helm\nsome helm code\n```" + assert.Equal(t, expectedValue, hoverResult.Value, "The value should be formatted with Helm code block") +} diff --git a/internal/util/yaml.go b/internal/util/yaml.go index ec2b9fea..6f0b34ba 100644 --- a/internal/util/yaml.go +++ b/internal/util/yaml.go @@ -7,6 +7,7 @@ import ( lsp "go.lsp.dev/protocol" yamlv3 "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/chartutil" ) func GetPositionOfNode(node *yamlv3.Node, query []string) (lsp.Position, error) { @@ -85,3 +86,12 @@ func ReadYamlFileToNode(filename string) (node yamlv3.Node, err error) { err = yamlv3.Unmarshal(data, &node) return node, err } + +func ValuesToYamlNode(values chartutil.Values) (node yamlv3.Node, err error) { + yaml, error := values.YAML() + if error != nil { + return yamlv3.Node{}, error + } + err = yamlv3.Unmarshal([]byte(yaml), &node) + return node, err +} diff --git a/testdata/dependenciesExample/.helmignore b/testdata/dependenciesExample/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/testdata/dependenciesExample/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/testdata/dependenciesExample/Chart.lock b/testdata/dependenciesExample/Chart.lock new file mode 100644 index 00000000..9d2ff4d0 --- /dev/null +++ b/testdata/dependenciesExample/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: common + repository: oci://registry-1.docker.io/bitnamicharts + version: 2.20.3 +- name: subchartexample + repository: "" + version: x.x.x +digest: sha256:17700eb1463d461f8c1a70e1ab96fadd0749adccf9970b6acb5b8c791374ac8a +generated: "2024-07-02T11:38:56.75677057Z" diff --git a/testdata/dependenciesExample/Chart.yaml b/testdata/dependenciesExample/Chart.yaml new file mode 100644 index 00000000..1049faca --- /dev/null +++ b/testdata/dependenciesExample/Chart.yaml @@ -0,0 +1,32 @@ +apiVersion: v2 +name: dependeciesExample +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" +dependencies: + - name: common + repository: oci://registry-1.docker.io/bitnamicharts + tags: + - bitnami-common + version: 2.20.3 + - name: subchartexample + version: x.x.x diff --git a/testdata/dependenciesExample/charts/common-2.20.3.tgz b/testdata/dependenciesExample/charts/common-2.20.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..8624f69bbb31a6d394b6b2aa08582dd04e032b3e GIT binary patch literal 16164 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

ymCh$<0K*VJ;2g;TD}TNX@BHQFcRhYH2@Sun&> zeRYo+qJaupoA$XI=U?=+2H#)vrPk;AYkH~9^Z(T0--qYnd3gSXpZ^blrpY}3kO2UE C$j|x! literal 0 HcmV?d00001 diff --git a/testdata/dependenciesExample/charts/subchartexample/.helmignore b/testdata/dependenciesExample/charts/subchartexample/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/testdata/dependenciesExample/charts/subchartexample/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/testdata/dependenciesExample/charts/subchartexample/Chart.yaml b/testdata/dependenciesExample/charts/subchartexample/Chart.yaml new file mode 100644 index 00000000..1df061e3 --- /dev/null +++ b/testdata/dependenciesExample/charts/subchartexample/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: subchartexample +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/testdata/dependenciesExample/charts/subchartexample/values.yaml b/testdata/dependenciesExample/charts/subchartexample/values.yaml new file mode 100644 index 00000000..6e49e9d3 --- /dev/null +++ b/testdata/dependenciesExample/charts/subchartexample/values.yaml @@ -0,0 +1,3 @@ +global: + subchart: works +subchartWithoutGlobal: works diff --git a/testdata/dependenciesExample/templates/NOTES.txt b/testdata/dependenciesExample/templates/NOTES.txt new file mode 100644 index 00000000..f2bda6bc --- /dev/null +++ b/testdata/dependenciesExample/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "dependeciesExample.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "dependeciesExample.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "dependeciesExample.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "dependeciesExample.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/testdata/dependenciesExample/templates/_helpers.tpl b/testdata/dependenciesExample/templates/_helpers.tpl new file mode 100644 index 00000000..fd2ea5b0 --- /dev/null +++ b/testdata/dependenciesExample/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "dependeciesExample.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "dependeciesExample.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "dependeciesExample.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "dependeciesExample.labels" -}} +helm.sh/chart: {{ include "dependeciesExample.chart" . }} +{{ include "dependeciesExample.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "dependeciesExample.selectorLabels" -}} +app.kubernetes.io/name: {{ include "dependeciesExample.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "dependeciesExample.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "dependeciesExample.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/testdata/dependenciesExample/templates/deployment.yaml b/testdata/dependenciesExample/templates/deployment.yaml new file mode 100644 index 00000000..2e75b618 --- /dev/null +++ b/testdata/dependenciesExample/templates/deployment.yaml @@ -0,0 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "common.names.name" . }} + labels: + {{- include "dependeciesExample.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "dependeciesExample.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "dependeciesExample.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "dependeciesExample.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + example: {{ .Values.common.exampleValue }} + example2: {{ .Values.global.subchart }} + example3: {{ .Values.subchartexample.subchartWithoutGlobal }} # todo fix this: missing hover, completion etc. diff --git a/testdata/dependenciesExample/values.yaml b/testdata/dependenciesExample/values.yaml new file mode 100644 index 00000000..4100784c --- /dev/null +++ b/testdata/dependenciesExample/values.yaml @@ -0,0 +1,54 @@ +affinity: {} +autoscaling: + enabled: false + maxReplicas: 100 + minReplicas: 1 + targetCPUUtilizationPercentage: 80 +fullnameOverride: "" +global: + subchart: works +image: + pullPolicy: IfNotPresent + repository: nginx + tag: "" +imagePullSecrets: [] +ingress: + annotations: {} + className: "" + enabled: false + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] +livenessProbe: + httpGet: + path: / + port: http +nameOverride: "" +nodeSelector: {} +podAnnotations: {} +podLabels: {} +podSecurityContext: {} +readinessProbe: + httpGet: + path: / + port: http +replicaCount: 1 +resources: {} +securityContext: {} +service: + port: 80 + type: ClusterIP +serviceAccount: + annotations: {} + automount: true + create: true + name: "" +subchartWithoutGlobal: works +subchartexample: + subchartWithoutGlobal: worksToo +tolerations: [] +volumeMounts: [] +volumes: [] From 00af29882fc50ec83927348fb0ba82cd4549dccd Mon Sep 17 00:00:00 2001 From: qvalentin Date: Tue, 20 Aug 2024 20:52:23 +0200 Subject: [PATCH 2/6] feat: sync values files to disk for definition --- .gitignore | 3 +- internal/charts/chart.go | 16 -------- internal/charts/chart_for_document.go | 3 +- internal/charts/chart_store.go | 13 ++++-- internal/charts/helm_chart.go | 34 +++++++++++++++ internal/charts/metadata.go | 9 +++- internal/charts/values_file.go | 45 +++++++++++++------- internal/charts/values_file_test.go | 12 +++--- internal/charts/values_files.go | 6 +-- internal/handler/definition_chart_test.go | 50 +++++++++++++++++++---- internal/util/yaml.go | 10 +---- 11 files changed, 137 insertions(+), 64 deletions(-) create mode 100644 internal/charts/helm_chart.go diff --git a/.gitignore b/.gitignore index f306bf19..ccd174e0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /dist __debug_bin* .vscode -.coverage testdata/dependenciesExample/charts/.helm_ls_cache/ +helm-ls +helm_ls diff --git a/internal/charts/chart.go b/internal/charts/chart.go index a894a513..df66d19a 100644 --- a/internal/charts/chart.go +++ b/internal/charts/chart.go @@ -39,22 +39,6 @@ func NewChart(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *Chart } } -func NewChartFromHelmChart(helmChart *chart.Chart, rootURI uri.URI) *Chart { - valuesFile := NewValuesFileFromValues(uri.File(filepath.Join(rootURI.Filename(), "values.yaml")), helmChart.Values) - - return &Chart{ - ValuesFiles: &ValuesFiles{ - MainValuesFile: valuesFile, - OverlayValuesFile: &ValuesFile{}, - AdditionalValuesFiles: []*ValuesFile{}, - }, - ChartMetadata: NewChartMetadataForDependencyChart(helmChart.Metadata, rootURI), - RootURI: rootURI, - ParentChart: ParentChart{}, - HelmChart: helmChart, - } -} - func (c *Chart) GetDependecyURI(dependencyName string) uri.URI { unpackedPath := filepath.Join(c.RootURI.Filename(), "charts", dependencyName) fileInfo, err := os.Stat(unpackedPath) diff --git a/internal/charts/chart_for_document.go b/internal/charts/chart_for_document.go index 5e8f4c70..207ea6f1 100644 --- a/internal/charts/chart_for_document.go +++ b/internal/charts/chart_for_document.go @@ -23,8 +23,7 @@ func (s *ChartStore) GetChartForDoc(uri lsp.DocumentURI) (*Chart, error) { URI: uri, } } - s.Charts[chart.RootURI] = chart - s.loadChartDependencies(chart) + s.AddChart(chart) return chart, nil } diff --git a/internal/charts/chart_store.go b/internal/charts/chart_store.go index 42ea2391..7a74577e 100644 --- a/internal/charts/chart_store.go +++ b/internal/charts/chart_store.go @@ -23,6 +23,12 @@ func NewChartStore(rootURI uri.URI, newChart func(uri.URI, util.ValuesFilesConfi } } +// AddChart adds a new chart to the store and loads its dependencies +func (s *ChartStore) AddChart(chart *Chart) { + s.Charts[chart.RootURI] = chart + s.loadChartDependencies(chart) +} + func (s *ChartStore) SetValuesFilesConfig(valuesFilesConfig util.ValuesFilesConfig) { logger.Debug("SetValuesFilesConfig", valuesFilesConfig) if valuesFilesConfig.MainValuesFileName == s.valuesFilesConfig.MainValuesFileName && @@ -32,7 +38,7 @@ func (s *ChartStore) SetValuesFilesConfig(valuesFilesConfig util.ValuesFilesConf } s.valuesFilesConfig = valuesFilesConfig for uri := range s.Charts { - s.Charts[uri] = s.newChart(uri, valuesFilesConfig) + s.AddChart(s.newChart(uri, valuesFilesConfig)) } } @@ -48,7 +54,7 @@ func (s *ChartStore) GetChartForURI(fileURI uri.URI) (*Chart, error) { } if chart != nil { - s.Charts[chart.RootURI] = chart + s.AddChart(chart) return chart, nil } @@ -77,7 +83,6 @@ func (s *ChartStore) loadChartDependencies(chart *Chart) { dependencyURI := chart.GetDependecyURI(dependency.Name()) chart := NewChartFromHelmChart(dependency, dependencyURI) - s.Charts[dependencyURI] = chart - s.loadChartDependencies(chart) + s.AddChart(chart) } } diff --git a/internal/charts/helm_chart.go b/internal/charts/helm_chart.go new file mode 100644 index 00000000..cbb9d103 --- /dev/null +++ b/internal/charts/helm_chart.go @@ -0,0 +1,34 @@ +package charts + +import ( + "path/filepath" + + "go.lsp.dev/uri" + "helm.sh/helm/v3/pkg/chart" +) + +// TODO: this ignores the newChart callback present in the ChartStore +func NewChartFromHelmChart(helmChart *chart.Chart, rootURI uri.URI) *Chart { + valuesFile := getValues(helmChart, rootURI) + return &Chart{ + ValuesFiles: &ValuesFiles{ + MainValuesFile: valuesFile, + OverlayValuesFile: &ValuesFile{}, + AdditionalValuesFiles: []*ValuesFile{}, + }, + ChartMetadata: NewChartMetadataForDependencyChart(helmChart.Metadata, rootURI), + RootURI: rootURI, + ParentChart: ParentChart{}, + HelmChart: helmChart, + } +} + +func getValues(helmChart *chart.Chart, rootURI uri.URI) *ValuesFile { + // Use Raw values if present because they also contain comments and documentation can be useful + for _, file := range helmChart.Raw { + if file.Name == "values.yaml" { + return NewValuesFileFromContent(uri.File(filepath.Join(rootURI.Filename(), "values.yaml")), file.Data) + } + } + return NewValuesFileFromValues(uri.File(filepath.Join(rootURI.Filename(), "values.yaml")), helmChart.Values) +} diff --git a/internal/charts/metadata.go b/internal/charts/metadata.go index fe59bee7..04d382f4 100644 --- a/internal/charts/metadata.go +++ b/internal/charts/metadata.go @@ -1,6 +1,7 @@ package charts import ( + "os" "path/filepath" "github.com/mrjosh/helm-ls/internal/util" @@ -18,7 +19,13 @@ type ChartMetadata struct { func NewChartMetadata(rootURI uri.URI) *ChartMetadata { filePath := filepath.Join(rootURI.Filename(), chartutil.ChartfileName) - chartNode, err := util.ReadYamlFileToNode(filePath) + contents, err := os.ReadFile(filePath) + if err != nil { + logger.Error("Error loading Chart.yaml file", filePath, err) + return nil + } + + chartNode, err := util.ReadYamlToNode(contents) if err != nil { logger.Error("Error loading Chart.yaml file", rootURI, err) } diff --git a/internal/charts/values_file.go b/internal/charts/values_file.go index 1f35e291..ec47e6ef 100644 --- a/internal/charts/values_file.go +++ b/internal/charts/values_file.go @@ -2,6 +2,7 @@ package charts import ( "fmt" + "os" "github.com/mrjosh/helm-ls/internal/util" "go.lsp.dev/uri" @@ -11,12 +12,13 @@ import ( ) type ValuesFile struct { - Values chartutil.Values - ValueNode yaml.Node - URI uri.URI + Values chartutil.Values + ValueNode yaml.Node + URI uri.URI + rawContent []byte } -func NewValuesFile(filePath string) *ValuesFile { +func NewValuesFileFromPath(filePath string) *ValuesFile { vals, valueNodes := readInValuesFile(filePath) return &ValuesFile{ @@ -26,6 +28,16 @@ func NewValuesFile(filePath string) *ValuesFile { } } +func NewValuesFileFromContent(uri uri.URI, data []byte) *ValuesFile { + vals, valueNode := parseYaml(data) + return &ValuesFile{ + ValueNode: valueNode, + Values: vals, + URI: uri, + rawContent: data, + } +} + func NewValuesFileFromValues(uri uri.URI, values chartutil.Values) *ValuesFile { valueNode, error := util.ValuesToYamlNode(values) if error != nil { @@ -48,26 +60,31 @@ func (v *ValuesFile) Reload() { } func readInValuesFile(filePath string) (chartutil.Values, yaml.Node) { - vals, err := chartutil.ReadValuesFile(filePath) + content, err := os.ReadFile(filePath) if err != nil { - logger.Error("Error loading values file ", filePath, err) + logger.Error(fmt.Sprintf("Error loading values file %s ", filePath), err) + return chartutil.Values{}, yaml.Node{} } - valueNodes, err := util.ReadYamlFileToNode(filePath) + return parseYaml(content) +} + +func parseYaml(content []byte) (chartutil.Values, yaml.Node) { + vals, err := chartutil.ReadValues(content) if err != nil { - logger.Error("Error loading values file ", filePath, err) + logger.Error("Error parsing values file ", err) + } + + valueNodes, err := util.ReadYamlToNode(content) + if err != nil { + logger.Error("Error parsing values file ", err) } return vals, valueNodes } // GetContent implements PossibleDependencyFile. func (d *ValuesFile) GetContent() string { - yaml, err := yaml.Marshal(d.Values) - if err != nil { - logger.Error(fmt.Sprintf("Could not load values for file %s", d.URI.Filename()), err) - return "" - } - return string(yaml) + return string(d.rawContent) } // GetPath implements PossibleDependencyFile. diff --git a/internal/charts/values_file_test.go b/internal/charts/values_file_test.go index 2a4cf76d..58da03ff 100644 --- a/internal/charts/values_file_test.go +++ b/internal/charts/values_file_test.go @@ -7,6 +7,7 @@ import ( "github.com/mrjosh/helm-ls/internal/charts" "github.com/stretchr/testify/assert" + "go.lsp.dev/uri" "gopkg.in/yaml.v3" "helm.sh/helm/v3/pkg/chartutil" ) @@ -16,7 +17,7 @@ func TestNewValuesFile(t *testing.T) { valuesContent := `foo: bar` _ = os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte(valuesContent), 0o644) - valuesFile := charts.NewValuesFile(filepath.Join(tempDir, "values.yaml")) + valuesFile := charts.NewValuesFileFromPath(filepath.Join(tempDir, "values.yaml")) assert.Equal(t, "bar", valuesFile.Values["foo"]) assert.NotEqual(t, yaml.Node{}, valuesFile.ValueNode) @@ -25,7 +26,7 @@ func TestNewValuesFile(t *testing.T) { func TestNewValuesFileFileNotFound(t *testing.T) { tempDir := t.TempDir() - valuesFile := charts.NewValuesFile(filepath.Join(tempDir, "values.yaml")) + valuesFile := charts.NewValuesFileFromPath(filepath.Join(tempDir, "values.yaml")) assert.Equal(t, chartutil.Values{}, valuesFile.Values) assert.Equal(t, yaml.Node{}, valuesFile.ValueNode) @@ -35,7 +36,7 @@ func TestReload(t *testing.T) { tempDir := t.TempDir() valuesContent := `foo: bar` _ = os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte(valuesContent), 0o644) - valuesFile := charts.NewValuesFile(filepath.Join(tempDir, "values.yaml")) + valuesFile := charts.NewValuesFileFromPath(filepath.Join(tempDir, "values.yaml")) assert.Equal(t, "bar", valuesFile.Values["foo"]) assert.NotEqual(t, yaml.Node{}, valuesFile.ValueNode) @@ -49,7 +50,6 @@ func TestReload(t *testing.T) { func TestGetContent(t *testing.T) { tempDir := t.TempDir() valuesContent := "foo: bar" - _ = os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte(valuesContent), 0o644) - valuesFile := charts.NewValuesFile(filepath.Join(tempDir, "values.yaml")) - assert.Equal(t, valuesContent+"\n", valuesFile.GetContent()) + valuesFile := charts.NewValuesFileFromContent(uri.File(filepath.Join(tempDir, "values.yaml")), []byte(valuesContent)) + assert.Equal(t, valuesContent, valuesFile.GetContent()) } diff --git a/internal/charts/values_files.go b/internal/charts/values_files.go index 4f5ce580..05adf4ce 100644 --- a/internal/charts/values_files.go +++ b/internal/charts/values_files.go @@ -22,7 +22,7 @@ func NewValuesFiles(rootURI uri.URI, mainValuesFileName string, lintOverlayValue overlayValuesFile := getLintOverlayValuesFile(lintOverlayValuesFile, additionalValuesFiles, rootURI) return &ValuesFiles{ - MainValuesFile: NewValuesFile(filepath.Join(rootURI.Filename(), mainValuesFileName)), + MainValuesFile: NewValuesFileFromPath(filepath.Join(rootURI.Filename(), mainValuesFileName)), OverlayValuesFile: overlayValuesFile, AdditionalValuesFiles: additionalValuesFiles, } @@ -40,7 +40,7 @@ func getLintOverlayValuesFile(lintOverlayValuesFile string, additionalValuesFile } } if overlayValuesFile == nil { - overlayValuesFile = NewValuesFile(filepath.Join(rootURI.Filename(), lintOverlayValuesFile)) + overlayValuesFile = NewValuesFileFromPath(filepath.Join(rootURI.Filename(), lintOverlayValuesFile)) } } return overlayValuesFile @@ -58,7 +58,7 @@ func getAdditionalValuesFiles(additionalValuesFilesGlob string, rootURI uri.URI, if match == filepath.Join(rootURI.Filename(), mainValuesFileName) { continue } - additionalValuesFiles = append(additionalValuesFiles, NewValuesFile(match)) + additionalValuesFiles = append(additionalValuesFiles, NewValuesFileFromPath(match)) } } } diff --git a/internal/handler/definition_chart_test.go b/internal/handler/definition_chart_test.go index d060ddc2..1032fe41 100644 --- a/internal/handler/definition_chart_test.go +++ b/internal/handler/definition_chart_test.go @@ -26,6 +26,7 @@ type testCase struct { // Must be content of a line in the file fileURI templateLineWithMarker string expectedFile string + expectedFileCount int expectedStartPosition lsp.Position expectedError error } @@ -35,18 +36,36 @@ func TestDefinitionChart(t *testing.T) { { `{{ include "common.na^mes.name" . }}`, "charts/.helm_ls_cache/common/templates/_names.tpl", + 1, lsp.Position{Line: 9, Character: 0}, nil, }, { `{{- include "dependeciesEx^ample.labels" . | nindent 4 }}`, "templates/_helpers.tpl", + 1, lsp.Position{Line: 35, Character: 0}, nil, }, { `{{ .Values.gl^obal.subchart }}`, "values.yaml", + 2, + lsp.Position{Line: 7, Character: 0}, + nil, + }, + { + `{{ .Values.gl^obal.subchart }}`, + "charts/subchartexample/values.yaml", + 2, + lsp.Position{Line: 0, Character: 0}, + nil, + }, + { + `{{ .Values.common.exa^mpleValue }}`, + "charts/.helm_ls_cache/common/values.yaml", + 1, + // this tests, that the file also contains comments lsp.Position{Line: 7, Character: 0}, nil, }, @@ -57,11 +76,11 @@ func TestDefinitionChart(t *testing.T) { t.Fatal(err) } lines := strings.Split(string(fileContent), "\n") - for _, tC := range testCases { - t.Run("Definition on "+tC.templateLineWithMarker, func(t *testing.T) { - pos, found := getPosition(tC, lines) + for _, tc := range testCases { + t.Run("Definition on "+tc.templateLineWithMarker, func(t *testing.T) { + pos, found := getPosition(tc, lines) if !found { - t.Fatal(fmt.Sprintf("%s is not in the file %s", tC.templateLineWithMarker, fileURI.Filename())) + t.Fatal(fmt.Sprintf("%s is not in the file %s", tc.templateLineWithMarker, fileURI.Filename())) } documents := lsplocal.NewDocumentStore() @@ -69,7 +88,7 @@ func TestDefinitionChart(t *testing.T) { chart := charts.NewChart(rootUri, util.DefaultConfig.ValuesFilesConfig) chartStore := charts.NewChartStore(rootUri, charts.NewChart) - chartStore.Charts = map[uri.URI]*charts.Chart{rootUri: chart} + chartStore.GetChartForURI(rootUri) h := &langHandler{ chartStore: chartStore, documents: documents, @@ -86,11 +105,24 @@ func TestDefinitionChart(t *testing.T) { }, }) - assert.Equal(t, tC.expectedError, err) - assert.Len(t, locations, 1) + assert.Equal(t, tc.expectedError, err) + assert.Len(t, locations, tc.expectedFileCount) + + // find the location with the correct file path + foundLocation := false + for _, location := range locations { + if location.URI.Filename() == filepath.Join(rootUri.Filename(), tc.expectedFile) { + locations = []lsp.Location{location} + foundLocation = true + break + } + } + + assert.True(t, foundLocation, fmt.Sprintf("Did not find a result with the expected file path %s ", filepath.Join(rootUri.Filename(), tc.expectedFile))) + if len(locations) > 0 { - assert.Equal(t, filepath.Join(rootUri.Filename(), tC.expectedFile), locations[0].URI.Filename()) - assert.Equal(t, tC.expectedStartPosition, locations[0].Range.Start) + assert.Equal(t, filepath.Join(rootUri.Filename(), tc.expectedFile), locations[0].URI.Filename()) + assert.Equal(t, tc.expectedStartPosition, locations[0].Range.Start) } for _, location := range locations { diff --git a/internal/util/yaml.go b/internal/util/yaml.go index 6f0b34ba..c46aed0c 100644 --- a/internal/util/yaml.go +++ b/internal/util/yaml.go @@ -2,7 +2,6 @@ package util import ( "fmt" - "os" "strings" lsp "go.lsp.dev/protocol" @@ -76,13 +75,8 @@ func getPositionOfNodeAfterRange(node *yamlv3.Node, query []string) (lsp.Positio return lsp.Position{}, fmt.Errorf("could not find Position of %s in values. Found no match", query) } -// ReadYamlFileToNode will parse a YAML file into a yaml Node. -func ReadYamlFileToNode(filename string) (node yamlv3.Node, err error) { - data, err := os.ReadFile(filename) - if err != nil { - return yamlv3.Node{}, err - } - +// ReadYamlToNode will parse a YAML file into a yaml Node. +func ReadYamlToNode(data []byte) (node yamlv3.Node, err error) { err = yamlv3.Unmarshal(data, &node) return node, err } From d06454952a1576ebe9fa81d29f443de316d8053d Mon Sep 17 00:00:00 2001 From: qvalentin Date: Wed, 21 Aug 2024 21:10:10 +0200 Subject: [PATCH 3/6] feat: test definition with dependencies --- internal/charts/chart.go | 31 +++---- internal/charts/chart_test.go | 30 ++++++- internal/charts/dependency_files.go | 11 ++- internal/handler/definition_chart_test.go | 81 ++++++++++++++++--- internal/handler/definition_test.go | 5 +- .../templates/_helpers_subchart.tpl | 6 ++ .../subchartexample/templates/subchart.yaml | 2 + .../templates/deployment.yaml | 1 + 8 files changed, 138 insertions(+), 29 deletions(-) create mode 100644 testdata/dependenciesExample/charts/subchartexample/templates/_helpers_subchart.tpl create mode 100644 testdata/dependenciesExample/charts/subchartexample/templates/subchart.yaml diff --git a/internal/charts/chart.go b/internal/charts/chart.go index df66d19a..984e3ef3 100644 --- a/internal/charts/chart.go +++ b/internal/charts/chart.go @@ -79,32 +79,29 @@ func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) []*Que logger.Error("Could not resolve values files for nil chart") return []*QueriedValuesFiles{} } - // logger.Debug(fmt.Sprintf("Resolving values files for %s with query %s", c.HelmChart.Name(), query)) result := []*QueriedValuesFiles{{Selector: query, ValuesFiles: c.ValuesFiles}} - if len(query) == 0 { return result } - if c.HelmChart != nil { - result = c.resolveValuesFilesOfDependencies(query, chartStore, result) - } + result = append(result, c.resolveValuesFilesOfDependencies(query, chartStore)...) + + result = append(result, c.resolveValuesFilesOfParent(chartStore, query)...) + return result +} + +func (c *Chart) resolveValuesFilesOfParent(chartStore *ChartStore, query []string) (result []*QueriedValuesFiles) { parentChart := c.ParentChart.GetParentChart(chartStore) + if parentChart == nil { + return []*QueriedValuesFiles{} + } if query[0] == "global" { - if parentChart == nil { - return result - } - return append(result, parentChart.ResolveValueFiles(query, chartStore)...) } - if parentChart == nil { - return result - } - chartName := c.ChartMetadata.Metadata.Name extendedQuery := append([]string{chartName}, query...) @@ -112,7 +109,10 @@ func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) []*Que parentChart.ResolveValueFiles(extendedQuery, chartStore)...) } -func (c *Chart) resolveValuesFilesOfDependencies(query []string, chartStore *ChartStore, result []*QueriedValuesFiles) []*QueriedValuesFiles { +func (c *Chart) resolveValuesFilesOfDependencies(query []string, chartStore *ChartStore) (result []*QueriedValuesFiles) { + if c.HelmChart == nil { + return []*QueriedValuesFiles{} + } for _, dependency := range c.HelmChart.Dependencies() { logger.Debug(fmt.Sprintf("Resolving dependency %s with query %s", dependency.Name(), query)) @@ -120,7 +120,7 @@ func (c *Chart) resolveValuesFilesOfDependencies(query []string, chartStore *Cha subQuery := query if dependency.Name() == query[0] { - if len(query) > 1 { + if len(query) >= 1 { subQuery = query[1:] } } @@ -132,6 +132,7 @@ func (c *Chart) resolveValuesFilesOfDependencies(query []string, chartStore *Cha } // TODO: why does this cause infinite recursion? + // because dependencyChart have the parent as dependency // result = append(result, dependencyChart.ResolveValueFiles(subQuery, chartStore)...) result = append(result, diff --git a/internal/charts/chart_test.go b/internal/charts/chart_test.go index ebfe5366..d725ddd6 100644 --- a/internal/charts/chart_test.go +++ b/internal/charts/chart_test.go @@ -170,6 +170,25 @@ func TestResolvesValuesFileOfDependencyWithChartName(t *testing.T) { assert.Contains(t, selectors, []string{"foo"}) } +func TestResolvesValuesFileOfDependencyWithOnlyChartName(t *testing.T) { + var ( + rootDir = "../../testdata/dependenciesExample" + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chart, err = chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) + valueFiles = chart.ResolveValueFiles([]string{"subchartexample"}, chartStore) + ) + + assert.NoError(t, err) + assert.Len(t, valueFiles, 2) + + selectors := [][]string{} + for _, valueFile := range valueFiles { + selectors = append(selectors, valueFile.Selector) + } + assert.Contains(t, selectors, []string{"subchartexample"}) + assert.Contains(t, selectors, []string{}) +} + func TestResolvesValuesFileOfDependencyWithChartNameForPackedDependency(t *testing.T) { var ( rootDir = "../../testdata/dependenciesExample" @@ -202,5 +221,14 @@ func TestLoadsHelmChartWithDependecies(t *testing.T) { chart := charts.NewChart(uri.File("../../testdata/dependenciesExample/"), util.ValuesFilesConfig{}) dependecyTemplates := chart.GetDependeciesTemplates() - assert.Len(t, dependecyTemplates, 21) + assert.Len(t, dependecyTemplates, 23) + + filePaths := []string{} + for _, dependency := range dependecyTemplates { + filePaths = append(filePaths, dependency.Path) + } + path, _ := filepath.Abs("../../testdata/dependenciesExample/charts/subchartexample/templates/subchart.yaml") + assert.Contains(t, filePaths, path) + path, _ = filepath.Abs("../../testdata/dependenciesExample/charts/" + charts.DependencyCacheFolder + "/common/templates/_names.tpl") + assert.Contains(t, filePaths, path) } diff --git a/internal/charts/dependency_files.go b/internal/charts/dependency_files.go index 8d205195..387c102c 100644 --- a/internal/charts/dependency_files.go +++ b/internal/charts/dependency_files.go @@ -16,7 +16,7 @@ type DependencyTemplateFile struct { var DependencyCacheFolder = ".helm_ls_cache" func (c *Chart) NewDependencyTemplateFile(chartName string, file *chart.File) *DependencyTemplateFile { - path := filepath.Join(c.RootURI.Filename(), "charts", DependencyCacheFolder, chartName, file.Name) + path := filepath.Join(c.getDependencyDir(chartName), file.Name) return &DependencyTemplateFile{Content: file.Data, Path: path} } @@ -26,6 +26,15 @@ type PossibleDependencyFile interface { GetPath() string } +func (c *Chart) getDependencyDir(chartName string) string { + extractedPath := filepath.Join(c.RootURI.Filename(), "charts", chartName) + _, err := os.Stat(extractedPath) + if err == nil { + return extractedPath + } + return filepath.Join(c.RootURI.Filename(), "charts", DependencyCacheFolder, chartName) +} + // SyncToDisk writes the content of the document to disk if it is a dependency file. // If it is a dependency file, it was read from a archive, so we need to write it back, // to be able to open it in a editor when using go-to-definition or go-to-reference. diff --git a/internal/handler/definition_chart_test.go b/internal/handler/definition_chart_test.go index 1032fe41..edb06434 100644 --- a/internal/handler/definition_chart_test.go +++ b/internal/handler/definition_chart_test.go @@ -18,8 +18,9 @@ import ( ) var ( - rootUri = uri.File("../../testdata/dependenciesExample/") - fileURI = uri.File("../../testdata/dependenciesExample/templates/deployment.yaml") + rootUri = uri.File("../../testdata/dependenciesExample/") + fileURI = uri.File("../../testdata/dependenciesExample/templates/deployment.yaml") + fileURIInSubchart = uri.File("../../testdata/dependenciesExample/charts/subchartexample/templates/subchart.yaml") ) type testCase struct { @@ -29,8 +30,10 @@ type testCase struct { expectedFileCount int expectedStartPosition lsp.Position expectedError error + inSubchart bool } +// Test definition on a real chart found in $rootUri func TestDefinitionChart(t *testing.T) { testCases := []testCase{ { @@ -39,6 +42,7 @@ func TestDefinitionChart(t *testing.T) { 1, lsp.Position{Line: 9, Character: 0}, nil, + false, }, { `{{- include "dependeciesEx^ample.labels" . | nindent 4 }}`, @@ -46,6 +50,7 @@ func TestDefinitionChart(t *testing.T) { 1, lsp.Position{Line: 35, Character: 0}, nil, + false, }, { `{{ .Values.gl^obal.subchart }}`, @@ -53,6 +58,7 @@ func TestDefinitionChart(t *testing.T) { 2, lsp.Position{Line: 7, Character: 0}, nil, + false, }, { `{{ .Values.gl^obal.subchart }}`, @@ -60,6 +66,7 @@ func TestDefinitionChart(t *testing.T) { 2, lsp.Position{Line: 0, Character: 0}, nil, + false, }, { `{{ .Values.common.exa^mpleValue }}`, @@ -68,16 +75,70 @@ func TestDefinitionChart(t *testing.T) { // this tests, that the file also contains comments lsp.Position{Line: 7, Character: 0}, nil, + false, + }, + { + `{{ .Values.comm^on.exampleValue }}`, + "charts/.helm_ls_cache/common/values.yaml", + 1, + lsp.Position{Line: 7, Character: 0}, + nil, + false, + }, + { + `{{ .Values.subch^artexample.subchartWithoutGlobal }}`, + "values.yaml", + 2, + lsp.Position{Line: 49, Character: 0}, + nil, + false, + }, + { + `{{ .Values.subch^artexample.subchartWithoutGlobal }}`, + "charts/subchartexample/values.yaml", + 2, + lsp.Position{Line: 0, Character: 0}, + nil, + false, + }, + { + `{{ .Values.subchartexample.subchartWith^outGlobal }}`, + "values.yaml", + 2, + lsp.Position{Line: 50, Character: 2}, + nil, + false, + }, + { + `{{ .Values.subchartexample.subchart^WithoutGlobal }}`, + "charts/subchartexample/values.yaml", + 2, + lsp.Position{Line: 2, Character: 0}, + nil, + false, + }, + { + `{{ .Values.subchart^WithoutGlobal }}`, + "charts/subchartexample/values.yaml", + 2, // TODO: this should also find the parent, but the parent of the chart is not found :? + lsp.Position{Line: 2, Character: 0}, + nil, + true, }, } - fileContent, err := os.ReadFile(fileURI.Filename()) - if err != nil { - t.Fatal(err) - } - lines := strings.Split(string(fileContent), "\n") for _, tc := range testCases { t.Run("Definition on "+tc.templateLineWithMarker, func(t *testing.T) { + uri := fileURI + if tc.inSubchart { + uri = fileURIInSubchart + } + fileContent, err := os.ReadFile(uri.Filename()) + if err != nil { + t.Fatal(err) + } + lines := strings.Split(string(fileContent), "\n") + pos, found := getPosition(tc, lines) if !found { t.Fatal(fmt.Sprintf("%s is not in the file %s", tc.templateLineWithMarker, fileURI.Filename())) @@ -88,7 +149,7 @@ func TestDefinitionChart(t *testing.T) { chart := charts.NewChart(rootUri, util.DefaultConfig.ValuesFilesConfig) chartStore := charts.NewChartStore(rootUri, charts.NewChart) - chartStore.GetChartForURI(rootUri) + _, err = chartStore.GetChartForURI(rootUri) h := &langHandler{ chartStore: chartStore, documents: documents, @@ -96,11 +157,13 @@ func TestDefinitionChart(t *testing.T) { helmlsConfig: util.DefaultConfig, } + assert.NoError(t, err) + h.LoadDocsOnNewChart(chart) locations, err := h.Definition(context.TODO(), &lsp.DefinitionParams{ TextDocumentPositionParams: lsp.TextDocumentPositionParams{ - TextDocument: lsp.TextDocumentIdentifier{URI: fileURI}, + TextDocument: lsp.TextDocumentIdentifier{URI: uri}, Position: pos, }, }) diff --git a/internal/handler/definition_test.go b/internal/handler/definition_test.go index 3a52f7fc..876d9489 100644 --- a/internal/handler/definition_test.go +++ b/internal/handler/definition_test.go @@ -18,7 +18,8 @@ import ( "helm.sh/helm/v3/pkg/chart" ) -var testFileContent = ` +var ( + testFileContent = ` {{ $variable := "text" }} # line 1 {{ $variable }} # line 2 @@ -34,8 +35,6 @@ var testFileContent = ` {{ end }} {{ range $index, $element := pipeline }}{{ $index }}{{ $element }}{{ end }} # line 14 ` - -var ( testDocumentTemplateURI = uri.URI("file:///templates/test.yaml") testValuesURI = uri.URI("file:///values.yaml") testOtherValuesURI = uri.URI("file:///values.other.yaml") diff --git a/testdata/dependenciesExample/charts/subchartexample/templates/_helpers_subchart.tpl b/testdata/dependenciesExample/charts/subchartexample/templates/_helpers_subchart.tpl new file mode 100644 index 00000000..8c72ce3b --- /dev/null +++ b/testdata/dependenciesExample/charts/subchartexample/templates/_helpers_subchart.tpl @@ -0,0 +1,6 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "subchartexample.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} diff --git a/testdata/dependenciesExample/charts/subchartexample/templates/subchart.yaml b/testdata/dependenciesExample/charts/subchartexample/templates/subchart.yaml new file mode 100644 index 00000000..29a0b3d1 --- /dev/null +++ b/testdata/dependenciesExample/charts/subchartexample/templates/subchart.yaml @@ -0,0 +1,2 @@ +example2: {{ .Values.global.subchart }} +example3: {{ .Values.subchartWithoutGlobal }} diff --git a/testdata/dependenciesExample/templates/deployment.yaml b/testdata/dependenciesExample/templates/deployment.yaml index 2e75b618..e1aa3c39 100644 --- a/testdata/dependenciesExample/templates/deployment.yaml +++ b/testdata/dependenciesExample/templates/deployment.yaml @@ -69,3 +69,4 @@ spec: example: {{ .Values.common.exampleValue }} example2: {{ .Values.global.subchart }} example3: {{ .Values.subchartexample.subchartWithoutGlobal }} # todo fix this: missing hover, completion etc. + example4: {{ include "subchartexample.name" . }} From 7dc39d61beea1966202cf95fc9ec25705022a01b Mon Sep 17 00:00:00 2001 From: qvalentin Date: Fri, 23 Aug 2024 16:55:56 +0200 Subject: [PATCH 4/6] fix: add ParentChart for NewChartFromHelmChart --- .gitignore | 1 + cmds/lint.go | 2 +- internal/charts/chart.go | 117 +----------------- internal/charts/chart_dependecies.go | 35 ++++++ internal/charts/chart_for_document_test.go | 10 +- internal/charts/chart_store.go | 5 +- internal/charts/chart_store_test.go | 10 +- internal/charts/chart_test.go | 35 +++++- internal/charts/chart_values_files.go | 89 +++++++++++++ internal/charts/helm_chart.go | 23 +++- internal/charts/values_file.go | 13 -- internal/handler/completion_main_test.go | 2 +- internal/handler/configuration_test.go | 8 +- internal/handler/definition_chart_test.go | 29 ++++- internal/handler/definition_test.go | 6 +- internal/handler/hover_main_test.go | 4 +- internal/handler/initialization.go | 7 +- internal/handler/references_test.go | 8 +- internal/handler/watched_files.go | 7 +- .../language_features/template_context.go | 2 +- .../template_context_hover_test.go | 4 + internal/lsp/document_store.go | 8 +- internal/util/yaml.go | 10 -- 23 files changed, 246 insertions(+), 189 deletions(-) create mode 100644 internal/charts/chart_dependecies.go create mode 100644 internal/charts/chart_values_files.go diff --git a/.gitignore b/.gitignore index ccd174e0..12d695fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __debug_bin* .vscode +.coverage testdata/dependenciesExample/charts/.helm_ls_cache/ helm-ls helm_ls diff --git a/cmds/lint.go b/cmds/lint.go index 03ba3f05..680b48f2 100644 --- a/cmds/lint.go +++ b/cmds/lint.go @@ -20,7 +20,7 @@ func newLintCmd() *cobra.Command { } rootPath := uri.File(args[0]) - chartStore := charts.NewChartStore(rootPath, charts.NewChart) + chartStore := charts.NewChartStore(rootPath, charts.NewChart, func(chart *charts.Chart) {}) chart, err := chartStore.GetChartForURI(rootPath) if err != nil { return err diff --git a/internal/charts/chart.go b/internal/charts/chart.go index 984e3ef3..3ea42df9 100644 --- a/internal/charts/chart.go +++ b/internal/charts/chart.go @@ -2,8 +2,6 @@ package charts import ( "fmt" - "os" - "path/filepath" "strings" "github.com/mrjosh/helm-ls/internal/log" @@ -39,27 +37,14 @@ func NewChart(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *Chart } } -func (c *Chart) GetDependecyURI(dependencyName string) uri.URI { - unpackedPath := filepath.Join(c.RootURI.Filename(), "charts", dependencyName) - fileInfo, err := os.Stat(unpackedPath) - - if err == nil && fileInfo.IsDir() { - return uri.File(unpackedPath) - } - - return uri.File(filepath.Join(c.RootURI.Filename(), "charts", DependencyCacheFolder, dependencyName)) -} - -func loadHelmChart(rootURI uri.URI) *chart.Chart { - var helmChart *chart.Chart - - loader, err := loader.Loader(rootURI.Filename()) +func loadHelmChart(rootURI uri.URI) (helmChart *chart.Chart) { + chartLoader, err := loader.Loader(rootURI.Filename()) if err != nil { logger.Error(fmt.Sprintf("Error loading chart %s: %s", rootURI.Filename(), err.Error())) return nil } - helmChart, err = loader.Load() + helmChart, err = chartLoader.Load() if err != nil { logger.Error(fmt.Sprintf("Error loading chart %s: %s", rootURI.Filename(), err.Error())) } @@ -67,84 +52,8 @@ func loadHelmChart(rootURI uri.URI) *chart.Chart { return helmChart } -type QueriedValuesFiles struct { - Selector []string - ValuesFiles *ValuesFiles -} - -// ResolveValueFiles returns a list of all values files in the chart -// and all parent charts if the query tries to access global values -func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) []*QueriedValuesFiles { - if c == nil { - logger.Error("Could not resolve values files for nil chart") - return []*QueriedValuesFiles{} - } - result := []*QueriedValuesFiles{{Selector: query, ValuesFiles: c.ValuesFiles}} - if len(query) == 0 { - return result - } - - result = append(result, c.resolveValuesFilesOfDependencies(query, chartStore)...) - - result = append(result, c.resolveValuesFilesOfParent(chartStore, query)...) - - return result -} - -func (c *Chart) resolveValuesFilesOfParent(chartStore *ChartStore, query []string) (result []*QueriedValuesFiles) { - parentChart := c.ParentChart.GetParentChart(chartStore) - if parentChart == nil { - return []*QueriedValuesFiles{} - } - - if query[0] == "global" { - return append(result, - parentChart.ResolveValueFiles(query, chartStore)...) - } - - chartName := c.ChartMetadata.Metadata.Name - extendedQuery := append([]string{chartName}, query...) - - return append(result, - parentChart.ResolveValueFiles(extendedQuery, chartStore)...) -} - -func (c *Chart) resolveValuesFilesOfDependencies(query []string, chartStore *ChartStore) (result []*QueriedValuesFiles) { - if c.HelmChart == nil { - return []*QueriedValuesFiles{} - } - for _, dependency := range c.HelmChart.Dependencies() { - logger.Debug(fmt.Sprintf("Resolving dependency %s with query %s", dependency.Name(), query)) - - if dependency.Name() == query[0] || query[0] == "global" { - subQuery := query - - if dependency.Name() == query[0] { - if len(query) >= 1 { - subQuery = query[1:] - } - } - - dependencyChart := chartStore.Charts[c.GetDependecyURI(dependency.Name())] - if dependencyChart == nil { - logger.Error(fmt.Sprintf("Could not find dependency %s", dependency.Name())) - continue - } - - // TODO: why does this cause infinite recursion? - // because dependencyChart have the parent as dependency - // result = append(result, dependencyChart.ResolveValueFiles(subQuery, chartStore)...) - - result = append(result, - &QueriedValuesFiles{Selector: subQuery, ValuesFiles: dependencyChart.ValuesFiles}) - } - } - - return result -} - -func (c *Chart) GetValueLocation(templateContext []string) (lsp.Location, error) { - modifyedVar := make([]string, len(templateContext)) +func (c *Chart) GetMetadataLocation(templateContext []string) (lsp.Location, error) { + modifyedVar := []string{} // make the first letter lowercase since in the template the first letter is // capitalized, but it is not in the Chart.yaml file for _, value := range templateContext { @@ -161,19 +70,3 @@ func (c *Chart) GetValueLocation(templateContext []string) (lsp.Location, error) return lsp.Location{URI: c.ChartMetadata.URI, Range: lsp.Range{Start: position}}, err } - -func (c *Chart) GetDependeciesTemplates() []*DependencyTemplateFile { - result := []*DependencyTemplateFile{} - if c.HelmChart == nil { - return result - } - - for _, dependency := range c.HelmChart.Dependencies() { - for _, file := range dependency.Templates { - dependencyTemplate := c.NewDependencyTemplateFile(dependency.Name(), file) - result = append(result, dependencyTemplate) - } - } - - return result -} diff --git a/internal/charts/chart_dependecies.go b/internal/charts/chart_dependecies.go new file mode 100644 index 00000000..a139c32b --- /dev/null +++ b/internal/charts/chart_dependecies.go @@ -0,0 +1,35 @@ +package charts + +import ( + "os" + "path/filepath" + + "go.lsp.dev/uri" +) + +func (c *Chart) GetDependecyURI(dependencyName string) uri.URI { + unpackedPath := filepath.Join(c.RootURI.Filename(), "charts", dependencyName) + fileInfo, err := os.Stat(unpackedPath) + + if err == nil && fileInfo.IsDir() { + return uri.File(unpackedPath) + } + + return uri.File(filepath.Join(c.RootURI.Filename(), "charts", DependencyCacheFolder, dependencyName)) +} + +func (c *Chart) GetDependeciesTemplates() []*DependencyTemplateFile { + result := []*DependencyTemplateFile{} + if c.HelmChart == nil { + return result + } + + for _, dependency := range c.HelmChart.Dependencies() { + for _, file := range dependency.Templates { + dependencyTemplate := c.NewDependencyTemplateFile(dependency.Name(), file) + result = append(result, dependencyTemplate) + } + } + + return result +} diff --git a/internal/charts/chart_for_document_test.go b/internal/charts/chart_for_document_test.go index 9aebb325..0aa6f294 100644 --- a/internal/charts/chart_for_document_test.go +++ b/internal/charts/chart_for_document_test.go @@ -16,7 +16,7 @@ import ( func TestGetChartForDocumentWorksForAlreadyAddedCharts(t *testing.T) { chartStore := charts.NewChartStore("file:///tmp", func(uri uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return &charts.Chart{RootURI: uri} - }) + }, addChartCallback) chart := &charts.Chart{} chartStore.Charts["file:///tmp/chart"] = chart @@ -58,7 +58,7 @@ func TestGetChartForDocumentWorksForNewToAddChart(t *testing.T) { HelmChart: &chart.Chart{}, } newChartFunc = func(_ uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return expectedChart } - chartStore = charts.NewChartStore(uri.File(rootDir), newChartFunc) + chartStore = charts.NewChartStore(uri.File(rootDir), newChartFunc, addChartCallback) err = os.MkdirAll(expectedChartDirectory, 0o755) ) assert.NoError(t, err) @@ -84,7 +84,7 @@ func TestGetChartForDocumentWorksForNewToAddChartWithNestedFile(t *testing.T) { HelmChart: &chart.Chart{}, } newChartFunc = func(_ uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return expectedChart } - chartStore = charts.NewChartStore(uri.File(rootDir), newChartFunc) + chartStore = charts.NewChartStore(uri.File(rootDir), newChartFunc, addChartCallback) err = os.MkdirAll(expectedChartDirectory, 0o755) ) assert.NoError(t, err) @@ -101,7 +101,7 @@ func TestGetChartForDocumentWorksForNewToAddChartWithNestedFile(t *testing.T) { func TestGetChartOrParentForDocWorks(t *testing.T) { chartStore := charts.NewChartStore("file:///tmp", func(uri uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return &charts.Chart{RootURI: uri} - }) + }, addChartCallback) chart := &charts.Chart{} chartStore.Charts["file:///tmp/chart"] = chart @@ -142,7 +142,7 @@ func TestGetChartOrParentForDocWorks(t *testing.T) { func TestGetChartForDocumentWorksForChartWithDependencies(t *testing.T) { var ( rootDir = "../../testdata/dependenciesExample/" - chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart, addChartCallback) ) result1, error := chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) diff --git a/internal/charts/chart_store.go b/internal/charts/chart_store.go index 7a74577e..738d6af7 100644 --- a/internal/charts/chart_store.go +++ b/internal/charts/chart_store.go @@ -11,14 +11,16 @@ type ChartStore struct { Charts map[uri.URI]*Chart RootURI uri.URI newChart func(uri.URI, util.ValuesFilesConfig) *Chart + addChartCallback func(chart *Chart) valuesFilesConfig util.ValuesFilesConfig } -func NewChartStore(rootURI uri.URI, newChart func(uri.URI, util.ValuesFilesConfig) *Chart) *ChartStore { +func NewChartStore(rootURI uri.URI, newChart func(uri.URI, util.ValuesFilesConfig) *Chart, addChartCallback func(chart *Chart)) *ChartStore { return &ChartStore{ Charts: map[uri.URI]*Chart{}, RootURI: rootURI, newChart: newChart, + addChartCallback: addChartCallback, valuesFilesConfig: util.DefaultConfig.ValuesFilesConfig, } } @@ -27,6 +29,7 @@ func NewChartStore(rootURI uri.URI, newChart func(uri.URI, util.ValuesFilesConfi func (s *ChartStore) AddChart(chart *Chart) { s.Charts[chart.RootURI] = chart s.loadChartDependencies(chart) + s.addChartCallback(chart) } func (s *ChartStore) SetValuesFilesConfig(valuesFilesConfig util.ValuesFilesConfig) { diff --git a/internal/charts/chart_store_test.go b/internal/charts/chart_store_test.go index 3cd00468..2ed94fe1 100644 --- a/internal/charts/chart_store_test.go +++ b/internal/charts/chart_store_test.go @@ -12,6 +12,8 @@ import ( "helm.sh/helm/v3/pkg/chartutil" ) +var addChartCallback = func(chart *Chart) {} + func TestSetValuesFilesConfigOverwrites(t *testing.T) { valuesFilesConfig := util.ValuesFilesConfig{ MainValuesFileName: "value.yaml", @@ -25,7 +27,7 @@ func TestSetValuesFilesConfigOverwrites(t *testing.T) { _ = os.WriteFile(filepath.Join(tempDir, "value.yaml"), []byte("foo: main"), 0o644) _ = os.WriteFile(filepath.Join(tempDir, "something.yaml"), []byte(valuesContent), 0o644) _ = os.WriteFile(filepath.Join(tempDir, "values.other.yaml"), []byte(valuesContent), 0o644) - s := NewChartStore(uri.File(tempDir), NewChart) + s := NewChartStore(uri.File(tempDir), NewChart, addChartCallback) chartOld, err := s.GetChartForURI(uri.File(tempDir)) assert.Equal(t, chartutil.Values{}, chartOld.ValuesFiles.MainValuesFile.Values) @@ -54,7 +56,7 @@ func TestSetValuesFilesConfigDoesNotOverwrite(t *testing.T) { _ = os.WriteFile(filepath.Join(tempDir, "something.yaml"), []byte(valuesContent), 0o644) _ = os.WriteFile(filepath.Join(tempDir, "values.lint.yaml"), []byte(valuesContent), 0o644) _ = os.WriteFile(filepath.Join(tempDir, "values.other.yaml"), []byte(valuesContent), 0o644) - s := NewChartStore(uri.File(tempDir), NewChart) + s := NewChartStore(uri.File(tempDir), NewChart, addChartCallback) chart, err := s.GetChartForURI(uri.File(tempDir)) assert.NoError(t, err) @@ -71,7 +73,7 @@ func TestGetChartForURIWhenChartYamlDoesNotExist(t *testing.T) { tempDir := t.TempDir() _ = os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte("foo: main"), 0o644) - s := NewChartStore(uri.File(tempDir), NewChart) + s := NewChartStore(uri.File(tempDir), NewChart, addChartCallback) chart, err := s.GetChartForURI(uri.File(tempDir)) assert.Error(t, err) @@ -90,7 +92,7 @@ func TestReloadValuesFiles(t *testing.T) { RootURI: uri.File(tempDir), ParentChart: ParentChart{}, } - s := NewChartStore(uri.File(tempDir), NewChart) + s := NewChartStore(uri.File(tempDir), NewChart, addChartCallback) s.Charts[chart.RootURI] = chart assert.Equal(t, "bar", chart.ValuesFiles.MainValuesFile.Values["foo"]) diff --git a/internal/charts/chart_test.go b/internal/charts/chart_test.go index d725ddd6..bc4a9c8d 100644 --- a/internal/charts/chart_test.go +++ b/internal/charts/chart_test.go @@ -11,9 +11,13 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" + lsp "go.lsp.dev/protocol" + "github.com/stretchr/testify/assert" ) +var addChartCallback = func(chart *charts.Chart) {} + func TestNewChartsLoadsMetadata(t *testing.T) { tempDir := t.TempDir() @@ -85,7 +89,7 @@ func TestResolvesValuesFileOfParent(t *testing.T) { HelmChart: &chart.Chart{}, } newChartFunc := func(_ uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return expectedChart } - chartStore := charts.NewChartStore(uri.File(tempDir), newChartFunc) + chartStore := charts.NewChartStore(uri.File(tempDir), newChartFunc, addChartCallback) valueFiles := sut.ResolveValueFiles([]string{"global", "foo"}, chartStore) @@ -122,7 +126,7 @@ func TestResolvesValuesFileOfParentByName(t *testing.T) { HelmChart: &chart.Chart{}, } newChartFunc := func(_ uri.URI, _ util.ValuesFilesConfig) *charts.Chart { return expectedChart } - chartStore := charts.NewChartStore(uri.File(tempDir), newChartFunc) + chartStore := charts.NewChartStore(uri.File(tempDir), newChartFunc, addChartCallback) valueFiles := subchart.ResolveValueFiles([]string{"foo"}, chartStore) @@ -136,7 +140,7 @@ func TestResolvesValuesFileOfParentByName(t *testing.T) { func TestResolvesValuesFileOfDependencyWithGlobal(t *testing.T) { var ( rootDir = "../../testdata/dependenciesExample" - chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart, addChartCallback) chart, err = chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) valueFiles = chart.ResolveValueFiles([]string{"global"}, chartStore) ) @@ -154,7 +158,7 @@ func TestResolvesValuesFileOfDependencyWithGlobal(t *testing.T) { func TestResolvesValuesFileOfDependencyWithChartName(t *testing.T) { var ( rootDir = "../../testdata/dependenciesExample" - chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart, addChartCallback) chart, err = chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) valueFiles = chart.ResolveValueFiles([]string{"subchartexample", "foo"}, chartStore) ) @@ -173,7 +177,7 @@ func TestResolvesValuesFileOfDependencyWithChartName(t *testing.T) { func TestResolvesValuesFileOfDependencyWithOnlyChartName(t *testing.T) { var ( rootDir = "../../testdata/dependenciesExample" - chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart, addChartCallback) chart, err = chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) valueFiles = chart.ResolveValueFiles([]string{"subchartexample"}, chartStore) ) @@ -192,7 +196,7 @@ func TestResolvesValuesFileOfDependencyWithOnlyChartName(t *testing.T) { func TestResolvesValuesFileOfDependencyWithChartNameForPackedDependency(t *testing.T) { var ( rootDir = "../../testdata/dependenciesExample" - chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart) + chartStore = charts.NewChartStore(uri.File(rootDir), charts.NewChart, addChartCallback) chart, err = chartStore.GetChartForDoc(uri.File(filepath.Join(rootDir, "templates", "deployment.yaml"))) valueFiles = chart.ResolveValueFiles([]string{"common", "exampleValue"}, chartStore) ) @@ -232,3 +236,22 @@ func TestLoadsHelmChartWithDependecies(t *testing.T) { path, _ = filepath.Abs("../../testdata/dependenciesExample/charts/" + charts.DependencyCacheFolder + "/common/templates/_names.tpl") assert.Contains(t, filePaths, path) } + +func TestGetValueLocation(t *testing.T) { + chart := charts.NewChart(uri.File("../../testdata/dependenciesExample/"), util.ValuesFilesConfig{}) + + valueLocation, err := chart.GetMetadataLocation([]string{"Name"}) + assert.NoError(t, err) + + expected := lsp.Location{ + URI: uri.File("../../testdata/dependenciesExample/Chart.yaml"), + Range: lsp.Range{ + Start: lsp.Position{ + Line: 1, + Character: 0, + }, + }, + } + + assert.Equal(t, expected, valueLocation) +} diff --git a/internal/charts/chart_values_files.go b/internal/charts/chart_values_files.go new file mode 100644 index 00000000..4a90467d --- /dev/null +++ b/internal/charts/chart_values_files.go @@ -0,0 +1,89 @@ +package charts + +import ( + "fmt" + + "go.lsp.dev/uri" +) + +type QueriedValuesFiles struct { + Selector []string + ValuesFiles *ValuesFiles +} + +// ResolveValueFiles returns a list of all values files in the chart +// and all parent and dependency charts with the adjusted query +func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) (result []*QueriedValuesFiles) { + recResult := map[uri.URI]*QueriedValuesFiles{} + c.ResolveValueFilesRecursive(query, chartStore, recResult) + + // TODO(@qvalentin): use maps.Values once we have Go 1.23 + for _, valuesFiles := range recResult { + result = append(result, valuesFiles) + } + return result +} + +func (c *Chart) ResolveValueFilesRecursive(query []string, chartStore *ChartStore, result map[uri.URI]*QueriedValuesFiles) { + // check if chart was already processed + if _, ok := result[c.RootURI]; ok { + return + } + + if c == nil { + logger.Error("Could not resolve values files for nil chart") + return + } + + currentResult := &QueriedValuesFiles{Selector: query, ValuesFiles: c.ValuesFiles} + result[c.RootURI] = currentResult + if len(query) == 0 { + return + } + + c.resolveValuesFilesOfDependencies(query, chartStore, result) + c.resolveValuesFilesOfParent(chartStore, query, result) +} + +func (c *Chart) resolveValuesFilesOfParent(chartStore *ChartStore, query []string, result map[uri.URI]*QueriedValuesFiles) { + parentChart := c.ParentChart.GetParentChart(chartStore) + if parentChart == nil { + return + } + + if query[0] == "global" { + parentChart.ResolveValueFilesRecursive(query, chartStore, result) + } + + chartName := c.ChartMetadata.Metadata.Name + extendedQuery := append([]string{chartName}, query...) + + parentChart.ResolveValueFilesRecursive(extendedQuery, chartStore, result) +} + +func (c *Chart) resolveValuesFilesOfDependencies(query []string, chartStore *ChartStore, result map[uri.URI]*QueriedValuesFiles) { + if c.HelmChart == nil { + return + } + for _, dependency := range c.HelmChart.Dependencies() { + logger.Debug(fmt.Sprintf("Resolving dependency %s with query %s", dependency.Name(), query)) + + if dependency.Name() == query[0] || query[0] == "global" { + subQuery := query + + if dependency.Name() == query[0] { + if len(query) >= 1 { + subQuery = query[1:] + } + } + + dependencyChart := chartStore.Charts[c.GetDependecyURI(dependency.Name())] + if dependencyChart == nil { + logger.Error(fmt.Sprintf("Could not find dependency %s", dependency.Name())) + continue + } + + dependencyChart.ResolveValueFilesRecursive(subQuery, chartStore, result) + } + } +} diff --git a/internal/charts/helm_chart.go b/internal/charts/helm_chart.go index cbb9d103..fbc1b761 100644 --- a/internal/charts/helm_chart.go +++ b/internal/charts/helm_chart.go @@ -4,31 +4,44 @@ import ( "path/filepath" "go.lsp.dev/uri" + "gopkg.in/yaml.v3" "helm.sh/helm/v3/pkg/chart" ) // TODO: this ignores the newChart callback present in the ChartStore func NewChartFromHelmChart(helmChart *chart.Chart, rootURI uri.URI) *Chart { - valuesFile := getValues(helmChart, rootURI) return &Chart{ ValuesFiles: &ValuesFiles{ - MainValuesFile: valuesFile, + MainValuesFile: getValues(helmChart, rootURI), OverlayValuesFile: &ValuesFile{}, AdditionalValuesFiles: []*ValuesFile{}, }, ChartMetadata: NewChartMetadataForDependencyChart(helmChart.Metadata, rootURI), RootURI: rootURI, - ParentChart: ParentChart{}, + ParentChart: getParent(helmChart, rootURI), HelmChart: helmChart, } } func getValues(helmChart *chart.Chart, rootURI uri.URI) *ValuesFile { // Use Raw values if present because they also contain comments and documentation can be useful + uri := uri.File(filepath.Join(rootURI.Filename(), "values.yaml")) for _, file := range helmChart.Raw { if file.Name == "values.yaml" { - return NewValuesFileFromContent(uri.File(filepath.Join(rootURI.Filename(), "values.yaml")), file.Data) + return NewValuesFileFromContent(uri, file.Data) } } - return NewValuesFileFromValues(uri.File(filepath.Join(rootURI.Filename(), "values.yaml")), helmChart.Values) + return &ValuesFile{ + ValueNode: yaml.Node{}, + Values: helmChart.Values, + URI: uri, + rawContent: []byte{}, + } +} + +func getParent(helmChart *chart.Chart, rootURI uri.URI) ParentChart { + if helmChart.Parent() != nil { + return newParentChart(rootURI) + } + return ParentChart{} } diff --git a/internal/charts/values_file.go b/internal/charts/values_file.go index ec47e6ef..df6d866b 100644 --- a/internal/charts/values_file.go +++ b/internal/charts/values_file.go @@ -38,19 +38,6 @@ func NewValuesFileFromContent(uri uri.URI, data []byte) *ValuesFile { } } -func NewValuesFileFromValues(uri uri.URI, values chartutil.Values) *ValuesFile { - valueNode, error := util.ValuesToYamlNode(values) - if error != nil { - logger.Error(fmt.Sprintf("Could not load values for file %s", uri.Filename()), error) - return &ValuesFile{} - } - return &ValuesFile{ - ValueNode: valueNode, - Values: values, - URI: uri, - } -} - func (v *ValuesFile) Reload() { vals, valueNodes := readInValuesFile(v.URI.Filename()) diff --git a/internal/handler/completion_main_test.go b/internal/handler/completion_main_test.go index 75e69f5b..449350c7 100644 --- a/internal/handler/completion_main_test.go +++ b/internal/handler/completion_main_test.go @@ -227,7 +227,7 @@ func completionTestCall(fileURI uri.URI, buf string, pos lsp.Position) (*lsp.Com } documents.DidOpen(&d, util.DefaultConfig) h := &langHandler{ - chartStore: charts.NewChartStore(uri.File("."), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("."), charts.NewChart, func(chart *charts.Chart) {}), documents: documents, yamllsConnector: &yamlls.Connector{}, } diff --git a/internal/handler/configuration_test.go b/internal/handler/configuration_test.go index a3298d80..012a0f2c 100644 --- a/internal/handler/configuration_test.go +++ b/internal/handler/configuration_test.go @@ -21,7 +21,7 @@ func TestConfigurationWorks(t *testing.T) { mockClient := mocks.NewMockClient(t) handler := &langHandler{ helmlsConfig: util.DefaultConfig, - chartStore: charts.NewChartStore(uri.File("/"), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("/"), charts.NewChart, addChartCallback), } handler.client = mockClient @@ -43,7 +43,7 @@ func TestConfigurationWorksForEmptyConfig(t *testing.T) { mockClient := mocks.NewMockClient(t) handler := &langHandler{ helmlsConfig: util.DefaultConfig, - chartStore: charts.NewChartStore(uri.File("/"), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("/"), charts.NewChart, addChartCallback), } handler.client = mockClient // disable yamlls to avoid configuring it in the test @@ -62,7 +62,7 @@ func TestConfigurationWorksForError(t *testing.T) { mockClient := mocks.NewMockClient(t) handler := &langHandler{ helmlsConfig: util.DefaultConfig, - chartStore: charts.NewChartStore(uri.File("/"), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("/"), charts.NewChart, addChartCallback), } handler.client = mockClient @@ -84,7 +84,7 @@ func TestConfigurationWorksForJsonError(t *testing.T) { mockClient := mocks.NewMockClient(t) handler := &langHandler{ helmlsConfig: util.DefaultConfig, - chartStore: charts.NewChartStore(uri.File("/"), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("/"), charts.NewChart, addChartCallback), } handler.client = mockClient diff --git a/internal/handler/definition_chart_test.go b/internal/handler/definition_chart_test.go index edb06434..49c8bff7 100644 --- a/internal/handler/definition_chart_test.go +++ b/internal/handler/definition_chart_test.go @@ -36,6 +36,14 @@ type testCase struct { // Test definition on a real chart found in $rootUri func TestDefinitionChart(t *testing.T) { testCases := []testCase{ + { + `{{ .Values.subchart^WithoutGlobal }}`, + "charts/subchartexample/values.yaml", + 2, // TODO: this should also find the parent, but the parent of the chart is not found :? + lsp.Position{Line: 2, Character: 0}, + nil, + true, + }, { `{{ include "common.na^mes.name" . }}`, "charts/.helm_ls_cache/common/templates/_names.tpl", @@ -118,12 +126,20 @@ func TestDefinitionChart(t *testing.T) { false, }, { - `{{ .Values.subchart^WithoutGlobal }}`, - "charts/subchartexample/values.yaml", - 2, // TODO: this should also find the parent, but the parent of the chart is not found :? - lsp.Position{Line: 2, Character: 0}, + `{{ .Cha^rt.Name }}`, + "Chart.yaml", + 1, + lsp.Position{Line: 0, Character: 0}, nil, - true, + false, + }, + { + `{{ .Chart.Na^me }}`, + "Chart.yaml", + 1, + lsp.Position{Line: 1, Character: 0}, + nil, + false, }, } @@ -148,7 +164,8 @@ func TestDefinitionChart(t *testing.T) { chart := charts.NewChart(rootUri, util.DefaultConfig.ValuesFilesConfig) - chartStore := charts.NewChartStore(rootUri, charts.NewChart) +var addChartCallback = func(chart *charts.Chart) {} + chartStore := charts.NewChartStore(rootUri, charts.NewChart,addChartCallback) _, err = chartStore.GetChartForURI(rootUri) h := &langHandler{ chartStore: chartStore, diff --git a/internal/handler/definition_test.go b/internal/handler/definition_test.go index 876d9489..064e5c41 100644 --- a/internal/handler/definition_test.go +++ b/internal/handler/definition_test.go @@ -80,7 +80,7 @@ func genericDefinitionTest(t *testing.T, position lsp.Position, expectedLocation }, } documents.DidOpen(&d, util.DefaultConfig) - chartStore := charts.NewChartStore(rootUri, charts.NewChart) + chartStore := charts.NewChartStore(rootUri, charts.NewChart, addChartCallback) chartStore.Charts = map[uri.URI]*charts.Chart{rootUri: testChart} h := &langHandler{ chartStore: chartStore, @@ -275,7 +275,7 @@ func genericDefinitionTestMultipleValuesFiles(t *testing.T, position lsp.Positio }, } documents.DidOpen(&d, util.DefaultConfig) - chartStore := charts.NewChartStore(rootUri, charts.NewChart) + chartStore := charts.NewChartStore(rootUri, charts.NewChart, addChartCallback) chartStore.Charts = map[uri.URI]*charts.Chart{rootUri: chart} h := &langHandler{ chartStore: chartStore, @@ -368,7 +368,7 @@ func TestDefinitionSingleLine(t *testing.T) { } documents.DidOpen(&d, util.DefaultConfig) h := &langHandler{ - chartStore: charts.NewChartStore(rootUri, charts.NewChart), + chartStore: charts.NewChartStore(rootUri, charts.NewChart, addChartCallback), documents: documents, } diff --git a/internal/handler/hover_main_test.go b/internal/handler/hover_main_test.go index 4e013aac..1931a956 100644 --- a/internal/handler/hover_main_test.go +++ b/internal/handler/hover_main_test.go @@ -161,8 +161,10 @@ func TestHoverMain(t *testing.T) { }, } documents.DidOpen(&d, util.DefaultConfig) + + addChartCallback := func(chart *charts.Chart) {} h := &langHandler{ - chartStore: charts.NewChartStore(uri.File("."), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("."), charts.NewChart, addChartCallback), documents: documents, yamllsConnector: &yamlls.Connector{}, } diff --git a/internal/handler/initialization.go b/internal/handler/initialization.go index b5497d63..42ca2bb6 100644 --- a/internal/handler/initialization.go +++ b/internal/handler/initialization.go @@ -28,7 +28,7 @@ func (h *langHandler) Initialize(ctx context.Context, params *lsp.InitializePara } logger.Debug("Initializing chartStore") - h.chartStore = charts.NewChartStore(workspaceURI, h.NewChartWithInitActions) + h.chartStore = charts.NewChartStore(workspaceURI, charts.NewChart, h.NewChartWithInitActions) logger.Debug("Initializing done") return &lsp.InitializeResult{ @@ -96,8 +96,7 @@ func configureLogLevel(helmlsConfig util.HelmlsConfiguration) { } } -func (h *langHandler) NewChartWithInitActions(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *charts.Chart { - chart := h.NewChartWithWatchedFiles(rootURI, valuesFilesConfig) +func (h *langHandler) NewChartWithInitActions(chart *charts.Chart) { + h.NewChartWithWatchedFiles(chart) go h.LoadDocsOnNewChart(chart) - return chart } diff --git a/internal/handler/references_test.go b/internal/handler/references_test.go index ba106d66..07136e1c 100644 --- a/internal/handler/references_test.go +++ b/internal/handler/references_test.go @@ -15,6 +15,8 @@ import ( "go.lsp.dev/uri" ) +var addChartCallback = func(chart *charts.Chart) {} + func TestRefercesTemplateContext(t *testing.T) { content := ` {{ .Values.test }} @@ -94,7 +96,7 @@ func TestRefercesTemplateContext(t *testing.T) { } documents.DidOpen(&d, util.DefaultConfig) h := &langHandler{ - chartStore: charts.NewChartStore(uri.File("."), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("."), charts.NewChart, addChartCallback), documents: documents, yamllsConnector: &yamlls.Connector{}, } @@ -161,7 +163,7 @@ func TestRefercesTemplateContextWithTestFile(t *testing.T) { } documents.DidOpen(&d, util.DefaultConfig) h := &langHandler{ - chartStore: charts.NewChartStore(uri.File("."), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("."), charts.NewChart, addChartCallback), documents: documents, yamllsConnector: &yamlls.Connector{}, } @@ -219,7 +221,7 @@ func TestRefercesSingleLines(t *testing.T) { } documents.DidOpen(&d, util.DefaultConfig) h := &langHandler{ - chartStore: charts.NewChartStore(uri.File("."), charts.NewChart), + chartStore: charts.NewChartStore(uri.File("."), charts.NewChart, addChartCallback), documents: documents, yamllsConnector: &yamlls.Connector{}, } diff --git a/internal/handler/watched_files.go b/internal/handler/watched_files.go index 74e7eb05..b4dc37a3 100644 --- a/internal/handler/watched_files.go +++ b/internal/handler/watched_files.go @@ -4,16 +4,14 @@ import ( "context" "github.com/mrjosh/helm-ls/internal/charts" - "github.com/mrjosh/helm-ls/internal/util" lsp "go.lsp.dev/protocol" "go.lsp.dev/uri" "go.lsp.dev/jsonrpc2" ) -func (h *langHandler) NewChartWithWatchedFiles(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *charts.Chart { - logger.Debug("NewChartWithWatchedFiles", rootURI, valuesFilesConfig) - chart := charts.NewChart(rootURI, valuesFilesConfig) +func (h *langHandler) NewChartWithWatchedFiles(chart *charts.Chart) { + logger.Debug("NewChartWithWatchedFiles ", chart.RootURI) uris := make([]uri.URI, 0) for _, valuesFile := range chart.ValuesFiles.AllValuesFiles() { @@ -21,7 +19,6 @@ func (h *langHandler) NewChartWithWatchedFiles(rootURI uri.URI, valuesFilesConfi } go h.RegisterWatchedFiles(context.Background(), h.connPool, uris) - return chart } func (h *langHandler) RegisterWatchedFiles(ctx context.Context, conn jsonrpc2.Conn, files []uri.URI) { diff --git a/internal/language_features/template_context.go b/internal/language_features/template_context.go index 614fda78..34b584cb 100644 --- a/internal/language_features/template_context.go +++ b/internal/language_features/template_context.go @@ -81,7 +81,7 @@ func (f *TemplateContextFeature) getDefinitionLocations(templateContext lsplocal } return locations case "Chart": - location, _ := f.Chart.GetValueLocation(templateContext.Tail()) + location, _ := f.Chart.GetMetadataLocation(templateContext.Tail()) return []lsp.Location{location} } return locations diff --git a/internal/language_features/template_context_hover_test.go b/internal/language_features/template_context_hover_test.go index 02c92e47..515af407 100644 --- a/internal/language_features/template_context_hover_test.go +++ b/internal/language_features/template_context_hover_test.go @@ -123,6 +123,7 @@ key: ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"global": map[string]interface{}{"key": "value"}}, URI: "file://tmp/charts/subchart/values.yaml"}, }, + RootURI: uri.File("/tmp/charts/subchart"), ParentChart: charts.ParentChart{ ParentChartURI: uri.New("file://tmp/"), HasParent: true, @@ -163,6 +164,7 @@ value ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"key": "value"}, URI: "file://tmp/charts/subchart/values.yaml"}, }, + RootURI: uri.File("/tmp/charts/subchart"), ParentChart: charts.ParentChart{ ParentChartURI: uri.New("file://tmp/"), HasParent: true, @@ -201,6 +203,7 @@ value ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"key": "value"}, URI: "file://tmp/charts/subchart/charts/subsubchart/values.yaml"}, }, + RootURI: uri.File("/tmp/charts/subchart/charts/subsubchart"), ParentChart: charts.ParentChart{ ParentChartURI: uri.New("file://tmp/charts/subchart"), HasParent: true, @@ -213,6 +216,7 @@ value ValuesFiles: &charts.ValuesFiles{ MainValuesFile: &charts.ValuesFile{Values: map[string]interface{}{"subsubchart": map[string]interface{}{"key": "middleValue"}}, URI: "file://tmp/charts/subchart/values.yaml"}, }, + RootURI: uri.File("/tmp/charts/subchart"), ParentChart: charts.ParentChart{ ParentChartURI: uri.New("file://tmp/"), HasParent: true, diff --git a/internal/lsp/document_store.go b/internal/lsp/document_store.go index 025ae07b..b7cb0a41 100644 --- a/internal/lsp/document_store.go +++ b/internal/lsp/document_store.go @@ -56,17 +56,17 @@ func (s *DocumentStore) Store(filename string, content []byte, helmlsConfig util return } ast := ParseAst(nil, string(content)) - fileUri := uri.File(filename) - s.documents.Store(fileUri.Filename(), + fileURI := uri.File(filename) + s.documents.Store(fileURI.Filename(), &Document{ - URI: fileUri, + URI: fileURI, Path: filename, Content: string(content), Ast: ast, DiagnosticsCache: NewDiagnosticsCache(helmlsConfig), IsOpen: false, SymbolTable: NewSymbolTable(ast, content), - IsYaml: IsYamlDocument(fileUri, helmlsConfig.YamllsConfiguration), + IsYaml: IsYamlDocument(fileURI, helmlsConfig.YamllsConfiguration), }, ) } diff --git a/internal/util/yaml.go b/internal/util/yaml.go index c46aed0c..77f614b0 100644 --- a/internal/util/yaml.go +++ b/internal/util/yaml.go @@ -6,7 +6,6 @@ import ( lsp "go.lsp.dev/protocol" yamlv3 "gopkg.in/yaml.v3" - "helm.sh/helm/v3/pkg/chartutil" ) func GetPositionOfNode(node *yamlv3.Node, query []string) (lsp.Position, error) { @@ -80,12 +79,3 @@ func ReadYamlToNode(data []byte) (node yamlv3.Node, err error) { err = yamlv3.Unmarshal(data, &node) return node, err } - -func ValuesToYamlNode(values chartutil.Values) (node yamlv3.Node, err error) { - yaml, error := values.YAML() - if error != nil { - return yamlv3.Node{}, error - } - err = yamlv3.Unmarshal([]byte(yaml), &node) - return node, err -} From 6bed5f2cc786380839cb55b50790b0fdda999a0c Mon Sep 17 00:00:00 2001 From: qvalentin Date: Sun, 25 Aug 2024 15:13:34 +0200 Subject: [PATCH 5/6] fix(hover): correct helm code formatting tests and cleanup feat(hover): remove newlines in hover fix(windows): test with different linebreaks fix: test should work on windows now minor changes fix: minor change fix: minor fixes minor fix --- internal/charts/chart_values_files.go | 2 +- internal/charts/dependency_files.go | 2 +- internal/charts/helm_chart.go | 1 - internal/charts/metadata.go | 6 +-- internal/charts/values_file.go | 8 ++-- internal/charts/values_file_test.go | 2 +- internal/handler/definition_chart_test.go | 20 ++++----- internal/handler/hover_main_test.go | 43 +++++++++++++++---- internal/handler/initialization.go | 4 +- internal/handler/watched_files.go | 3 ++ internal/language_features/includes.go | 4 +- .../language_features/template_context.go | 2 +- .../template_context_hover_test.go | 16 ------- internal/lsp/document.go | 4 +- internal/protocol/hover.go | 17 +++++--- internal/protocol/hover_test.go | 25 +++++------ internal/util/values.go | 4 ++ internal/util/values_test.go | 2 +- 18 files changed, 90 insertions(+), 75 deletions(-) diff --git a/internal/charts/chart_values_files.go b/internal/charts/chart_values_files.go index 4a90467d..73b0e769 100644 --- a/internal/charts/chart_values_files.go +++ b/internal/charts/chart_values_files.go @@ -17,7 +17,7 @@ func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) (resul recResult := map[uri.URI]*QueriedValuesFiles{} c.ResolveValueFilesRecursive(query, chartStore, recResult) - // TODO(@qvalentin): use maps.Values once we have Go 1.23 + // TODO: @qvalentin use maps.Values once we have Go 1.23 for _, valuesFiles := range recResult { result = append(result, valuesFiles) } diff --git a/internal/charts/dependency_files.go b/internal/charts/dependency_files.go index 387c102c..bf65c0f3 100644 --- a/internal/charts/dependency_files.go +++ b/internal/charts/dependency_files.go @@ -22,7 +22,7 @@ func (c *Chart) NewDependencyTemplateFile(chartName string, file *chart.File) *D } type PossibleDependencyFile interface { - GetContent() string + GetContent() []byte GetPath() string } diff --git a/internal/charts/helm_chart.go b/internal/charts/helm_chart.go index fbc1b761..a57a50e9 100644 --- a/internal/charts/helm_chart.go +++ b/internal/charts/helm_chart.go @@ -8,7 +8,6 @@ import ( "helm.sh/helm/v3/pkg/chart" ) -// TODO: this ignores the newChart callback present in the ChartStore func NewChartFromHelmChart(helmChart *chart.Chart, rootURI uri.URI) *Chart { return &Chart{ ValuesFiles: &ValuesFiles{ diff --git a/internal/charts/metadata.go b/internal/charts/metadata.go index 04d382f4..e8965944 100644 --- a/internal/charts/metadata.go +++ b/internal/charts/metadata.go @@ -21,13 +21,13 @@ func NewChartMetadata(rootURI uri.URI) *ChartMetadata { filePath := filepath.Join(rootURI.Filename(), chartutil.ChartfileName) contents, err := os.ReadFile(filePath) if err != nil { - logger.Error("Error loading Chart.yaml file", filePath, err) + logger.Error("Error loading Chart.yaml file ", filePath, err) return nil } chartNode, err := util.ReadYamlToNode(contents) if err != nil { - logger.Error("Error loading Chart.yaml file", rootURI, err) + logger.Error("Error loading Chart.yaml file ", rootURI, err) } return &ChartMetadata{ @@ -49,7 +49,7 @@ func NewChartMetadataForDependencyChart(metadata *chart.Metadata, URI uri.URI) * func loadChartMetadata(filePath string) chart.Metadata { chartMetadata, err := chartutil.LoadChartfile(filePath) if err != nil { - logger.Error("Error loading Chart.yaml file", filePath, err) + logger.Error("Error loading Chart.yaml file ", filePath, err) return chart.Metadata{} } return *chartMetadata diff --git a/internal/charts/values_file.go b/internal/charts/values_file.go index df6d866b..82624ab0 100644 --- a/internal/charts/values_file.go +++ b/internal/charts/values_file.go @@ -70,11 +70,11 @@ func parseYaml(content []byte) (chartutil.Values, yaml.Node) { } // GetContent implements PossibleDependencyFile. -func (d *ValuesFile) GetContent() string { - return string(d.rawContent) +func (v *ValuesFile) GetContent() []byte { + return v.rawContent } // GetPath implements PossibleDependencyFile. -func (d *ValuesFile) GetPath() string { - return d.URI.Filename() +func (v *ValuesFile) GetPath() string { + return v.URI.Filename() } diff --git a/internal/charts/values_file_test.go b/internal/charts/values_file_test.go index 58da03ff..c3faf6ed 100644 --- a/internal/charts/values_file_test.go +++ b/internal/charts/values_file_test.go @@ -49,7 +49,7 @@ func TestReload(t *testing.T) { func TestGetContent(t *testing.T) { tempDir := t.TempDir() - valuesContent := "foo: bar" + valuesContent := []byte(`foo: bar`) valuesFile := charts.NewValuesFileFromContent(uri.File(filepath.Join(tempDir, "values.yaml")), []byte(valuesContent)) assert.Equal(t, valuesContent, valuesFile.GetContent()) } diff --git a/internal/handler/definition_chart_test.go b/internal/handler/definition_chart_test.go index 49c8bff7..1de28335 100644 --- a/internal/handler/definition_chart_test.go +++ b/internal/handler/definition_chart_test.go @@ -36,14 +36,6 @@ type testCase struct { // Test definition on a real chart found in $rootUri func TestDefinitionChart(t *testing.T) { testCases := []testCase{ - { - `{{ .Values.subchart^WithoutGlobal }}`, - "charts/subchartexample/values.yaml", - 2, // TODO: this should also find the parent, but the parent of the chart is not found :? - lsp.Position{Line: 2, Character: 0}, - nil, - true, - }, { `{{ include "common.na^mes.name" . }}`, "charts/.helm_ls_cache/common/templates/_names.tpl", @@ -141,6 +133,14 @@ func TestDefinitionChart(t *testing.T) { nil, false, }, + { + `{{ .Values.subchart^WithoutGlobal }}`, + "charts/subchartexample/values.yaml", + 2, + lsp.Position{Line: 2, Character: 0}, + nil, + true, + }, } for _, tc := range testCases { @@ -164,8 +164,8 @@ func TestDefinitionChart(t *testing.T) { chart := charts.NewChart(rootUri, util.DefaultConfig.ValuesFilesConfig) -var addChartCallback = func(chart *charts.Chart) {} - chartStore := charts.NewChartStore(rootUri, charts.NewChart,addChartCallback) + addChartCallback := func(chart *charts.Chart) {} + chartStore := charts.NewChartStore(rootUri, charts.NewChart, addChartCallback) _, err = chartStore.GetChartForURI(rootUri) h := &langHandler{ chartStore: chartStore, diff --git a/internal/handler/hover_main_test.go b/internal/handler/hover_main_test.go index 1931a956..bcbcfd52 100644 --- a/internal/handler/hover_main_test.go +++ b/internal/handler/hover_main_test.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "github.com/mrjosh/helm-ls/internal/adapter/yamlls" @@ -29,7 +30,7 @@ func TestHoverMain(t *testing.T) { Line: 85, Character: 26, }, - expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\nvalue\n```"), + expected: fmt.Sprintf("### %s\n%s\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\nvalue\n```"), expectedError: nil, }, { @@ -38,7 +39,7 @@ func TestHoverMain(t *testing.T) { Line: 74, Character: 50, }, - expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\nfirst:\n some: value\nsecond:\n some: value\n\n```"), + expected: fmt.Sprintf("### %s\n%s\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\nfirst:\n some: value\nsecond:\n some: value\n```"), expectedError: nil, }, { @@ -47,7 +48,7 @@ func TestHoverMain(t *testing.T) { Line: 80, Character: 31, }, - expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\nvalue\n```"), + expected: fmt.Sprintf("### %s\n%s\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\nvalue\n```"), expectedError: nil, }, { @@ -56,7 +57,7 @@ func TestHoverMain(t *testing.T) { Line: 17, Character: 19, }, - expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\n{}\n\n```"), + expected: fmt.Sprintf("### %s\n%s\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\n{}\n```"), expectedError: nil, }, { @@ -83,7 +84,7 @@ func TestHoverMain(t *testing.T) { Line: 25, Character: 28, }, - expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\nimagePullSecrets: []\n\n```"), + expected: fmt.Sprintf("### %s\n%s\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\nimagePullSecrets: []\n```"), expectedError: nil, }, { @@ -128,7 +129,7 @@ func TestHoverMain(t *testing.T) { Line: 71, Character: 35, }, - expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\ningress.hosts:\n- host: chart-example.local\n paths:\n - path: /\n pathType: ImplementationSpecific\n\n```"), + expected: fmt.Sprintf("### %s\n%s\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\ningress.hosts:\n- host: chart-example.local\n paths:\n - path: /\n pathType: ImplementationSpecific\n```"), expectedError: nil, }, { @@ -137,7 +138,28 @@ func TestHoverMain(t *testing.T) { Line: 8, Character: 28, }, - expected: fmt.Sprintf("### %s\n%s\n\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\n1\n```"), + expected: fmt.Sprintf("### %s\n%s\n", filepath.Join("..", "..", "testdata", "example", "values.yaml"), "```yaml\n1\n```"), + expectedError: nil, + }, + { + desc: "Test hover include parameter", + position: lsp.Position{ + Line: 3, + Character: 28, + }, + expected: "### " + filepath.Join("..", "_helpers.tpl") + "\n```helm\n" + `{{- define "example.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} +` + "```\n", expectedError: nil, }, } @@ -162,12 +184,15 @@ func TestHoverMain(t *testing.T) { } documents.DidOpen(&d, util.DefaultConfig) - addChartCallback := func(chart *charts.Chart) {} h := &langHandler{ chartStore: charts.NewChartStore(uri.File("."), charts.NewChart, addChartCallback), documents: documents, yamllsConnector: &yamlls.Connector{}, + helmlsConfig: util.DefaultConfig, } + h.chartStore = charts.NewChartStore(uri.File("."), charts.NewChart, h.AddChartCallback) + chart, _ := h.chartStore.GetChartOrParentForDoc(fileURI) + h.LoadDocsOnNewChart(chart) result, err := h.Hover(context.Background(), &lsp.HoverParams{ TextDocumentPositionParams: lsp.TextDocumentPositionParams{ TextDocument: lsp.TextDocumentIdentifier{ @@ -180,7 +205,7 @@ func TestHoverMain(t *testing.T) { if result == nil { t.Fatal("Result is nil") } - assert.Equal(t, tt.expected, result.Contents.Value) + assert.Equal(t, tt.expected, strings.ReplaceAll(result.Contents.Value, "\r\n", "\n")) }) } } diff --git a/internal/handler/initialization.go b/internal/handler/initialization.go index 42ca2bb6..7a4ba313 100644 --- a/internal/handler/initialization.go +++ b/internal/handler/initialization.go @@ -28,7 +28,7 @@ func (h *langHandler) Initialize(ctx context.Context, params *lsp.InitializePara } logger.Debug("Initializing chartStore") - h.chartStore = charts.NewChartStore(workspaceURI, charts.NewChart, h.NewChartWithInitActions) + h.chartStore = charts.NewChartStore(workspaceURI, charts.NewChart, h.AddChartCallback) logger.Debug("Initializing done") return &lsp.InitializeResult{ @@ -96,7 +96,7 @@ func configureLogLevel(helmlsConfig util.HelmlsConfiguration) { } } -func (h *langHandler) NewChartWithInitActions(chart *charts.Chart) { +func (h *langHandler) AddChartCallback(chart *charts.Chart) { h.NewChartWithWatchedFiles(chart) go h.LoadDocsOnNewChart(chart) } diff --git a/internal/handler/watched_files.go b/internal/handler/watched_files.go index b4dc37a3..3acd9504 100644 --- a/internal/handler/watched_files.go +++ b/internal/handler/watched_files.go @@ -22,6 +22,9 @@ func (h *langHandler) NewChartWithWatchedFiles(chart *charts.Chart) { } func (h *langHandler) RegisterWatchedFiles(ctx context.Context, conn jsonrpc2.Conn, files []uri.URI) { + if conn == nil { + return + } watchers := make([]lsp.FileSystemWatcher, 0) for _, file := range files { diff --git a/internal/language_features/includes.go b/internal/language_features/includes.go index d0a9746e..1d8ab8e7 100644 --- a/internal/language_features/includes.go +++ b/internal/language_features/includes.go @@ -124,7 +124,7 @@ func (f *IncludesFeature) getDefinitionsHover(includeName string) protocol.Hover result = append(result, protocol.HoverResultWithFile{ Value: node.Content([]byte(doc.Content)), URI: doc.URI, - }.AsHelmCode()) + }) } } } @@ -139,7 +139,7 @@ func (f *IncludesCallFeature) Hover() (string, error) { } result := f.getDefinitionsHover(includeName) - return result.Format(f.GenericDocumentUseCase.Document.URI), nil + return result.FormatHelm(f.GenericDocumentUseCase.Document.URI), nil } func (f *IncludesCallFeature) Definition() (result []lsp.Location, err error) { diff --git a/internal/language_features/template_context.go b/internal/language_features/template_context.go index 34b584cb..8b61ea91 100644 --- a/internal/language_features/template_context.go +++ b/internal/language_features/template_context.go @@ -123,7 +123,7 @@ func (f *TemplateContextFeature) valuesHover(templateContext lsplocal.TemplateCo } } } - return hoverResults.Format(f.ChartStore.RootURI), nil + return hoverResults.FormatYaml(f.ChartStore.RootURI), nil } func (f *TemplateContextFeature) getMetadataField(v *chart.Metadata, fieldName string) string { diff --git a/internal/language_features/template_context_hover_test.go b/internal/language_features/template_context_hover_test.go index 515af407..4ba6dfde 100644 --- a/internal/language_features/template_context_hover_test.go +++ b/internal/language_features/template_context_hover_test.go @@ -44,7 +44,6 @@ func Test_langHandler_getValueHover(t *testing.T) { %s value %s - `, "```yaml", "```"), wantErr: false, }, @@ -65,10 +64,8 @@ value %s value %s - ### values.other.yaml "" - `, "```yaml", "```"), wantErr: false, }, @@ -87,9 +84,7 @@ value want: fmt.Sprintf(`### values.yaml %s nested: value - %s - `, "```yaml", "```"), wantErr: false, }, @@ -109,9 +104,7 @@ nested: value %s key: - nested: value - %s - `, "```yaml", "```"), wantErr: false, }, @@ -145,12 +138,10 @@ key: %s parentValue %s - ### `+filepath.Join("charts", "subchart", "values.yaml")+` %s value %s - `, "```yaml", "```", "```yaml", "```"), wantErr: false, }, @@ -186,12 +177,10 @@ value %s parentValue %s - ### `+filepath.Join("charts", "subchart", "values.yaml")+` %s value %s - `, "```yaml", "```", "```yaml", "```"), wantErr: false, }, @@ -239,17 +228,14 @@ value %s parentValue %s - ### `+filepath.Join("charts", "subchart", "values.yaml")+` %s middleValue %s - ### `+filepath.Join("charts", "subchart", "charts", "subsubchart", "values.yaml")+` %s value %s - `, "```yaml", "```", "```yaml", "```", "```yaml", "```"), wantErr: false, }, @@ -274,7 +260,6 @@ value %s 1.2345 %s - `, "```yaml", "```"), wantErr: false, }, @@ -299,7 +284,6 @@ value %s hello %s - `, "```yaml", "```"), wantErr: false, }, diff --git a/internal/lsp/document.go b/internal/lsp/document.go index cf58a273..00fb9a57 100644 --- a/internal/lsp/document.go +++ b/internal/lsp/document.go @@ -80,8 +80,8 @@ func (d *Document) getLines() []string { } // GetContent implements PossibleDependencyFile. -func (d *Document) GetContent() string { - return d.Content +func (d *Document) GetContent() []byte { + return []byte(d.Content) } // GetPath implements PossibleDependencyFile. diff --git a/internal/protocol/hover.go b/internal/protocol/hover.go index caab89ec..db7bfc5f 100644 --- a/internal/protocol/hover.go +++ b/internal/protocol/hover.go @@ -14,14 +14,17 @@ type HoverResultWithFile struct { URI uri.URI } -func (h HoverResultWithFile) AsHelmCode() HoverResultWithFile { - h.Value = fmt.Sprintf("```%s\n%s\n```", "helm", h.Value) - return h +type HoverResultsWithFiles []HoverResultWithFile + +func (h HoverResultsWithFiles) FormatHelm(rootURI uri.URI) string { + return h.format(rootURI, "helm") } -type HoverResultsWithFiles []HoverResultWithFile +func (h HoverResultsWithFiles) FormatYaml(rootURI uri.URI) string { + return h.format(rootURI, "yaml") +} -func (h HoverResultsWithFiles) Format(rootURI uri.URI) string { +func (h HoverResultsWithFiles) format(rootURI uri.URI, codeLanguage string) string { var formatted string sort.Slice(h, func(i, j int) bool { return h[i].URI > h[j].URI @@ -32,13 +35,13 @@ func (h HoverResultsWithFiles) Format(rootURI uri.URI) string { if value == "" { value = "\"\"" } else { - value = fmt.Sprintf("```yaml\n%s\n```", value) + value = fmt.Sprintf("```%s\n%s\n```", codeLanguage, value) } filepath, err := filepath.Rel(rootURI.Filename(), result.URI.Filename()) if err != nil { filepath = result.URI.Filename() } - formatted += fmt.Sprintf("### %s\n%s\n\n", filepath, value) + formatted += fmt.Sprintf("### %s\n%s\n", filepath, value) } return formatted } diff --git a/internal/protocol/hover_test.go b/internal/protocol/hover_test.go index f1d1b59d..fbfd2f6b 100644 --- a/internal/protocol/hover_test.go +++ b/internal/protocol/hover_test.go @@ -23,20 +23,17 @@ func TestHoverResultsWithFiles_Format(t *testing.T) { %s value3 %s - ### file2.yaml %s value2 %s - ### file1.yaml %s value1 %s - `, "```yaml", "```", "```yaml", "```", "```yaml", "```") - formatted := results.Format(rootURI) + formatted := results.FormatYaml(rootURI) assert.Equal(t, expected, formatted, "The formatted output should match the expected output") } @@ -48,10 +45,9 @@ func TestHoverResultsWithFiles_Format_EmptyValue(t *testing.T) { } expected := `### file1.yaml "" - ` - formatted := results.Format(rootURI) + formatted := results.FormatYaml(rootURI) assert.Equal(t, expected, formatted, "The formatted output should match the expected output") } @@ -66,18 +62,19 @@ func TestHoverResultsWithFiles_Format_DifferenPath(t *testing.T) { %s value %s - `, filepath.Join("..", "..", "..", "invalid", "uri"), "```yaml", "```") - formatted := results.Format(rootURI) + formatted := results.FormatYaml(rootURI) assert.Equal(t, expected, formatted, "The formatted output should match the expected output") } func TestHoverResultWithFile_WithHelmCode(t *testing.T) { - hoverResult := HoverResultWithFile{ - Value: "some helm code", - URI: uri.New("file:///home/user/project/file1.yaml"), - }.AsHelmCode() + hoverResult := HoverResultsWithFiles{ + { + Value: "some helm code", + URI: uri.New("file:///home/user/project/file1.yaml"), + }, + } - expectedValue := "```helm\nsome helm code\n```" - assert.Equal(t, expectedValue, hoverResult.Value, "The value should be formatted with Helm code block") + expectedValue := "### file1.yaml\n```helm\nsome helm code\n```\n" + assert.Equal(t, expectedValue, hoverResult.FormatHelm(uri.New("file:///home/user/project")), "The value should be formatted with Helm code block") } diff --git a/internal/util/values.go b/internal/util/values.go index b446b1a6..b4e409f4 100644 --- a/internal/util/values.go +++ b/internal/util/values.go @@ -200,6 +200,10 @@ func FormatToYAML(field reflect.Value, fieldName string) string { func toYAML(value interface{}) string { valBytes, _ := yaml.Marshal(value) + // remove trailing new line + if len(valBytes) > 0 && valBytes[len(valBytes)-1] == '\n' { + valBytes = valBytes[:len(valBytes)-1] + } return string(valBytes) } diff --git a/internal/util/values_test.go b/internal/util/values_test.go index 3672307e..911ae6b9 100644 --- a/internal/util/values_test.go +++ b/internal/util/values_test.go @@ -74,6 +74,6 @@ func TestValuesRangeLookupOnMapping(t *testing.T) { inputCopy := append([]string{}, input...) result, err := GetTableOrValueForSelector(values, input) assert.NoError(t, err) - assert.Equal(t, "a: 1\n", result) + assert.Equal(t, "a: 1", result) assert.Equal(t, inputCopy, input) } From 9b3ba2ed772af05361f25dbcdefdae97adc370db Mon Sep 17 00:00:00 2001 From: qvalentin Date: Sun, 25 Aug 2024 23:25:14 +0200 Subject: [PATCH 6/6] docs(dependencies): add not about helm dependency build --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index fd4f6cc4..19be4f33 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Helm-ls is a [helm](https://github.com/helm/helm) language server protocol [LSP] - [Manual download](#manual-download) - [Make it executable](#make-it-executable) - [Integration with yaml-language-server](#integration-with-yaml-language-server) + - [Dependency Charts](#dependency-charts) - [Configuration options](#configuration-options) - [General](#general) - [Values Files](#values-files) @@ -140,6 +141,15 @@ apiVersion: keda.sh/v1alpha1 kind: ScaledObject ``` +### Dependency Charts + +Helm-ls can process dependency charts to provide autocompletion, hover etc. with values from the dependencies. +For this the dependency charts have to be downloaded. Run the following command in your project to download them: + +```bash +helm dependency build +``` + ## Configuration options You can configure helm-ls with lsp workspace configurations.