mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
fix(helm-lint): Add HTTP/HTTPS URL support for json schema references
Signed-off-by: Isaiah Lewis <isaiah@roof12.com>
(cherry picked from commit fa73b6743b)
This commit is contained in:
parent
093c885548
commit
854370978e
2 changed files with 168 additions and 0 deletions
|
|
@ -21,12 +21,52 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v6"
|
||||
|
||||
"net/http"
|
||||
|
||||
"helm.sh/helm/v3/internal/version"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// HTTPURLLoader implements a loader for HTTP/HTTPS URLs
|
||||
type HTTPURLLoader http.Client
|
||||
|
||||
func (l *HTTPURLLoader) Load(urlStr string) (any, error) {
|
||||
client := (*http.Client)(l)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create HTTP request for %s: %w", urlStr, err)
|
||||
}
|
||||
req.Header.Set("User-Agent", version.GetUserAgent())
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HTTP request failed for %s: %w", urlStr, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP request to %s returned status %d (%s)", urlStr, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
}
|
||||
|
||||
return jsonschema.UnmarshalJSON(resp.Body)
|
||||
}
|
||||
|
||||
// newHTTPURLLoader creates a HTTP URL loader with proxy support.
|
||||
func newHTTPURLLoader() *HTTPURLLoader {
|
||||
httpLoader := HTTPURLLoader(http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
})
|
||||
return &httpLoader
|
||||
}
|
||||
|
||||
// ValidateAgainstSchema checks that values does not violate the structure laid out in schema
|
||||
func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error {
|
||||
var sb strings.Builder
|
||||
|
|
@ -68,7 +108,15 @@ func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error
|
|||
return err
|
||||
}
|
||||
|
||||
// Configure compiler with loaders for different URL schemes
|
||||
loader := jsonschema.SchemeURLLoader{
|
||||
"file": jsonschema.FileLoader{},
|
||||
"http": newHTTPURLLoader(),
|
||||
"https": newHTTPURLLoader(),
|
||||
}
|
||||
|
||||
compiler := jsonschema.NewCompiler()
|
||||
compiler.UseLoader(loader)
|
||||
err = compiler.AddResource("file:///values.schema.json", schema)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ limitations under the License.
|
|||
package chartutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
|
|
@ -105,6 +108,21 @@ const subchartSchema = `{
|
|||
}
|
||||
`
|
||||
|
||||
const subchartSchema2020 = `{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "Values",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"contains": { "type": "string" },
|
||||
"unevaluatedItems": { "type": "number" }
|
||||
}
|
||||
},
|
||||
"required": ["data"]
|
||||
}
|
||||
`
|
||||
|
||||
func TestValidateAgainstSchema(t *testing.T) {
|
||||
subchartJSON := []byte(subchartSchema)
|
||||
subchart := &chart.Chart{
|
||||
|
|
@ -166,3 +184,105 @@ func TestValidateAgainstSchemaNegative(t *testing.T) {
|
|||
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAgainstSchema2020(t *testing.T) {
|
||||
subchartJSON := []byte(subchartSchema2020)
|
||||
subchart := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "subchart",
|
||||
},
|
||||
Schema: subchartJSON,
|
||||
}
|
||||
chrt := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "chrt",
|
||||
},
|
||||
}
|
||||
chrt.AddDependency(subchart)
|
||||
|
||||
vals := map[string]interface{}{
|
||||
"name": "John",
|
||||
"subchart": map[string]interface{}{
|
||||
"data": []any{"hello", 12},
|
||||
},
|
||||
}
|
||||
|
||||
if err := ValidateAgainstSchema(chrt, vals); err != nil {
|
||||
t.Errorf("Error validating Values against Schema: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAgainstSchema2020Negative(t *testing.T) {
|
||||
subchartJSON := []byte(subchartSchema2020)
|
||||
subchart := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "subchart",
|
||||
},
|
||||
Schema: subchartJSON,
|
||||
}
|
||||
chrt := &chart.Chart{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "chrt",
|
||||
},
|
||||
}
|
||||
chrt.AddDependency(subchart)
|
||||
|
||||
vals := map[string]interface{}{
|
||||
"name": "John",
|
||||
"subchart": map[string]interface{}{
|
||||
"data": []any{12},
|
||||
},
|
||||
}
|
||||
|
||||
var errString string
|
||||
if err := ValidateAgainstSchema(chrt, vals); err == nil {
|
||||
t.Fatalf("Expected an error, but got nil")
|
||||
} else {
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
expectedErrString := `subchart:
|
||||
- at '/data': no items match contains schema
|
||||
- at '/data/0': got number, want string
|
||||
`
|
||||
if errString != expectedErrString {
|
||||
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPURLLoader_Load(t *testing.T) {
|
||||
// Test successful JSON schema loading
|
||||
t.Run("successful load", func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"type": "object", "properties": {"name": {"type": "string"}}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
loader := newHTTPURLLoader()
|
||||
result, err := loader.Load(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got: %v", err)
|
||||
}
|
||||
if result == nil {
|
||||
t.Fatal("Expected result to be non-nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("HTTP error status", func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
loader := newHTTPURLLoader()
|
||||
_, err := loader.Load(server.URL)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for HTTP 404")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "404") {
|
||||
t.Errorf("Expected error message to contain '404', got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue