dicker
Version:
Dicker - Trivial docker build system
322 lines (296 loc) • 10.5 kB
JavaScript
/* @flow */
import { DEFAULT_DOMAIN, DEFAULT_REFERENCE, LEGACY_DEFAULT_DOMAIN, LOCALHOST } from '../constants';
import { anchoredDigestRegexp, anchoredIdentifierRegexp } from './regexp';
export type ReferenceType = {
// Repo
domain?: string,
repository?: string,
path?: string,
tag?: string,
digest?: string,
digestAlgorithm?: string,
}
export type DigestType = { digest: ?string, digestAlgorithm: ?string };
// NameTotalLengthMax is the maximum total number of characters in a repository name.
const NameTotalLengthMax = 255;
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
// eslint-disable-next-line
const ErrReferenceInvalidFormat = 'invalid reference format';
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
// const ErrTagInvalidFormat = new Error('invalid tag format');
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
// const ErrDigestInvalidFormat = new Error('invalid digest format');
// ErrNameContainsUppercase is returned for invalid repository names
// that contain uppercase characters.
// eslint-disable-next-line
const ErrNameContainsUppercase = 'repository name must be lowercase';
// ErrNameEmpty is returned for empty, invalid repository names.
// eslint-disable-next-line
const ErrNameEmpty = 'repository name must have at least one component';
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
// eslint-disable-next-line
const ErrNameTooLong = `repository name must not be more than ${NameTotalLengthMax} characters`;
// ErrNameNotCanonical is returned when a name is not canonical.
// const ErrNameNotCanonical = 'repository name must be canonical';
// splitDockerDomain splits a repository name to domain and remotename string.
// If no valid domain is found, the default domain is used. Repository name
// needs to be already validated before.
const splitDockerDomain = (str: string): [string, string, string] => {
if (
str.match(new RegExp(anchoredDigestRegexp)) || str.match(new RegExp(anchoredIdentifierRegexp))
) {
return ['', '', str];
}
const repoAndPathSep = str.indexOf('/');
let domain = (repoAndPathSep === -1) ? '' : str.substring(0, repoAndPathSep);
const domainOK = (domain && (domain.match(/[.:]/) || (domain === LOCALHOST)));
const domainIsDefault = (domain === LEGACY_DEFAULT_DOMAIN) || (domain === DEFAULT_DOMAIN);
domain = ((!domainOK) || domainIsDefault) ? '' : domain;
const repoAndPath = domainOK ? str.substr(repoAndPathSep + 1) : str;
const pathSep = repoAndPath.indexOf('/');
let repo = '';
let remainder = '';
if (pathSep !== -1) {
repo = repoAndPath.substring(0, pathSep);
remainder = repoAndPath.substring(pathSep + 1);
} else {
repo = '';
remainder = repoAndPath;
}
return [domain, repo === DEFAULT_REFERENCE.repository ? '' : repo, remainder];
};
// const getBestReferenceType = (ref: ReferenceType): ReferenceType => {
// if (!ref.name) {
// // Allow digest only references
// if (ref.digest !== '') {
// return {
// ...ref,
// ...(ref.namedRepository),
// digest: ref.digest,
// name: ref.name,
// tag: ref.tag,
// namedRepository: ref.namedRepository,
// };
// }
// throw new Error('No matching reference type');
// }
// if (!ref.tag) {
// if (ref.digest) {
// return {
// ...ref,
// ...(ref.namedRepository),
// namedRepository: ref.namedRepository,
// digest: ref.digest,
// };
// }
// return { namedRepository: ref.namedRepository };
// }
// if (ref.digest) {
// return {
// ...ref,
// ...(ref.namedRepository),
// namedRepository: ref.namedRepository,
// tag: ref.tag,
// };
// }
// return ref;
// };
//
// export const Parse = (s: string): ReferenceType => {
// const matchReference = s.match(new RegExp(ReferenceRegexp));
// if (matchReference === null) {
// if (!s) {
// throw new Error(ErrNameEmpty);
// }
// if (s.toLowerCase() !== s) {
// throw new Error(ErrNameContainsUppercase);
// }
// throw new Error(ErrReferenceInvalidFormat);
// }
//
// const matches = new Array(matchReference || [])[0];
// const head = matches[0] || '';
// const middle = matches[1] || '';
// const tail = matches[2] || '';
// if (head.length > NameTotalLengthMax) {
// throw new Error(ErrNameTooLong);
// }
//
// console.log('head, middle, tail', [head, middle, tail]);
// const repo = {
// path: null,
// domain: null,
// digest: null,
// tag: null,
// };
// const headIsDigest = head.match(new RegExp(anchoredDigestRegexp));
// const middleIsDigest = middle.match(new RegExp(anchoredDigestRegexp));
// const middleIsName = new RegExp(anchoredNameRegexp).exec(middle);
//
// if (headIsDigest) {
// repo.digest = head;
// } else {
// if (middleIsDigest) {
// repo.path = middleIsName ? head : null;
// repo.tag = middleIsName ? null : head;
// repo.digest = middle;
// } else {
// repo.path = head;
// repo.tag = middle;
// repo.digest = tail;
// }
// }
// return repo;
// };
// familiarizeName returns a shortened version of the name familiar
// to to the Docker UI. Familiar names have the default domain
// "docker.io" and "library/" repository prefix removed.
// For example, "docker.io/library/redis" will have the familiar
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
// Returns a familiarized named only reference.
// const familiarizeName = (repo: RepositoryType): RepositoryType => {
// if (repo.domain === DEFAULT_DOMAIN) {
// repo.domain = '';
// // Handle official repositories which have the pattern "library/<official repo name>"
// const split = repo.path.split(PATH_SEPARATOR);
// if ((split.length === 2) && (split[0] === OFFICIAL_REPO_NAME)) {
// repo.path = split[1];
// }
// }
// return repo;
// };
/**
* Remove object empty attributes or replace them with provided defaults
* @param obj {Object} = initial object
* @param defaults {Object} - defaults object
*/
const applyDefaults = (obj: Object, defaults: Object = {}): { [string]: string } => {
const res = {};
const keys = Object.keys({ ...obj, ...defaults }).sort();
keys.forEach((field) => {
const val = obj[field] || defaults[field];
if (val) {
res[field] = val;
}
});
return res;
};
const parseDigest = (digestStr: ?string): DigestType => {
let digest = null;
let digestAlgorithm = null;
if (digestStr) {
if (digestStr.indexOf(':') === -1) {
digestAlgorithm = '';
digest = digestStr;
} else {
[digestAlgorithm, digest] = digestStr.split(':');
}
}
return { digest, digestAlgorithm };
};
// ParseNormalizedNamed parses a string into a named reference
// transforming a familiar name from Docker UI to a fully
// qualified reference. If the value may be an identifier
// use ParseAnyReference.
export const ParseNormalizedNamed = (s: string, ds?: ?Array<string>): ReferenceType => {
const [domain, repository, remainder] = splitDockerDomain(s);
// console.log('[domain, remainder]', [domain, remainder]);
let path: ?string;
let tag: ?string;
let digestStr: ?string;
if (
remainder.match(new RegExp(anchoredDigestRegexp))
|| remainder.match(new RegExp(anchoredIdentifierRegexp))
) {
digestStr = remainder;
} else {
const tagSep = remainder.indexOf(':');
if (tagSep !== -1) {
path = remainder.substring(0, tagSep);
const tagAndDigest = remainder.substring(tagSep + 1);
const digestSep = tagAndDigest.indexOf('@');
if (digestSep !== -1) {
digestStr = tagAndDigest.substring(digestSep + 1);
tag = tagAndDigest.substring(0, digestSep);
} else {
tag = tagAndDigest;
}
} else {
path = remainder;
}
}
let { digest, digestAlgorithm } = parseDigest(digestStr);
if (ds) {
let dsArr = [];
if (typeof ds === 'string') {
dsArr = [ds];
} else if (Array.isArray(ds)) {
dsArr = ds;
}
if (dsArr.length > 0) {
const filteredDs: Array<?DigestType> = dsArr.map(
(dsStr): ?DigestType => {
const parsedDs: DigestType = parseDigest(dsStr);
if ((digest === parsedDs.digest) && (
(digestAlgorithm || DEFAULT_REFERENCE.digestAlgorithm)
=== (parsedDs.digestAlgorithm || DEFAULT_REFERENCE.digestAlgorithm)
)) {
return parsedDs;
}
return null;
},
).filter(x => !!x);
if ((filteredDs.length === 1) && (filteredDs[0])) {
digest = filteredDs[0].digest;
digestAlgorithm = filteredDs[0].digestAlgorithm;
}
}
}
if (path && (path.toLowerCase() !== path)) {
throw new Error('invalid reference format: repository name must be lowercase');
}
const ref = { domain, tag, digest, digestAlgorithm, repository, path };
if (Object.keys(ref) === 0) {
throw new Error(`reference ${JSON.stringify(ref)} has no name`);
}
const defaulted = applyDefaults(ref, DEFAULT_REFERENCE);
const remoteName = ([defaulted.repository, defaulted.path].filter(x => !!x).join('/'));
if (remoteName.match(anchoredIdentifierRegexp)) {
throw new Error(`invalid repository name ${s}, cannot specify 64-byte hexadecimal strings`);
}
return {
remoteName,
...defaulted,
};
};
export const normalizedNameToString = (nn: Object): string => {
const {
domain,
repository,
path,
digest,
digestAlgorithm,
tag,
} = applyDefaults(nn, DEFAULT_REFERENCE);
const digestStr = digest ? [digestAlgorithm, digest].filter(x => !!x).join(':') : '';
// console.log('cond', digest,
// tag,
// repository,
// domain,
// (tag === DEFAULT_REFERENCE.tag),
// (repository === DEFAULT_REFERENCE.repository),
// (domain === DEFAULT_REFERENCE.domain));
if (digest
&& (tag === DEFAULT_REFERENCE.tag)
&& (repository === DEFAULT_REFERENCE.repository)
&& (!path)
&& (domain === DEFAULT_REFERENCE.domain)
) {
return digestStr;
}
if (digest && (!path)) {
return `${domain}/${repository}/${digest}`;
}
const suffixStr = `:${[tag, digestStr].filter(x => !!x).join('@')}`;
return `${domain}/${repository}/${path}${suffixStr}`;
};