diff --git a/clab/clab.go b/clab/clab.go index 9c7d2c763..681dc72a2 100644 --- a/clab/clab.go +++ b/clab/clab.go @@ -138,7 +138,7 @@ func WithTopoPath(path, varsFile string) ClabOption { return err } // if the path is not a local file and a URL, download the file and store it in the tmp dir - case !utils.FileOrDirExists(path) && utils.IsHttpURL(path): + case !utils.FileOrDirExists(path) && utils.IsHttpURL(path, true): file, err = c.downloadTopoFile(path) if err != nil { return err diff --git a/clab/config.go b/clab/config.go index 62951b65a..1a28a93dd 100644 --- a/clab/config.go +++ b/clab/config.go @@ -264,7 +264,7 @@ func (c *CLab) processStartupConfig(nodeCfg *types.NodeConfig) error { // it contains at least one newline isEmbeddedConfig := strings.Count(p, "\n") >= 1 // downloadable config starts with http(s):// - isDownloadableConfig := utils.IsHttpURL(p) + isDownloadableConfig := utils.IsHttpURL(p, false) if isEmbeddedConfig || isDownloadableConfig { // both embedded and downloadable configs are require clab tmp dir to be created diff --git a/cmd/root.go b/cmd/root.go index 734c9fa0d..e2043b597 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -120,7 +120,7 @@ func getTopoFilePath(cmd *cobra.Command) error { if err != nil { return err } - case utils.IsHttpURL(topo): + case utils.IsHttpURL(topo, true): // canonize the passed topo as URL by adding https schema if it was missing if !strings.HasPrefix(topo, "http://") && !strings.HasPrefix(topo, "https://") { topo = "https://" + topo diff --git a/nodes/srl/srl.go b/nodes/srl/srl.go index 6040822ff..7786b6d37 100644 --- a/nodes/srl/srl.go +++ b/nodes/srl/srl.go @@ -252,7 +252,7 @@ func (s *srl) PreDeploy(_ context.Context, params *nodes.PreDeployParams) error for _, fullpath := range agents { basename := filepath.Base(fullpath) // if it is a url extract filename from url or content-disposition header - if utils.IsHttpURL(fullpath) { + if utils.IsHttpURL(fullpath, false) { basename = utils.FilenameForURL(fullpath) } // enforce yml extension diff --git a/utils/file.go b/utils/file.go index 59eb10cb8..ce14d55de 100644 --- a/utils/file.go +++ b/utils/file.go @@ -49,7 +49,7 @@ func FileOrDirExists(filename string) bool { // mode is the desired target file permissions, e.g. "0644". func CopyFile(src, dst string, mode os.FileMode) (err error) { var sfi os.FileInfo - if !IsHttpURL(src) { + if !IsHttpURL(src, false) { sfi, err = os.Stat(src) if err != nil { return err @@ -82,7 +82,21 @@ func CopyFile(src, dst string, mode os.FileMode) (err error) { } // IsHttpURL checks if the url is a downloadable HTTP URL. -func IsHttpURL(s string) bool { +// The allowSchemaless toggle when set to true will allow URLs without a schema +// such as "srlinux.dev/clab-srl". This is shortened notion that is used with +// "deploy -t " only. +// Other callers of IsHttpURL should set the toggle to false. +func IsHttpURL(s string, allowSchemaless bool) bool { + // '-' denotes stdin and not the URL + if s == "-" { + return false + } + + // + if !allowSchemaless && !strings.HasPrefix(s, "http://") && !strings.HasPrefix(s, "https://") { + return false + } + if !strings.HasPrefix(s, "http://") && !strings.HasPrefix(s, "https://") { s = "https://" + s } @@ -100,7 +114,7 @@ func IsHttpURL(s string) bool { func CopyFileContents(src, dst string, mode os.FileMode) (err error) { var in io.ReadCloser - if IsHttpURL(src) { + if IsHttpURL(src, false) { client := NewHTTPClient() // download using client @@ -280,7 +294,7 @@ func FilenameForURL(rawUrl string) string { } // try extracting the filename from "content-disposition" header - if IsHttpURL(rawUrl) { + if IsHttpURL(rawUrl, false) { resp, err := http.Head(rawUrl) if err != nil { return filepath.Base(u.Path) diff --git a/utils/file_test.go b/utils/file_test.go index ebafe147c..2e96c454d 100644 --- a/utils/file_test.go +++ b/utils/file_test.go @@ -87,36 +87,52 @@ func TestFileLines(t *testing.T) { func TestIsHttpURL(t *testing.T) { tests := []struct { - name string - url string - want bool + name string + url string + allowSchemaless bool + want bool }{ { - name: "Valid HTTP URL", - url: "http://example.com", - want: true, + name: "Valid HTTP URL", + url: "http://example.com", + allowSchemaless: false, + want: true, + }, + { + name: "Valid HTTPS URL", + url: "https://example.com", + allowSchemaless: false, + want: true, + }, + { + name: "Valid URL without scheme", + url: "srlinux.dev/clab-srl", + allowSchemaless: true, + want: true, }, { - name: "Valid HTTPS URL", - url: "https://example.com", - want: true, + name: "Valid URL without scheme and schemaless not allowed", + url: "srlinux.dev/clab-srl", + allowSchemaless: false, + want: false, }, { - name: "Valid URL without scheme", - url: "srlinux.dev/clab-srl", - want: true, + name: "Invalid URL", + url: "/foo/bar", + allowSchemaless: false, + want: false, }, { - name: "Invalid URL", - url: "/foo/bar", - want: false, + name: "stdin symbol '-'", + url: "-", + allowSchemaless: false, + want: false, }, - // Add more test cases as needed } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := IsHttpURL(tt.url); got != tt.want { + if got := IsHttpURL(tt.url, tt.allowSchemaless); got != tt.want { t.Errorf("IsHttpUri() = %v, want %v", got, tt.want) } })