188 lines
6.8 KiB
JavaScript
188 lines
6.8 KiB
JavaScript
/*
|
|
* Copyright (C) 2016-2023 Jones Magloire @Joxit
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
import { Http } from './http.js';
|
|
import { eventTransfer, ERROR_CAN_NOT_READ_CONTENT_DIGEST } from './utils.js';
|
|
import observable from '@riotjs/observable';
|
|
|
|
export const supportListManifest = (response) => {
|
|
if (response.mediaType === 'application/vnd.docker.distribution.manifest.list.v2+json') {
|
|
return true;
|
|
}
|
|
if (response.mediaType === 'application/vnd.oci.image.index.v1+json' && Array.isArray(response.manifests)) {
|
|
return !response.manifests.some(({ mediaType }) => mediaType !== 'application/vnd.oci.image.manifest.v1+json');
|
|
}
|
|
return false;
|
|
};
|
|
|
|
export const filterWrongManifests = (response) => {
|
|
return response.manifests.filter(
|
|
({ annotations }) => !annotations || annotations['vnd.docker.reference.type'] !== 'attestation-manifest'
|
|
);
|
|
};
|
|
|
|
export const platformToString = (platform) => {
|
|
if (!platform || !platform.architecture) {
|
|
return 'unknown';
|
|
}
|
|
return platform.architecture + (platform.variant ? platform.variant : '');
|
|
};
|
|
|
|
export class DockerImage {
|
|
constructor(name, tag, { list, registryUrl, onNotify, onAuthentication, useControlCacheHeader }) {
|
|
this.name = name;
|
|
this.tag = tag;
|
|
this.chars = 0;
|
|
this.opts = {
|
|
list,
|
|
registryUrl,
|
|
onNotify,
|
|
onAuthentication,
|
|
useControlCacheHeader,
|
|
};
|
|
this.ociImage = false;
|
|
observable(this);
|
|
this.on('get-size', function () {
|
|
if (this.size !== undefined) {
|
|
return this.trigger('size', this.size);
|
|
}
|
|
return this.fillInfo();
|
|
});
|
|
this.on('get-sha256', function () {
|
|
if (this.sha256 !== undefined) {
|
|
return this.trigger('sha256', this.sha256);
|
|
}
|
|
return this.fillInfo();
|
|
});
|
|
this.on('get-date', function () {
|
|
if (this.creationDate !== undefined) {
|
|
return this.trigger('creation-date', this.creationDate);
|
|
}
|
|
return this.fillInfo();
|
|
});
|
|
this.on('content-digest-chars', function (chars) {
|
|
this.chars = chars;
|
|
});
|
|
this.on('get-content-digest-chars', function () {
|
|
return this.trigger('content-digest-chars', this.chars);
|
|
});
|
|
this.on('get-content-digest', function () {
|
|
if (this.contentDigest !== undefined) {
|
|
return this.trigger('content-digest', this.contentDigest);
|
|
}
|
|
return this.fillInfo();
|
|
});
|
|
}
|
|
fillInfo() {
|
|
if (this._fillInfoWaiting) {
|
|
return;
|
|
}
|
|
this._fillInfoWaiting = true;
|
|
const oReq = new Http({ onAuthentication: this.opts.onAuthentication });
|
|
const self = this;
|
|
oReq.addEventListener('loadend', function () {
|
|
if (this.status === 200 || this.status === 202) {
|
|
const response = JSON.parse(this.responseText);
|
|
if (supportListManifest(response) && self.opts.list) {
|
|
const manifests = filterWrongManifests(response);
|
|
self.trigger('list', manifests);
|
|
self.manifests = manifests;
|
|
const manifest = response.manifests[0];
|
|
const image = new DockerImage(self.name, manifest.digest, { ...self.opts, list: false });
|
|
eventTransfer(image, self);
|
|
image.fillInfo();
|
|
self.variants = [image];
|
|
return;
|
|
}
|
|
self.ociImage = response.mediaType === 'application/vnd.oci.image.index.v1+json';
|
|
self.layers = response.layers || response.manifests;
|
|
self.size = self.layers.reduce(function (acc, e) {
|
|
return acc + e.size;
|
|
}, 0);
|
|
self.sha256 = response.config && response.config.digest;
|
|
self.trigger('size', self.size);
|
|
self.trigger('sha256', self.sha256);
|
|
oReq.getContentDigest(function (contentDigest) {
|
|
self.contentDigest = contentDigest;
|
|
self.trigger('content-digest', contentDigest);
|
|
if (!contentDigest) {
|
|
self.opts.onNotify(ERROR_CAN_NOT_READ_CONTENT_DIGEST);
|
|
}
|
|
});
|
|
if (!self.ociImage) {
|
|
self.getBlobs(self.sha256);
|
|
} else {
|
|
// Force updates
|
|
self.trigger('creation-date');
|
|
self.trigger('blobs');
|
|
self.trigger('oci-image');
|
|
}
|
|
} else if (this.status === 404) {
|
|
self.opts.onNotify(`Manifest for ${self.name}:${self.tag} not found`, true);
|
|
} else {
|
|
self.opts.onNotify(this.responseText);
|
|
}
|
|
});
|
|
oReq.open('GET', `${this.opts.registryUrl}/v2/${self.name}/manifests/${self.tag}`);
|
|
oReq.setRequestHeader(
|
|
'Accept',
|
|
'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json' +
|
|
(self.opts.list ? ', application/vnd.docker.distribution.manifest.list.v2+json' : '')
|
|
);
|
|
if (self.opts.useControlCacheHeader) {
|
|
oReq.setRequestHeader('Cache-Control', 'no-store, no-cache');
|
|
}
|
|
oReq.send();
|
|
}
|
|
getBlobs(blob) {
|
|
const oReq = new Http({ onAuthentication: this.opts.onAuthentication });
|
|
const self = this;
|
|
oReq.addEventListener('loadend', function () {
|
|
if (this.status === 200 || this.status === 202) {
|
|
const response = JSON.parse(this.responseText);
|
|
self.creationDate = new Date(response.created);
|
|
self.blobs = response;
|
|
self.blobs.history
|
|
.filter(function (e) {
|
|
return !e.empty_layer;
|
|
})
|
|
.forEach(function (e, i) {
|
|
e.size = self.layers[i].size;
|
|
e.id = self.layers[i].digest.replace('sha256:', '');
|
|
});
|
|
self.blobs.id = blob.replace('sha256:', '');
|
|
self.trigger('creation-date', self.creationDate);
|
|
self.trigger('blobs', self.blobs);
|
|
} else if (this.status === 404) {
|
|
self.opts.onNotify(`Blobs for ${self.name}:${self.tag} not found: blob '${self.blobs}'`, true);
|
|
} else if (!this.responseText) {
|
|
self.opts.onNotify(
|
|
`Can"t get blobs for ${self.name}:${self.tag}: blob '${self.blobs}' (no message error)`,
|
|
true
|
|
);
|
|
} else {
|
|
self.opts.onNotify(this.responseText);
|
|
}
|
|
});
|
|
oReq.open('GET', `${this.opts.registryUrl}/v2/${self.name}/blobs/${blob}`);
|
|
oReq.setRequestHeader(
|
|
'Accept',
|
|
'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json'
|
|
);
|
|
oReq.send();
|
|
}
|
|
}
|