import axios from 'axios'; import camelcaseKeys from 'camelcase-keys'; import { DateTime } from 'luxon'; import R from 'ramda'; import pino from 'pino'; /** * Union type representing the architecture defined in part of an OCI image's * manifest list. * * As specified in the Docker Manifest spec, any valid GOARCH values are valid * image architecture values, and vice versa: * > The platform object describes the platform which the image in the manifest * > runs on. A full list of valid operating system and architecture values are * > listed in the Go language documentation for $GOOS and $GOARCH * @see https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list-field-descriptions */ var Architecture; (function (Architecture) { Architecture["i386"] = "386"; Architecture["amd64"] = "amd64"; Architecture["arm"] = "arm"; Architecture["arm64"] = "arm64"; Architecture["mips"] = "mips"; Architecture["mips64"] = "mips64"; Architecture["mips64le"] = "mips64le"; Architecture["mipsle"] = "mipsle"; Architecture["ppc64"] = "ppc64"; Architecture["ppc64le"] = "ppc64le"; Architecture["s390x"] = "s390x"; Architecture["wasm"] = "wasm"; })(Architecture || (Architecture = {})); /** * Union type representing the OS defined in part of an OCI image's * manifest list. * See the docs for the `Architecture` type above for more info. */ var OS; (function (OS) { OS["aix"] = "aix"; OS["android"] = "android"; OS["darwin"] = "darwin"; OS["dragonfly"] = "dragonfly"; OS["freebsd"] = "freebsd"; OS["illumos"] = "illumos"; OS["js"] = "js"; OS["linux"] = "linux"; OS["netbsd"] = "netbsd"; OS["openbsd"] = "openbsd"; OS["plan9"] = "plan9"; OS["solaris"] = "solaris"; OS["windows"] = "windows"; })(OS || (OS = {})); var ManifestMediaType; (function (ManifestMediaType) { ManifestMediaType["Manifest"] = "application/vnd.docker.distribution.manifest.v2+json"; ManifestMediaType["ManifestList"] = "application/vnd.docker.distribution.manifest.list.v2+json"; })(ManifestMediaType || (ManifestMediaType = {})); var log = /*#__PURE__*/ pino({ base: null, useLevelLabels: true }); var DOCKER_HUB_API_ROOT = 'https://hub.docker.com/v2/'; var DOCKER_HUB_API_AUTH_URL = 'https://auth.docker.io/token'; /** * Currently only supports fetching the manifest for the `latest` tag; in * reality, we can pass any valid content digest[1] to retrieve the manifest(s) * for that image. * * [1]: https://github.com/opencontainers/distribution-spec/blob/master/spec.md#content-digests */ var createManifestListURL = function createManifestListURL(_ref) { var repo = _ref.repo; return "https://registry-1.docker.io/v2/" + repo.user + "/" + repo.name + "/manifests/latest"; }; var createUserReposListURL = function createUserReposListURL(user) { return DOCKER_HUB_API_ROOT + "repositories/" + user; }; /** * The OCI distribution spec requires a unique token for each repo manifest queried. */ var fetchDockerHubToken = function fetchDockerHubToken(repo) { try { var name = repo.name, user = repo.user; return Promise.resolve(axios.get(DOCKER_HUB_API_AUTH_URL, { params: { scope: "repository:" + user + "/" + name + ":pull", service: 'registry.docker.io' } })).then(function (tokenRequest) { var token = R.path(['data', 'token'], tokenRequest); if (!token) { throw new Error('Unable to retrieve auth token from registry.'); } return token; }); } catch (e) { return Promise.reject(e); } }; /** * Pure function that massages the Docker Hub API response into the * format we want to return. e.g., only extracting certain fields; * converting snake_case to camelCase, etc. */ var extractRepositoryDetails = function extractRepositoryDetails(repos, lastUpdatedSince) { if (!repos || R.isEmpty(repos)) { return []; } var parsedRepos = camelcaseKeys(repos); if (R.isNil(lastUpdatedSince)) { return parsedRepos; } return parsedRepos.filter(function (repo) { return DateTime.fromISO(repo.lastUpdated) < lastUpdatedSince; }); }; /** * Query a single repository given a repo name and username. * * @param user The DockerHub username or org name to query for. * @param name The DockerHub repo name -- restrict to this single repo. */ var queryRepo = function queryRepo(_ref2) { var name = _ref2.name, user = _ref2.user; try { return Promise.resolve(axios.request({ url: DOCKER_HUB_API_ROOT + "repositories/" + user + "/" + name + "/" })).then(function (repoResult) { var repo = R.prop('data', repoResult); if (repoResult.status !== 200 || !repo || R.isEmpty(repo)) { return; } return camelcaseKeys(repo); }); } catch (e) { return Promise.reject(e); } }; /** * Top-level function for querying repositories. * * @TODO Rename to just `queryRepos`. * * @param user The DockerHub username or org name to query for. * @param numRepos The number of repos to query (max 100). * @param lastUpdatedSince Filter by the DateTime at which a repo was last updated. */ var queryTopRepos = function queryTopRepos(_ref3) { var lastUpdatedSince = _ref3.lastUpdatedSince, _ref3$numRepos = _ref3.numRepos, numRepos = _ref3$numRepos === void 0 ? 100 : _ref3$numRepos, user = _ref3.user; try { if (numRepos > 100) { throw new RangeError('Number of repos to query cannot exceed 100.'); } var listReposURL = createUserReposListURL(user); return Promise.resolve(axios.get(listReposURL, { params: { page: 1, page_size: numRepos } })).then(function (repoResults) { var repos = R.path(['data', 'results'], repoResults); return extractRepositoryDetails(repos, lastUpdatedSince); }); } catch (e) { return Promise.reject(e); } }; /** * Query image tags. */ var queryTags = function queryTags(repo) { try { var repoUrl = createUserReposListURL(repo.user); var tagsUrl = repoUrl + "/" + repo.name + "/tags?page_size=100"; return Promise.resolve(axios.get(tagsUrl)).then(function (tagsResults) { var tags = R.path(['data', 'results'], tagsResults); if (!tags || R.isEmpty(tags)) { return; } // @ts-ignore return camelcaseKeys(tags); }); } catch (e) { return Promise.reject(e); } }; /** * Queries the Docker Hub API to retrieve a "fat manifest", an object of * `Content-Type` `application/vnd.docker.distribution.manifest.list.v2+json/`. * Read up on the Manifest v2, Schema 2 Spec in more detail: * @see https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md * Or the shiny new OCI distribution spec which builds on it: * @see https://github.com/opencontainers/distribution-spec/blob/f67bc11ba3a083a9c62f8fa53ad14c5bcf2116af/spec.md */ var fetchManifestList = function fetchManifestList(repo) { try { // Docker Hub requires a unique token for each repo manifest queried. return Promise.resolve(fetchDockerHubToken(repo)).then(function (token) { var manifestListURL = createManifestListURL({ repo: repo }); return Promise.resolve(axios.get(manifestListURL, { headers: { Accept: 'application/vnd.docker.distribution.manifest.list.v2+json', Authorization: "Bearer " + token } })).then(function (manifestListResponse) { // For now, just ignore legacy V1 schema manifests. They have an entirely // different response shape and it's not worth mucking up the schema to // support a legacy format. if (manifestListResponse.data.schemaVersion === 1) { log.info('Schema version 1 is unsupported.', repo.name); return; } return R.path(['data'], manifestListResponse); }); }); } catch (e) { return Promise.reject(e); } }; export { Architecture, DOCKER_HUB_API_AUTH_URL, DOCKER_HUB_API_ROOT, ManifestMediaType, extractRepositoryDetails, fetchDockerHubToken, fetchManifestList, queryRepo, queryTags, queryTopRepos }; //# sourceMappingURL=docker-hub-utils.esm.js.map