blob: ad5b8bea54e000752c681a9bfd900b25a4158544 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <vector>
#include <stout/protobuf.hpp>
#include <mesos/oci/spec.hpp>
using std::string;
using std::vector;
namespace oci {
namespace spec {
namespace image {
namespace v1 {
namespace internal {
Option<Error> validateDigest(const string& digest)
{
vector<string> split = strings::split(digest, ":");
if (split.size() != 2) {
return Error("Incorrect 'digest' format: " + digest);
}
// TODO(qianzhang): Validate algorithm (split[0]) and hex (split[1]).
return None();
}
Option<Error> validate(const Index& index)
{
if (index.schemaversion() != 2) {
return Error(
"Incorrect 'schemaVersion': " +
stringify(index.schemaversion()));
}
foreach (const ManifestDescriptor& manifest, index.manifests()) {
Option<Error> error = validateDigest(manifest.digest());
if (error.isSome()) {
return Error(
"Failed to validate 'digest' of the 'manifest': " + error->message);
}
}
return None();
}
Option<Error> validate(const Manifest& manifest)
{
if (manifest.schemaversion() != 2) {
return Error(
"Incorrect 'schemaVersion': " +
stringify(manifest.schemaversion()));
}
const Descriptor& config = manifest.config();
Option<Error> error = validateDigest(config.digest());
if (error.isSome()) {
return Error(
"Failed to validate 'digest' of the 'config': " + error->message);
}
if (config.mediatype() != MEDIA_TYPE_CONFIG) {
return Error(
"Incorrect 'mediaType' of the 'config': " + config.mediatype());
}
if (manifest.layers_size() <= 0) {
return Error("'layers' field size must be at least one");
}
foreach (const Descriptor& layer, manifest.layers()) {
Option<Error> error = validateDigest(layer.digest());
if (error.isSome()) {
return Error(
"Failed to validate 'digest' of the 'layer': " + error->message);
}
if (layer.mediatype() != MEDIA_TYPE_LAYER &&
layer.mediatype() != MEDIA_TYPE_LAYER_GZIP &&
layer.mediatype() != MEDIA_TYPE_NONDIST_LAYER &&
layer.mediatype() != MEDIA_TYPE_NONDIST_LAYER_GZIP) {
return Error(
"Incorrect 'mediaType' of the 'layer': " + layer.mediatype());
}
}
return None();
}
Option<Error> validate(const Configuration& configuration)
{
if (configuration.rootfs().type() != ROOTFS_TYPE) {
return Error("Incorrect 'type': " + configuration.rootfs().type());
}
return None();
}
} // namespace internal {
template <>
Try<Descriptor> parse(const string& s)
{
Try<JSON::Object> json = JSON::parse<JSON::Object>(s);
if (json.isError()) {
return Error("JSON parse failed: " + json.error());
}
Try<Descriptor> descriptor = protobuf::parse<Descriptor>(json.get());
if (descriptor.isError()) {
return Error("Protobuf parse failed: " + descriptor.error());
}
// Manually parse 'annotations' field.
Result<JSON::Value> annotations = json->find<JSON::Value>("annotations");
if (annotations.isError()) {
return Error(
"Failed to find 'annotations': " + annotations.error());
}
if (annotations.isSome() && !annotations->is<JSON::Null>()) {
foreachpair (const string& key,
const JSON::Value& value,
annotations->as<JSON::Object>().values) {
if (!value.is<JSON::String>()) {
return Error(
"The value of annotation key '" + key + "' is not a JSON string");
}
Label* annotation = descriptor->add_annotations();
annotation->set_key(key);
annotation->set_value(value.as<JSON::String>().value);
}
}
Option<Error> error = internal::validateDigest(descriptor->digest());
if (error.isSome()) {
return Error(
"OCI v1 image descriptor validation failed: " + error->message);
}
return descriptor.get();
}
template <>
Try<Index> parse(const string& s)
{
Try<JSON::Object> json = JSON::parse<JSON::Object>(s);
if (json.isError()) {
return Error("JSON parse failed: " + json.error());
}
Try<Index> index = protobuf::parse<Index>(json.get());
if (index.isError()) {
return Error("Protobuf parse failed: " + index.error());
}
// Manually parse 'manifest.annotations', 'manifest.platform.os.version'
// and 'manifest.platform.os.features'.
Result<JSON::Array> manifests = json->at<JSON::Array>("manifests");
if (manifests.isError()) {
return Error("Failed to find 'manifests': " + manifests.error());
} else if (manifests.isNone()) {
return Error("Unable to find 'manifests'");
}
foreach (const JSON::Value& value, manifests->values) {
if (!value.is<JSON::Object>()) {
return Error("Expecting 'manifest' to be JSON object type");
}
const JSON::Object& manifest = value.as<JSON::Object>();
Result<JSON::String> digest = manifest.at<JSON::String>("digest");
if (digest.isError()) {
return Error("Failed to find 'digest': " + digest.error());
} else if (digest.isNone()) {
return Error("Unable to find 'digest'");
}
ManifestDescriptor* _manifest = nullptr;
for (int i = 0; i < index->manifests_size(); i++) {
if (index->manifests(i).digest() == digest.get()) {
_manifest = index->mutable_manifests(i);
break;
}
}
if (_manifest == nullptr) {
return Error(
"Unable to find the manifest whose digest is '" +
digest->value + "'");
}
Result<JSON::Value> annotations = manifest.find<JSON::Value>("annotations");
if (annotations.isError()) {
return Error(
"Failed to find 'annotations': " + annotations.error());
}
if (annotations.isSome() && !annotations->is<JSON::Null>()) {
foreachpair (const string& key,
const JSON::Value& value,
annotations->as<JSON::Object>().values) {
if (!value.is<JSON::String>()) {
return Error(
"The value of annotation key '" + key + "' is not a JSON string");
}
Label* annotation = _manifest->add_annotations();
annotation->set_key(key);
annotation->set_value(value.as<JSON::String>().value);
}
}
Result<JSON::Object> platform = manifest.at<JSON::Object>("platform");
if (platform.isError()) {
return Error("Failed to find 'platform': " + platform.error());
}
if (platform.isSome()) {
Result<JSON::String> osVersion = platform->at<JSON::String>("os.version");
if (osVersion.isError()) {
return Error(
"Failed to find 'platform.os.version': " + osVersion.error());
}
if (osVersion.isSome()) {
Platform* platform = _manifest->mutable_platform();
platform->set_os_version(osVersion->value);
}
Result<JSON::Array> osFeatures = platform->at<JSON::Array>("os.features");
if (osFeatures.isError()) {
return Error(
"Failed to find 'platform.os.features': " + osFeatures.error());
}
if (osFeatures.isSome()) {
const vector<JSON::Value>& values = osFeatures->values;
if (values.size() != 0) {
Platform* platform = _manifest->mutable_platform();
foreach (const JSON::Value& value, values) {
if (!value.is<JSON::String>()) {
return Error("Expecting OS feature to be string type");
}
platform->add_os_features(value.as<JSON::String>().value);
}
}
}
}
}
// Manually parse 'annotations' field.
Result<JSON::Value> annotations = json->find<JSON::Value>("annotations");
if (annotations.isError()) {
return Error(
"Failed to find 'annotations': " + annotations.error());
}
if (annotations.isSome() && !annotations->is<JSON::Null>()) {
foreachpair (const string& key,
const JSON::Value& value,
annotations->as<JSON::Object>().values) {
if (!value.is<JSON::String>()) {
return Error(
"The value of annotation key '" + key + "' is not a JSON string");
}
Label* annotation = index->add_annotations();
annotation->set_key(key);
annotation->set_value(value.as<JSON::String>().value);
}
}
Option<Error> error = internal::validate(index.get());
if (error.isSome()) {
return Error(
"OCI v1 image index validation failed: " + error->message);
}
return index.get();
}
template <>
Try<Manifest> parse(const string& s)
{
Try<JSON::Object> json = JSON::parse<JSON::Object>(s);
if (json.isError()) {
return Error("JSON parse failed: " + json.error());
}
Try<Manifest> manifest = protobuf::parse<Manifest>(json.get());
if (manifest.isError()) {
return Error("Protobuf parse failed: " + manifest.error());
}
// Manually parse 'annotations' field.
Result<JSON::Value> annotations = json->find<JSON::Value>("annotations");
if (annotations.isError()) {
return Error(
"Failed to find 'annotations': " + annotations.error());
}
if (annotations.isSome() && !annotations->is<JSON::Null>()) {
foreachpair (const string& key,
const JSON::Value& value,
annotations->as<JSON::Object>().values) {
if (!value.is<JSON::String>()) {
return Error(
"The value of annotation key '" + key + "' is not a JSON string");
}
Label* annotation = manifest->add_annotations();
annotation->set_key(key);
annotation->set_value(value.as<JSON::String>().value);
}
}
Option<Error> error = internal::validate(manifest.get());
if (error.isSome()) {
return Error(
"OCI v1 image manifest validation failed: " + error->message);
}
return manifest.get();
}
template <>
Try<Configuration> parse(const string& s)
{
Try<JSON::Object> json = JSON::parse<JSON::Object>(s);
if (json.isError()) {
return Error("JSON parse failed: " + json.error());
}
Try<Configuration> configuration =
protobuf::parse<Configuration>(json.get());
if (configuration.isError()) {
return Error("Protobuf parse failed: " + configuration.error());
}
Result<JSON::Object> config = json->find<JSON::Object>("config");
if (config.isError()) {
return Error("Failed to find 'config': " + config.error());
}
if (config.isSome()) {
// Manually parse 'ExposedPorts' field.
Result<JSON::Value> value = config->find<JSON::Value>("ExposedPorts");
if (value.isError()) {
return Error("Failed to find 'ExposedPorts': " + value.error());
}
if (value.isSome() && !value->is<JSON::Null>()) {
foreachkey (const string& key, value->as<JSON::Object>().values) {
configuration->mutable_config()->add_exposedports(key);
}
}
// Manually parse 'Volumes' field.
value = config->find<JSON::Value>("Volumes");
if (value.isError()) {
return Error("Failed to find 'Volumes': " + value.error());
}
if (value.isSome() && !value->is<JSON::Null>()) {
foreachkey (const string& key, value->as<JSON::Object>().values) {
configuration->mutable_config()->add_volumes(key);
}
}
// Manually parse 'Labels' field.
value = config->find<JSON::Value>("Labels");
if (value.isError()) {
return Error("Failed to find 'Labels': " + value.error());
}
if (value.isSome() && !value->is<JSON::Null>()) {
foreachpair (const string& key,
const JSON::Value& value,
value->as<JSON::Object>().values) {
if (!value.is<JSON::String>()) {
return Error(
"The value of label key '" + key + "' is not a JSON string");
}
Label* label = configuration->mutable_config()->add_labels();
label->set_key(key);
label->set_value(value.as<JSON::String>().value);
}
}
}
Option<Error> error = internal::validate(configuration.get());
if (error.isSome()) {
return Error(
"OCI v1 image configuration validation failed: " + error->message);
}
return configuration.get();
}
} // namespace v1 {
} // namespace image {
} // namespace spec {
} // namespace oci {