Updated OCI spec parsing & validation code with latest OCI image spec. Review: https://reviews.apache.org/r/56852/
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/0c1d50d2 Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/0c1d50d2 Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/0c1d50d2 Branch: refs/heads/master Commit: 0c1d50d236dbc100b9cbab194daba409483eed7a Parents: 87ca2fb Author: Qian Zhang <[email protected]> Authored: Tue Mar 28 16:50:11 2017 +0800 Committer: Qian Zhang <[email protected]> Committed: Tue Mar 28 16:50:11 2017 +0800 ---------------------------------------------------------------------- include/mesos/oci/spec.hpp | 15 +++- src/oci/spec.cpp | 152 +++++++++++++++++++++++++++------------- 2 files changed, 116 insertions(+), 51 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/0c1d50d2/include/mesos/oci/spec.hpp ---------------------------------------------------------------------- diff --git a/include/mesos/oci/spec.hpp b/include/mesos/oci/spec.hpp index ea4f29e..d8eef84 100644 --- a/include/mesos/oci/spec.hpp +++ b/include/mesos/oci/spec.hpp @@ -26,8 +26,8 @@ namespace v1 { // Constant strings for OCI image media types: // https://github.com/opencontainers/image-spec/blob/master/media-types.md -constexpr char MEDIA_TYPE_MANIFEST_LIST[] = - "application/vnd.oci.image.manifest.list.v1+json"; +constexpr char MEDIA_TYPE_INDEX[] = + "application/vnd.oci.image.index.v1+json"; constexpr char MEDIA_TYPE_MANIFEST[] = "application/vnd.oci.image.manifest.v1+json"; @@ -36,13 +36,22 @@ constexpr char MEDIA_TYPE_CONFIG[] = "application/vnd.oci.image.config.v1+json"; constexpr char MEDIA_TYPE_LAYER[] = + "application/vnd.oci.image.layer.v1.tar"; + +constexpr char MEDIA_TYPE_LAYER_GZIP[] = "application/vnd.oci.image.layer.v1.tar+gzip"; constexpr char MEDIA_TYPE_NONDIST_LAYER[] = + "application/vnd.oci.image.layer.nondistributable.v1.tar"; + +constexpr char MEDIA_TYPE_NONDIST_LAYER_GZIP[] = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"; +// Rootfs type of OCI image configuration. +constexpr char ROOTFS_TYPE[] = "layers"; + /** - * Returns the OCI v1 descriptor, image manifest list, image manifest + * Returns the OCI v1 descriptor, image index, image manifest * and image configuration from the given string. */ template <typename T> http://git-wip-us.apache.org/repos/asf/mesos/blob/0c1d50d2/src/oci/spec.cpp ---------------------------------------------------------------------- diff --git a/src/oci/spec.cpp b/src/oci/spec.cpp index 0a88edb..06fceb4 100644 --- a/src/oci/spec.cpp +++ b/src/oci/spec.cpp @@ -43,25 +43,20 @@ Option<Error> validateDigest(const string& digest) } -Option<Error> validate(const ManifestList& manifestList) +Option<Error> validate(const Index& index) { - if (manifestList.schemaversion() != 2) { + if (index.schemaversion() != 2) { return Error( "Incorrect 'schemaVersion': " + - stringify(manifestList.schemaversion())); + stringify(index.schemaversion())); } - foreach (const ManifestDescriptor& manifest, manifestList.manifests()) { + 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); } - - if (manifest.mediatype() != MEDIA_TYPE_MANIFEST) { - return Error( - "Incorrect 'mediaType' of the 'manifest': " + manifest.mediatype()); - } } return None(); @@ -101,7 +96,9 @@ Option<Error> validate(const Manifest& manifest) } if (layer.mediatype() != MEDIA_TYPE_LAYER && - layer.mediatype() != MEDIA_TYPE_NONDIST_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()); } @@ -110,6 +107,16 @@ Option<Error> validate(const Manifest& manifest) 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 { @@ -126,6 +133,28 @@ Try<Descriptor> parse(const string& s) 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( @@ -137,20 +166,20 @@ Try<Descriptor> parse(const string& s) template <> -Try<ManifestList> parse(const string& s) +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<ManifestList> manifestList = protobuf::parse<ManifestList>(json.get()); - if (manifestList.isError()) { - return Error("Protobuf parse failed: " + manifestList.error()); + Try<Index> index = protobuf::parse<Index>(json.get()); + if (index.isError()) { + return Error("Protobuf parse failed: " + index.error()); } - // Manually parse 'manifest.platform.os.version' and - // 'manifest.platform.os.features'. + // 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()); @@ -172,9 +201,9 @@ Try<ManifestList> parse(const string& s) } ManifestDescriptor* _manifest = nullptr; - for (int i = 0; i < manifestList->manifests_size(); i++) { - if (manifestList->manifests(i).digest() == digest.get()) { - _manifest = manifestList->mutable_manifests(i); + for (int i = 0; i < index->manifests_size(); i++) { + if (index->manifests(i).digest() == digest.get()) { + _manifest = index->mutable_manifests(i); break; } } @@ -185,40 +214,61 @@ Try<ManifestList> parse(const string& s) digest->value + "'"); } - Result<JSON::Object> platform = manifest.at<JSON::Object>("platform"); - if (platform.isError()) { - return Error("Failed to find 'platform': " + platform.error()); - } else if (platform.isNone()) { - return Error("Unable to find 'platform'"); - } - - Result<JSON::String> osVersion = platform->at<JSON::String>("os.version"); - if (osVersion.isError()) { + Result<JSON::Value> annotations = manifest.find<JSON::Value>("annotations"); + if (annotations.isError()) { return Error( - "Failed to find 'platform.os.version': " + osVersion.error()); + "Failed to find 'annotations': " + annotations.error()); } - if (osVersion.isSome()) { - Platform* platform = _manifest->mutable_platform(); - platform->set_os_version(osVersion->value); + 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::Array> osFeatures = platform->at<JSON::Array>("os.features"); - if (osFeatures.isError()) { - return Error( - "Failed to find 'platform.os.features': " + osFeatures.error()); + Result<JSON::Object> platform = manifest.at<JSON::Object>("platform"); + if (platform.isError()) { + return Error("Failed to find 'platform': " + platform.error()); } - if (osFeatures.isSome()) { - const vector<JSON::Value>& values = osFeatures->values; - if (values.size() != 0) { + 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(); - foreach (const JSON::Value& value, values) { - if (!value.is<JSON::String>()) { - return Error("Expecting OS feature to be string type"); - } + platform->set_os_version(osVersion->value); + } - platform->add_os_features(value.as<JSON::String>().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); + } } } } @@ -240,19 +290,19 @@ Try<ManifestList> parse(const string& s) "The value of annotation key '" + key + "' is not a JSON string"); } - Label* annotation = manifestList->add_annotations(); + Label* annotation = index->add_annotations(); annotation->set_key(key); annotation->set_value(value.as<JSON::String>().value); } } - Option<Error> error = internal::validate(manifestList.get()); + Option<Error> error = internal::validate(index.get()); if (error.isSome()) { return Error( - "OCI v1 image manifest list validation failed: " + error->message); + "OCI v1 image index validation failed: " + error->message); } - return manifestList.get(); + return index.get(); } @@ -368,6 +418,12 @@ Try<Configuration> parse(const string& s) } } + Option<Error> error = internal::validate(configuration.get()); + if (error.isSome()) { + return Error( + "OCI v1 image configuration validation failed: " + error->message); + } + return configuration.get(); }
