diff --git a/.gitignore b/.gitignore index f30e142..edf2e2c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ jspm_packages # Optional npm cache directory .npm +# npm package lock file +package-lock.json + # Optional REPL history .node_repl_history diff --git a/.gometalinter.json b/.gometalinter.json index 5825bac..291500f 100644 --- a/.gometalinter.json +++ b/.gometalinter.json @@ -2,7 +2,6 @@ "Enable": [ "deadcode", "errcheck", - "gas", "goconst", "goimports", "golint", diff --git a/.taskcluster.yml b/.taskcluster.yml index 911ae2c..c5af173 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -24,7 +24,7 @@ tasks: - "-lc" - "git clone {{event.head.repo.url}} repo && cd repo && git checkout {{event.head.sha}} && yarn install && yarn test" metadata: - name: "taskcluster-lib-urls test" + name: "taskcluster-lib-urls node.js test" description: "Library for building taskcluster urls" owner: "{{ event.head.user.email }}" source: "{{ event.head.repo.url }}" @@ -87,7 +87,7 @@ tasks: gometalinter --install && gometalinter metadata: - name: taskcluster-lib-urls go test + name: "taskcluster-lib-urls go test" description: Run library test suite - golang 1.10 owner: '{{ event.head.user.email }}' source: '{{ event.head.repo.url }}' @@ -114,7 +114,7 @@ tasks: pip install tox && tox -e py27 metadata: - name: taskcluster-lib-urls python 2.7 test + name: "taskcluster-lib-urls python 2.7 test" description: Run library test suite - python2.7 owner: '{{ event.head.user.email }}' source: '{{ event.head.repo.url }}' diff --git a/README.md b/README.md index 4a2479c..1bc6c24 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ root URL: * `exchangeReference(rootUrl, service, version)` -> `String` * `schema(rootUrl, service, schema)` -> `String` * `ui(rootUrl, path)` -> `String` +* `servicesManifest(rootUrl)` -> `String` * `testRootUrl()` -> `String` * `withRootUrl(rootUrl)` -> `Class` instance for above methods @@ -50,6 +51,7 @@ libUrls.schema(rootUrl, 'auth', 'v1/foo.yml'); // Note that schema names have ve libUrls.apiReference(rootUrl, 'auth', 'v1'); libUrls.exchangeReference(rootUrl, 'auth', 'v1'); libUrls.ui(rootUrl, 'foo/bar'); +libUrls.servicesManifest(rootUrl); libUrls.docs(rootUrl, 'foo/bar'); ``` @@ -64,6 +66,7 @@ urls.schema('auth', 'v1/foo.yml'); urls.apiReference('auth', 'v1'); urls.exchangeReference('auth', 'v1'); urls.ui('foo/bar'); +urls.servicesManifest(); urls.docs('foo/bar'); ``` @@ -90,6 +93,7 @@ func Docs(rootURL string, path string) string func ExchangeReference(rootURL string, service string, version string) string func Schema(rootURL string, service string, name string) string func UI(rootURL string, path string) string +func ServicesManifest(rootURL string) string ``` Python Usage @@ -105,6 +109,7 @@ taskcluster_urls.schema(root_url, 'auth', 'v1/foo.yml') # Note that schema names taskcluster_urls.api_reference(root_url, 'auth', 'v1') taskcluster_urls.exchange_reference(root_url, 'auth', 'v1') taskcluster_urls.ui(root_url, 'foo/bar') +taskcluster_urls.servicesManifest(root_url) taskcluster_urls.docs(root_url, 'foo/bar') ``` diff --git a/docs/urls-spec.md b/docs/urls-spec.md index 73e7dac..69bf795 100644 --- a/docs/urls-spec.md +++ b/docs/urls-spec.md @@ -25,6 +25,7 @@ Taskcluster uses URLs with the following pattern: | exchangeReference(rootUrl, service, version) | `/references///exchanges.json` | | schema(rootUrl, service, schema) | `/schemas//` | | ui(rootUrl, path) | `/` | +| servicesManifest(rootUrl) | `/references/manifest.json` | *NOTE*: you should *always* use this library to generate URLs, rather than hard-coding any of the above patterns. diff --git a/specification.yml b/specification.yml index 8f67cd3..d4b5de8 100644 --- a/specification.yml +++ b/specification.yml @@ -64,3 +64,8 @@ specs: argSets: - [''] - [/] +- type: servicesManifest + expectedUrl: https://taskcluster.example.com/references/manifest.json + oldExpectedUrl: https://references.taskcluster.net/manifest.json + argSets: + - [] diff --git a/src/index.js b/src/index.js index 24f7ab0..2335223 100644 --- a/src/index.js +++ b/src/index.js @@ -50,6 +50,13 @@ class LegacyUrls { ui(path) { return `https://tools.taskcluster.net/${cleanPath(path)}`; } + + /** + * Returns a URL for the service manifest of a taskcluster deployment. + */ + servicesManifest() { + return 'https://references.taskcluster.net/manifest.json'; + } } class Urls { @@ -100,6 +107,13 @@ class Urls { ui(path) { return `${this.rootUrl}/${cleanPath(path)}`; } + + /** + * Returns a URL for the service manifest of a taskcluster deployment. + */ + servicesManifest() { + return `${this.rootUrl}/references/manifest.json`; + } } const withRootUrl = rootUrl => @@ -168,6 +182,13 @@ module.exports = { return withRootUrl(rootUrl).ui(path); }, + /** + * Returns a URL for the service manifest of a taskcluster deployment. + */ + servicesManifest(rootUrl) { + return withRootUrl(rootUrl).servicesManifest(); + }, + /** * Return the standardized taskcluster "testing" rootUrl. * Useful for nock and such things. diff --git a/taskcluster_urls/__init__.py b/taskcluster_urls/__init__.py index e79d385..ec4275f 100644 --- a/taskcluster_urls/__init__.py +++ b/taskcluster_urls/__init__.py @@ -51,3 +51,11 @@ def ui(root_url, path): return 'https://tools.taskcluster.net/{}'.format(path) else: return '{}/{}'.format(root_url, path) + +def services_manifest(root_url): + """Returns a URL for the service manifest of a taskcluster deployment.""" + root_url = root_url.rstrip('/') + if root_url == OLD_ROOT_URL: + return 'https://references.taskcluster.net/manifest.json' + else: + return '{}/references/manifest.json'.format(root_url) diff --git a/tcurls.go b/tcurls.go index 1c4bab4..6fecce9 100644 --- a/tcurls.go +++ b/tcurls.go @@ -70,3 +70,13 @@ func UI(rootURL string, path string) string { return fmt.Sprintf("%s/%s", r, path) } } + +// ServicesManifest returns a URL for the service manifest of a taskcluster deployment +func ServicesManifest(rootURL string) string { + switch r := strings.TrimRight(rootURL, "/"); r { + case oldRootURL: + return "https://references.taskcluster.net/manifest.json" + default: + return fmt.Sprintf("%s/references/manifest.json", r) + } +} diff --git a/tcurls_test.go b/tcurls_test.go index 94d98f1..8c3aa7e 100644 --- a/tcurls_test.go +++ b/tcurls_test.go @@ -3,6 +3,7 @@ package tcurls import ( "fmt" "io/ioutil" + "strings" "testing" "gopkg.in/yaml.v2" @@ -35,12 +36,14 @@ func testFunc(t *testing.T, functionType string, root string, args ...string) (s return Schema(root, args[0], args[1]), nil case "ui": return UI(root, args[0]), nil + case "servicesManifest": + return ServicesManifest(root), nil default: return "", fmt.Errorf("Unknown function type: %s", functionType) } } -func TestUrls(t *testing.T) { +func TestURLs(t *testing.T) { data, err := ioutil.ReadFile("specification.yml") if err != nil { t.Error(err) @@ -52,15 +55,16 @@ func TestUrls(t *testing.T) { } for _, test := range specs.Specs { - // First test "new" urls for _, argSet := range test.ArgSets { + + // Test "new" URLs result, err := testFunc(t, test.FunctionType, rootURL, argSet...) if err != nil { t.Error(err) continue } if result != test.ExpectedURL { - t.Errorf("Url is not correct. Got %s wanted %s", result, test.ExpectedURL) + t.Errorf("URL is not correct. Got %q wanted %q", result, test.ExpectedURL) continue } result, err = testFunc(t, test.FunctionType, fmt.Sprintf("%s/", rootURL), argSet...) @@ -68,25 +72,39 @@ func TestUrls(t *testing.T) { t.Error(err) } if result != test.ExpectedURL { - t.Errorf("Url is not correct. Got %s wanted %s", result, test.ExpectedURL) + t.Errorf("URL is not correct. Got %q wanted %q", result, test.ExpectedURL) continue } + t.Logf(`%v %v(%v) = %q`, greenTick(), test.FunctionType, quotedList(rootURL, argSet), result) - // Now the old ones + // Test "old" URLs result, err = testFunc(t, test.FunctionType, oldRootURL, argSet...) if err != nil { t.Error(err) } if result != test.OldExpectedURL { - t.Errorf("Url is not correct. Got %s wanted %s", result, test.OldExpectedURL) + t.Errorf("URL is not correct. Got %q wanted %q", result, test.OldExpectedURL) } result, err = testFunc(t, test.FunctionType, fmt.Sprintf("%s/", oldRootURL), argSet...) if err != nil { t.Error(err) } if result != test.OldExpectedURL { - t.Errorf("Url is not correct. Got %s wanted %s", result, test.OldExpectedURL) + t.Errorf("URL is not correct. Got %q wanted %q", result, test.OldExpectedURL) } + t.Logf(`%v %v(%v) = %q`, greenTick(), test.FunctionType, quotedList(oldRootURL, argSet), result) } } } + +// quotedList returns a quoted list of the arguments passed in +func quotedList(url string, args []string) string { + all := append([]string{url}, args...) + return `'` + strings.Join(all, `', '`) + `'` +} + +// greenTick returns an ANSI string including escape codes to render a light +// green tick (✓) in a color console +func greenTick() string { + return string([]byte{0x1b, 0x5b, 0x33, 0x32, 0x6d, 0xe2, 0x9c, 0x93, 0x1b, 0x5b, 0x30, 0x6d}) +}