diff --git a/examples/demo-context/atmos.yaml b/examples/demo-context/atmos.yaml
index c4b245060..7ceb65b14 100644
--- a/examples/demo-context/atmos.yaml
+++ b/examples/demo-context/atmos.yaml
@@ -2,8 +2,7 @@ base_path: "./"
schemas:
atmos:
- manifest: "schemas/atmos-manifest.json"
-
+ manifest: "https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json"
# https://pkg.go.dev/text/template
templates:
settings:
diff --git a/internal/exec/validate_stacks.go b/internal/exec/validate_stacks.go
index 71438ba62..29c50bd15 100644
--- a/internal/exec/validate_stacks.go
+++ b/internal/exec/validate_stacks.go
@@ -1,11 +1,16 @@
package exec
import (
+ "context"
"fmt"
+ "net/url"
+ "os"
"path"
"reflect"
"strings"
+ "time"
+ "github.com/hashicorp/go-getter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -86,15 +91,20 @@ func ValidateStacks(cliConfig schema.CliConfiguration) error {
atmosManifestJsonSchemaFilePath = cliConfig.Schemas.Atmos.Manifest
} else if u.FileExists(atmosManifestJsonSchemaFileAbsPath) {
atmosManifestJsonSchemaFilePath = atmosManifestJsonSchemaFileAbsPath
+ } else if u.IsURL(cliConfig.Schemas.Atmos.Manifest) {
+ atmosManifestJsonSchemaFilePath, err = downloadSchemaFromURL(cliConfig.Schemas.Atmos.Manifest)
+ if err != nil {
+ return err
+ }
} else {
return fmt.Errorf("the Atmos JSON Schema file '%s' does not exist.\n"+
"It can be configured in the 'schemas.atmos.manifest' section in 'atmos.yaml', or provided using the 'ATMOS_SCHEMAS_ATMOS_MANIFEST' "+
"ENV variable or '--schemas-atmos-manifest' command line argument.\n"+
- "The path to the schema file should be an absolute path or a path relative to the 'base_path' setting in 'atmos.yaml'.",
+ "The path to the schema file should be an absolute path or a path relative to the 'base_path' setting in 'atmos.yaml'. \n"+
+ "Alternatively, you can specify a schema file using a URL that will be downloaded automatically.",
cliConfig.Schemas.Atmos.Manifest)
}
}
-
// Include (process and validate) all YAML files in the `stacks` folder in all subfolders
includedPaths := []string{"**/*"}
// Don't exclude any YAML files for validation
@@ -340,3 +350,32 @@ func checkComponentStackMap(componentStackMap map[string]map[string][]string) ([
return res, nil
}
+
+// downloadSchemaFromURL downloads the Atmos JSON Schema file from the provided URL
+func downloadSchemaFromURL(manifestURL string) (string, error) {
+ parsedURL, err := url.Parse(manifestURL)
+ if err != nil {
+ return "", fmt.Errorf("invalid URL '%s': %w", manifestURL, err)
+ }
+ if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
+ return "", fmt.Errorf("unsupported URL scheme '%s' for schema manifest", parsedURL.Scheme)
+ }
+ tempDir := os.TempDir()
+ fileName, err := u.GetFileNameFromURL(manifestURL)
+ if err != nil || fileName == "" {
+ return "", fmt.Errorf("failed to get the file name from the URL '%s': %w", manifestURL, err)
+ }
+ atmosManifestJsonSchemaFilePath := path.Join(tempDir, fileName)
+ ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
+ defer cancel()
+ client := &getter.Client{
+ Ctx: ctx,
+ Dst: atmosManifestJsonSchemaFilePath,
+ Src: manifestURL,
+ Mode: getter.ClientModeFile,
+ }
+ if err = client.Get(); err != nil {
+ return "", fmt.Errorf("failed to download the Atmos JSON Schema file '%s' from the URL '%s': %w", fileName, manifestURL, err)
+ }
+ return atmosManifestJsonSchemaFilePath, nil
+}
diff --git a/pkg/utils/file_utils.go b/pkg/utils/file_utils.go
index 5cf32bb66..4c0d752a1 100644
--- a/pkg/utils/file_utils.go
+++ b/pkg/utils/file_utils.go
@@ -1,7 +1,9 @@
package utils
import (
+ "fmt"
"io/fs"
+ "net/url"
"os"
"path"
"path/filepath"
@@ -192,3 +194,40 @@ func SearchConfigFile(path string) (string, bool) {
}
return "", false
}
+
+// IsURL checks if a string is a URL
+func IsURL(s string) bool {
+ url, err := url.Parse(s)
+ if err != nil {
+ return false
+ }
+ validSchemes := []string{"http", "https"}
+ schemeValid := false
+ for _, scheme := range validSchemes {
+ if url.Scheme == scheme {
+ schemeValid = true
+ break
+ }
+ }
+ return schemeValid
+
+}
+
+// GetFileNameFromURL extracts the file name from a URL
+func GetFileNameFromURL(rawURL string) (string, error) {
+ if rawURL == "" {
+ return "", fmt.Errorf("empty URL provided")
+ }
+ parsedURL, err := url.Parse(rawURL)
+ if err != nil {
+ return "", err
+ }
+ // Extract the path from the URL
+ urlPath := parsedURL.Path
+ fileName := path.Base(urlPath)
+ if fileName == "/" || fileName == "." {
+ return "", fmt.Errorf("unable to extract filename from URL: %s", rawURL)
+ }
+ // Get the base name of the path
+ return fileName, nil
+}
diff --git a/website/docs/core-concepts/validate/json-schema.mdx b/website/docs/core-concepts/validate/json-schema.mdx
index d06a92416..a1b8a9e5f 100644
--- a/website/docs/core-concepts/validate/json-schema.mdx
+++ b/website/docs/core-concepts/validate/json-schema.mdx
@@ -43,6 +43,19 @@ schemas:
In the component [manifest](https://github.com/cloudposse/atmos/blob/master/examples/quick-start-advanced/stacks/catalog/vpc/defaults.yaml), add
the `settings.validation` section:
+### Use Remote Schemas
+
+You can specify remote schemas by setting the `manifest` field to a remote URL in your `atmos.yaml` configuration file.
+
+
+```yaml
+# Validation schemas (for validating atmos stacks and components)
+schemas:
+ atmos:
+ # You can specify a remote schema URL as well
+ manifest: "https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json"
+```
+
Add the following JSON Schema in the