blob: 07c0b87ee1a3b6981adfc96f5a7add6f625374c1 [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 <stout/foreach.hpp>
#include <stout/json.hpp>
#include <stout/none.hpp>
#include <stout/protobuf.hpp>
#include <stout/strings.hpp>
#include <mesos/docker/spec.hpp>
using std::ostream;
using std::string;
using std::vector;
namespace docker {
namespace spec {
// TODO(jieyu): Use regex to parse and verify the reference.
Try<ImageReference> parseImageReference(const string& _s)
{
ImageReference reference;
string s(_s);
// Extract the digest.
if (strings::contains(s, "@")) {
vector<string> split = strings::split(s, "@");
if (split.size() != 2) {
return Error("Multiple '@' symbols found");
}
s = split[0];
reference.set_digest(split[1]);
}
// Remove the tag. We need to watch out for a
// host:port registry, which also contains ':'.
if (strings::contains(s, ":")) {
vector<string> split = strings::split(s, ":");
// The tag must be the last component. If a slash is
// present there is a registry port and no tag.
if (!strings::contains(split.back(), "/")) {
reference.set_tag(split.back());
split.pop_back();
s = strings::join(":", split);
}
}
// Extract the registry and repository. The first component can
// either be the registry, or the first part of the repository!
// We resolve this ambiguity using the same hacks used in the
// docker code ('.', ':', 'localhost' indicate a registry).
vector<string> split = strings::split(s, "/", 2);
if (split.size() == 1) {
reference.set_repository(s);
} else if (strings::contains(split[0], ".") ||
strings::contains(split[0], ":") ||
split[0] == "localhost") {
reference.set_registry(split[0]);
reference.set_repository(split[1]);
} else {
reference.set_repository(s);
}
return reference;
}
ostream& operator<<(ostream& stream, const ImageReference& reference)
{
if (reference.has_registry()) {
stream << reference.registry() << "/" << reference.repository();
} else {
stream << reference.repository();
}
if (reference.has_tag()) {
stream << ":" << reference.tag();
}
return stream;
}
namespace v1 {
Option<Error> validate(const ImageManifest& manifest)
{
// TODO(gilbert): Add validations.
return None();
}
Try<ImageManifest> parse(const JSON::Object& json)
{
Try<ImageManifest> manifest = protobuf::parse<ImageManifest>(json);
if (manifest.isError()) {
return Error("Protobuf parse failed: " + manifest.error());
}
Option<Error> error = validate(manifest.get());
if (error.isSome()) {
return Error("Docker v1 image manifest validation failed: " +
error.get().message);
}
return manifest.get();
}
Try<ImageManifest> parse(const string& s)
{
Try<JSON::Object> json = JSON::parse<JSON::Object>(s);
if (json.isError()) {
return Error("JSON parse failed: " + json.error());
}
return parse(json.get());
}
} // namespace v1 {
namespace v2 {
Option<Error> validate(const ImageManifest& manifest)
{
// Validate required fields are present,
// e.g., repeated fields that has to be >= 1.
if (manifest.fslayers_size() <= 0) {
return Error("'fsLayers' field size must be at least one");
}
if (manifest.history_size() <= 0) {
return Error("'history' field size must be at least one");
}
if (manifest.signatures_size() <= 0) {
return Error("'signatures' field size must be at least one");
}
// Verify that blobSum and v1Compatibility numbers are equal.
if (manifest.fslayers_size() != manifest.history_size()) {
return Error("The size of 'fsLayers' should be equal "
"to the size of 'history'");
}
// Verify 'fsLayers' field.
foreach (const ImageManifest::FsLayer& fslayer, manifest.fslayers()) {
const string& blobSum = fslayer.blobsum();
if (!strings::contains(blobSum, ":")) {
return Error("Incorrect 'blobSum' format: " + blobSum);
}
}
return None();
}
Try<ImageManifest> parse(const JSON::Object& json)
{
Try<ImageManifest> manifest = protobuf::parse<ImageManifest>(json);
if (manifest.isError()) {
return Error("Protobuf parse failed: " + manifest.error());
}
for (int i = 0; i < manifest.get().history_size(); i++) {
Try<JSON::Object> v1Compatibility = JSON::parse<JSON::Object>(
manifest.get().history(i).v1compatibility());
if (v1Compatibility.isError()) {
return Error("Parsing v1Compatibility JSON failed: " +
v1Compatibility.error());
}
Try<v1::ImageManifest> v1 = v1::parse(v1Compatibility.get());
if (v1.isError()) {
return Error("Parsing v1Compatibility protobuf failed: " + v1.error());
}
CHECK(!manifest.get().history(i).has_v1());
manifest->mutable_history(i)->mutable_v1()->CopyFrom(v1.get());
}
Option<Error> error = validate(manifest.get());
if (error.isSome()) {
return Error("Docker v2 image manifest validation failed: " +
error.get().message);
}
return manifest.get();
}
Try<ImageManifest> parse(const string& s)
{
Try<JSON::Object> json = JSON::parse<JSON::Object>(s);
if (json.isError()) {
return Error("JSON parse failed: " + json.error());
}
return parse(json.get());
}
} // namespace v2 {
} // namespace spec {
} // namespace docker {