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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3009,6 +3009,37 @@ THE SOFTWARE.

```

## github.com/goccy/go-yaml

* Name: github.com/goccy/go-yaml
* Version: v1.18.0
* License: [MIT](https://github.com/goccy/go-yaml/blob/v1.18.0/LICENSE)

```
MIT License

Copyright (c) 2019 Masaaki Goshima

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

```

## github.com/gogo/protobuf

* Name: github.com/gogo/protobuf
Expand Down
142 changes: 142 additions & 0 deletions e2e/service_provisioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,148 @@ func TestUpdateDatabaseServiceStable(t *testing.T) {
t.Log("Service stability test completed successfully")
}

// TestUpdateMCPServiceConfig tests that updating a service's config (e.g.,
// changing the LLM model) triggers a successful database update and the service
// instance remains running. This exercises the MCPConfigResource.Update() path
// where config.yaml is regenerated while tokens.yaml/users.yaml are preserved.
func TestUpdateMCPServiceConfig(t *testing.T) {
t.Parallel()

host1 := fixture.HostIDs()[0]

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()

t.Log("Creating database with MCP service (claude-sonnet-4-5)")

db := fixture.NewDatabaseFixture(ctx, t, &controlplane.CreateDatabaseRequest{
Spec: &controlplane.DatabaseSpec{
DatabaseName: "test_update_mcp_config",
DatabaseUsers: []*controlplane.DatabaseUserSpec{
{
Username: "admin",
Password: pointerTo("testpassword"),
DbOwner: pointerTo(true),
Attributes: []string{"LOGIN", "SUPERUSER"},
},
},
Port: pointerTo(0),
Nodes: []*controlplane.DatabaseNodeSpec{
{
Name: "n1",
HostIds: []controlplane.Identifier{controlplane.Identifier(host1)},
},
},
Services: []*controlplane.ServiceSpec{
{
ServiceID: "mcp-server",
ServiceType: "mcp",
Version: "latest",
HostIds: []controlplane.Identifier{controlplane.Identifier(host1)},
Config: map[string]any{
"llm_provider": "anthropic",
"llm_model": "claude-sonnet-4-5",
"anthropic_api_key": "sk-ant-test-key-config",
},
},
},
},
})

require.Len(t, db.ServiceInstances, 1, "Expected 1 service instance")

// Wait for service to reach "running" state before recording baseline
if db.ServiceInstances[0].State != "running" {
t.Log("Service not yet running, waiting...")
deadline := time.Now().Add(5 * time.Minute)
for time.Now().Before(deadline) {
time.Sleep(5 * time.Second)
err := db.Refresh(ctx)
require.NoError(t, err, "Failed to refresh database")
if len(db.ServiceInstances) > 0 && db.ServiceInstances[0].State == "running" {
break
}
}
}
require.Equal(t, "running", db.ServiceInstances[0].State, "Service should be running")

// Record identifiers before the update to verify stability after
serviceInstanceID := db.ServiceInstances[0].ServiceInstanceID
createdAtBefore := db.ServiceInstances[0].CreatedAt

t.Logf("Baseline: service_instance_id=%s, created_at=%s", serviceInstanceID, createdAtBefore)

t.Log("Updating database with changed service config (claude-sonnet-4-5 -> claude-haiku-4-5)")

// Update database with a changed service config (different LLM model).
// This should trigger MCPConfigResource.Update() to regenerate config.yaml
// without deleting/recreating the service instance.
err := db.Update(ctx, UpdateOptions{
Spec: &controlplane.DatabaseSpec{
DatabaseName: "test_update_mcp_config",
DatabaseUsers: []*controlplane.DatabaseUserSpec{
{
Username: "admin",
Password: pointerTo("testpassword"),
DbOwner: pointerTo(true),
Attributes: []string{"LOGIN", "SUPERUSER"},
},
},
Port: pointerTo(0),
Nodes: []*controlplane.DatabaseNodeSpec{
{
Name: "n1",
HostIds: []controlplane.Identifier{controlplane.Identifier(host1)},
},
},
Services: []*controlplane.ServiceSpec{
{
ServiceID: "mcp-server",
ServiceType: "mcp",
Version: "latest",
HostIds: []controlplane.Identifier{controlplane.Identifier(host1)},
Config: map[string]any{
"llm_provider": "anthropic",
"llm_model": "claude-haiku-4-5",
"anthropic_api_key": "sk-ant-test-key-config",
},
},
},
},
})
require.NoError(t, err, "Failed to update database")

t.Log("Database updated, verifying service instance was updated in-place")

// Verify the service instance still exists
require.Len(t, db.ServiceInstances, 1, "Should still have 1 service instance")

// The service instance may briefly show "creating" after the update before
// the monitor converges to "running". Poll until it settles.
if db.ServiceInstances[0].State != "running" {
t.Logf("Service state is %q, waiting for running...", db.ServiceInstances[0].State)
deadline := time.Now().Add(5 * time.Minute)
for time.Now().Before(deadline) {
time.Sleep(5 * time.Second)
err = db.Refresh(ctx)
require.NoError(t, err, "Failed to refresh database")
if len(db.ServiceInstances) > 0 && db.ServiceInstances[0].State == "running" {
break
}
}
}
require.Equal(t, "running", db.ServiceInstances[0].State, "Service should be running after update")

// The key assertions: ServiceInstanceID and CreatedAt should be unchanged,
// proving the service was updated in-place (config.yaml regenerated) rather
// than deleted and recreated.
assert.Equal(t, serviceInstanceID, db.ServiceInstances[0].ServiceInstanceID, "Service instance ID should be unchanged (not recreated)")
assert.Equal(t, createdAtBefore, db.ServiceInstances[0].CreatedAt, "Service created_at should be unchanged (not recreated)")

t.Logf("Service instance %s updated in-place after config change", serviceInstanceID)
t.Log("MCP service config update test completed successfully")
}

// TestUpdateDatabaseRemoveService tests removing a service from a database.
func TestUpdateDatabaseRemoveService(t *testing.T) {
t.Parallel()
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ require (
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/crypto v0.46.0
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
Expand Down
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
Expand All @@ -129,6 +131,10 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
Expand Down Expand Up @@ -235,6 +241,8 @@ github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHT
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
Expand Down Expand Up @@ -273,6 +281,8 @@ github.com/mackerelio/go-osstat v0.2.5 h1:+MqTbZUhoIt4m8qzkVoXUJg1EuifwlAJSk4Yl2
github.com/mackerelio/go-osstat v0.2.5/go.mod h1:atxwWF+POUZcdtR1wnsUcQxTytoHG4uhl2AKKzrOajY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqCDBcAhLXoi3TzC27Zad/Vn+gnVQ=
github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s=
github.com/manveru/gobdd v0.0.0-20131210092515-f1a17fdd710b h1:3E44bLeN8uKYdfQqVQycPnaVviZdBLbizFhU49mtbe4=
Expand Down Expand Up @@ -300,14 +310,22 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down Expand Up @@ -400,6 +418,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/wI2L/jsondiff v0.6.1 h1:ISZb9oNWbP64LHnu4AUhsMF5W0FIj5Ok3Krip9Shqpw=
github.com/wI2L/jsondiff v0.6.1/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM=
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
1 change: 1 addition & 0 deletions server/internal/api/apiv1/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func isSensitiveConfigKey(key string) bool {
"api_key", "apikey", "api-key",
"credential", "private_key", "private-key",
"access_key", "access-key",
"init_users", // mcp 'init_users' contains embedded passwords and must be stripped
}
for _, p := range patterns {
if strings.Contains(k, p) {
Expand Down
65 changes: 15 additions & 50 deletions server/internal/api/apiv1/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func validateDatabaseSpec(spec *api.DatabaseSpec) error {
}
seenServiceIDs.Add(string(svc.ServiceID))

errs = append(errs, validateServiceSpec(svc, svcPath)...)
errs = append(errs, validateServiceSpec(svc, svcPath, false)...)
}

return errors.Join(errs...)
Expand Down Expand Up @@ -176,6 +176,12 @@ func validateDatabaseUpdate(old *database.Spec, new *api.DatabaseSpec) error {
}
}

// Validate services with isUpdate=true to reject bootstrap-only fields
for i, svc := range new.Services {
svcPath := []string{"services", arrayIndexPath(i)}
errs = append(errs, validateServiceSpec(svc, svcPath, true)...)
}

return errors.Join(errs...)
}

Expand Down Expand Up @@ -232,7 +238,7 @@ func validateNode(node *api.DatabaseNodeSpec, path []string) []error {
return errs
}

func validateServiceSpec(svc *api.ServiceSpec, path []string) []error {
func validateServiceSpec(svc *api.ServiceSpec, path []string, isUpdate bool) []error {
var errs []error

// Validate service_id
Expand Down Expand Up @@ -269,7 +275,7 @@ func validateServiceSpec(svc *api.ServiceSpec, path []string) []error {

// Validate config based on service_type
if svc.ServiceType == "mcp" {
errs = append(errs, validateMCPServiceConfig(svc.Config, appendPath(path, "config"))...)
errs = append(errs, validateMCPServiceConfig(svc.Config, appendPath(path, "config"), isUpdate)...)
}

// Validate cpus if provided
Expand All @@ -288,54 +294,13 @@ func validateServiceSpec(svc *api.ServiceSpec, path []string) []error {
return errs
}

// TODO: this is still a WIP based on use-case reqs...
func validateMCPServiceConfig(config map[string]any, path []string) []error {
var errs []error

// Required fields for MCP service
requiredFields := []string{"llm_provider", "llm_model"}
for _, field := range requiredFields {
if _, ok := config[field]; !ok {
err := fmt.Errorf("missing required field '%s'", field)
errs = append(errs, newValidationError(err, path))
}
func validateMCPServiceConfig(config map[string]any, path []string, isUpdate bool) []error {
_, errs := database.ParseMCPServiceConfig(config, isUpdate)
var result []error
for _, err := range errs {
result = append(result, newValidationError(err, path))
}

// Validate llm_provider
if val, exists := config["llm_provider"]; exists {
provider, ok := val.(string)
if !ok {
err := errors.New("llm_provider must be a string")
errs = append(errs, newValidationError(err, appendPath(path, mapKeyPath("llm_provider"))))
} else {
validProviders := []string{"anthropic", "openai", "ollama"}
if !slices.Contains(validProviders, provider) {
err := fmt.Errorf("unsupported llm_provider '%s' (must be one of: %s)", provider, strings.Join(validProviders, ", "))
errs = append(errs, newValidationError(err, appendPath(path, mapKeyPath("llm_provider"))))
}

// Provider-specific API key validation
switch provider {
case "anthropic":
if _, ok := config["anthropic_api_key"]; !ok {
err := errors.New("missing required field 'anthropic_api_key' for anthropic provider")
errs = append(errs, newValidationError(err, path))
}
case "openai":
if _, ok := config["openai_api_key"]; !ok {
err := errors.New("missing required field 'openai_api_key' for openai provider")
errs = append(errs, newValidationError(err, path))
}
case "ollama":
if _, ok := config["ollama_url"]; !ok {
err := errors.New("missing required field 'ollama_url' for ollama provider")
errs = append(errs, newValidationError(err, path))
}
}
}
}

return errs
return result
}

func validateCPUs(value *string, path []string) []error {
Expand Down
Loading