diff --git a/Gopkg.lock b/Gopkg.lock index cee1f94..cf2cb20 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,5 +1,10 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + name = "github.com/containers/image" + packages = ["docker/reference"] + revision = "1ec44148eb9b0bd57fd7b220c12257cccd528663" + version = "v1.5" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index 310a3a4..f59202d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -40,3 +40,7 @@ [[constraint]] branch = "master" name = "github.com/golang/glog" + +[[constraint]] + name = "github.com/containers/image" + version = "1.5.0" diff --git a/cmd/root.go b/cmd/root.go index 89b3da9..360e848 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,12 +7,16 @@ import ( "io/ioutil" "os" "strings" + // Enable sha256 in container image references + _ "crypto/sha256" "github.com/golang/glog" "github.com/openshift/pivot/types" "github.com/openshift/pivot/utils" "github.com/spf13/cobra" "github.com/spf13/pflag" + + imgref "github.com/containers/image/docker/reference" ) // flag storage @@ -70,6 +74,46 @@ func getDefaultDeployment() types.RpmOstreeDeployment { return rosState.Deployments[0] } +// getRefDigest parses a Docker/OCI image reference and returns +// its digest, or an error if the string fails to parse as +// a "canonical" image reference with a digest. +func getRefDigest(ref string) (string, error) { + refParsed, err := imgref.ParseNamed(ref) + if err != nil { + return "", fmt.Errorf("parsing reference: %q: %v", ref, err) + } + canon, ok := refParsed.(imgref.Canonical) + if !ok { + return "", fmt.Errorf("not canonical form: %q: %v", ref, err) + } + + return canon.Digest().String(), nil +} + +// compareOSImageURL determines whether two images are the same, or have +// matching digests. +func compareOSImageURL(current, desired string) (bool, error) { + if current == desired { + return true, nil + } + + currentDigest, err := getRefDigest(current) + if err != nil { + return false, fmt.Errorf("parsing current osImageURL: %v", err) + } + desiredDigest, err := getRefDigest(desired) + if err != nil { + return false, fmt.Errorf("parsing desired osImageURL: %v", err) + } + + if currentDigest == desiredDigest { + glog.Infof("Current and target osImageURL have matching digest %q", currentDigest) + return true, nil + } + + return false, nil +} + // pullAndRebase potentially rebases system if not already rebased. func pullAndRebase(container string) (imgid string, changed bool) { defaultDeployment := getDefaultDeployment() @@ -98,7 +142,11 @@ func pullAndRebase(container string) (imgid string, changed bool) { imgid = fmt.Sprintf("%s@%s", imagedata.Name, imagedata.Digest) glog.Infof("Resolved to: %s", imgid) - if previousPivot == imgid { + targetMatched, err := compareOSImageURL(previousPivot, imgid) + if err != nil { + glog.Fatalf("%v", err) + } + if targetMatched { changed = false return } diff --git a/cmd/root_test.go b/cmd/root_test.go new file mode 100644 index 0000000..a2209a1 --- /dev/null +++ b/cmd/root_test.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "testing" +) + +func mustCompareOSImageURL(t *testing.T, refA, refB string) bool { + m, err := compareOSImageURL(refA, refB) + if err != nil { + t.Fatalf("%v", err) + } + return m +} + +func TestCompareOSImageURL(t *testing.T) { + refA := "registry.example.com/foo/bar@sha256:0743a3cc3bcf3b4aabb814500c2739f84cb085ff4e7ec7996aef7977c4c19c7f" + refB := "registry.example.com/foo/baz@sha256:0743a3cc3bcf3b4aabb814500c2739f84cb085ff4e7ec7996aef7977c4c19c7f" + refC := "registry.example.com/foo/bar@sha256:2a76681fd15bfc06fa4aa0ff6913ba17527e075417fc92ea29f6bcc2afca24ff" + if !mustCompareOSImageURL(t, refA, refA) { + t.Fatalf("Expected refA ident") + } + if !mustCompareOSImageURL(t, refA, refB) { + t.Fatalf("Expected refA = refB") + } + if mustCompareOSImageURL(t, refA, refC) { + t.Fatalf("Expected refA != refC") + } + m, err := compareOSImageURL(refA, "registry.example.com/foo/bar") + if m || err == nil { + t.Fatalf("Expected err") + } +} diff --git a/vendor/github.com/containers/image/LICENSE b/vendor/github.com/containers/image/LICENSE new file mode 100644 index 0000000..9535635 --- /dev/null +++ b/vendor/github.com/containers/image/LICENSE @@ -0,0 +1,189 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Licensed 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 + + https://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. diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.bz2 b/vendor/github.com/containers/image/copy/fixtures/Hello.bz2 new file mode 120000 index 0000000..fc28d6c --- /dev/null +++ b/vendor/github.com/containers/image/copy/fixtures/Hello.bz2 @@ -0,0 +1 @@ +../../pkg/compression/fixtures/Hello.bz2 \ No newline at end of file diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.gz b/vendor/github.com/containers/image/copy/fixtures/Hello.gz new file mode 120000 index 0000000..08aa805 --- /dev/null +++ b/vendor/github.com/containers/image/copy/fixtures/Hello.gz @@ -0,0 +1 @@ +../../pkg/compression/fixtures/Hello.gz \ No newline at end of file diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed b/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed new file mode 120000 index 0000000..49b4662 --- /dev/null +++ b/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed @@ -0,0 +1 @@ +../../pkg/compression/fixtures/Hello.uncompressed \ No newline at end of file diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.xz b/vendor/github.com/containers/image/copy/fixtures/Hello.xz new file mode 120000 index 0000000..77bcd85 --- /dev/null +++ b/vendor/github.com/containers/image/copy/fixtures/Hello.xz @@ -0,0 +1 @@ +../../pkg/compression/fixtures/Hello.xz \ No newline at end of file diff --git a/vendor/github.com/containers/image/docker/reference/README.md b/vendor/github.com/containers/image/docker/reference/README.md new file mode 100644 index 0000000..53a88de --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/README.md @@ -0,0 +1,2 @@ +This is a copy of github.com/docker/distribution/reference as of commit fb0bebc4b64e3881cc52a2478d749845ed76d2a8, +except that ParseAnyReferenceWithSet has been removed to drop the dependency on github.com/docker/distribution/digestset. \ No newline at end of file diff --git a/vendor/github.com/containers/image/docker/reference/helpers.go b/vendor/github.com/containers/image/docker/reference/helpers.go new file mode 100644 index 0000000..978df7e --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/helpers.go @@ -0,0 +1,42 @@ +package reference + +import "path" + +// IsNameOnly returns true if reference only contains a repo name. +func IsNameOnly(ref Named) bool { + if _, ok := ref.(NamedTagged); ok { + return false + } + if _, ok := ref.(Canonical); ok { + return false + } + return true +} + +// FamiliarName returns the familiar name string +// for the given named, familiarizing if needed. +func FamiliarName(ref Named) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().Name() + } + return ref.Name() +} + +// FamiliarString returns the familiar string representation +// for the given reference, familiarizing if needed. +func FamiliarString(ref Reference) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().String() + } + return ref.String() +} + +// FamiliarMatch reports whether ref matches the specified pattern. +// See https://godoc.org/path#Match for supported patterns. +func FamiliarMatch(pattern string, ref Reference) (bool, error) { + matched, err := path.Match(pattern, FamiliarString(ref)) + if namedRef, isNamed := ref.(Named); isNamed && !matched { + matched, _ = path.Match(pattern, FamiliarName(namedRef)) + } + return matched, err +} diff --git a/vendor/github.com/containers/image/docker/reference/normalize.go b/vendor/github.com/containers/image/docker/reference/normalize.go new file mode 100644 index 0000000..fcc436a --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/normalize.go @@ -0,0 +1,152 @@ +package reference + +import ( + "errors" + "fmt" + "strings" + + "github.com/opencontainers/go-digest" +) + +var ( + legacyDefaultDomain = "index.docker.io" + defaultDomain = "docker.io" + officialRepoName = "library" + defaultTag = "latest" +) + +// normalizedNamed represents a name which has been +// normalized and has a familiar form. A familiar name +// is what is used in Docker UI. An example normalized +// name is "docker.io/library/ubuntu" and corresponding +// familiar name of "ubuntu". +type normalizedNamed interface { + Named + Familiar() Named +} + +// 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. +func ParseNormalizedNamed(s string) (Named, error) { + if ok := anchoredIdentifierRegexp.MatchString(s); ok { + return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) + } + domain, remainder := splitDockerDomain(s) + var remoteName string + if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { + remoteName = remainder[:tagSep] + } else { + remoteName = remainder + } + if strings.ToLower(remoteName) != remoteName { + return nil, errors.New("invalid reference format: repository name must be lowercase") + } + + ref, err := Parse(domain + "/" + remainder) + if err != nil { + return nil, err + } + named, isNamed := ref.(Named) + if !isNamed { + return nil, fmt.Errorf("reference %s has no name", ref.String()) + } + return named, nil +} + +// 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. +func splitDockerDomain(name string) (domain, remainder string) { + i := strings.IndexRune(name, '/') + if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { + domain, remainder = defaultDomain, name + } else { + domain, remainder = name[:i], name[i+1:] + } + if domain == legacyDefaultDomain { + domain = defaultDomain + } + if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { + remainder = officialRepoName + "/" + remainder + } + return +} + +// 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. +func familiarizeName(named namedRepository) repository { + repo := repository{ + domain: named.Domain(), + path: named.Path(), + } + + if repo.domain == defaultDomain { + repo.domain = "" + // Handle official repositories which have the pattern "library/" + if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName { + repo.path = split[1] + } + } + return repo +} + +func (r reference) Familiar() Named { + return reference{ + namedRepository: familiarizeName(r.namedRepository), + tag: r.tag, + digest: r.digest, + } +} + +func (r repository) Familiar() Named { + return familiarizeName(r) +} + +func (t taggedReference) Familiar() Named { + return taggedReference{ + namedRepository: familiarizeName(t.namedRepository), + tag: t.tag, + } +} + +func (c canonicalReference) Familiar() Named { + return canonicalReference{ + namedRepository: familiarizeName(c.namedRepository), + digest: c.digest, + } +} + +// TagNameOnly adds the default tag "latest" to a reference if it only has +// a repo name. +func TagNameOnly(ref Named) Named { + if IsNameOnly(ref) { + namedTagged, err := WithTag(ref, defaultTag) + if err != nil { + // Default tag must be valid, to create a NamedTagged + // type with non-validated input the WithTag function + // should be used instead + panic(err) + } + return namedTagged + } + return ref +} + +// ParseAnyReference parses a reference string as a possible identifier, +// full digest, or familiar name. +func ParseAnyReference(ref string) (Reference, error) { + if ok := anchoredIdentifierRegexp.MatchString(ref); ok { + return digestReference("sha256:" + ref), nil + } + if dgst, err := digest.Parse(ref); err == nil { + return digestReference(dgst), nil + } + + return ParseNormalizedNamed(ref) +} diff --git a/vendor/github.com/containers/image/docker/reference/reference.go b/vendor/github.com/containers/image/docker/reference/reference.go new file mode 100644 index 0000000..fd3510e --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/reference.go @@ -0,0 +1,433 @@ +// Package reference provides a general type to represent any way of referencing images within the registry. +// Its main purpose is to abstract tags and digests (content-addressable hash). +// +// Grammar +// +// reference := name [ ":" tag ] [ "@" digest ] +// name := [domain '/'] path-component ['/' path-component]* +// domain := domain-component ['.' domain-component]* [':' port-number] +// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ +// port-number := /[0-9]+/ +// path-component := alpha-numeric [separator alpha-numeric]* +// alpha-numeric := /[a-z0-9]+/ +// separator := /[_.]|__|[-]*/ +// +// tag := /[\w][\w.-]{0,127}/ +// +// digest := digest-algorithm ":" digest-hex +// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ] +// digest-algorithm-separator := /[+.-_]/ +// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ +// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value +// +// identifier := /[a-f0-9]{64}/ +// short-identifier := /[a-f0-9]{6,64}/ +package reference + +import ( + "errors" + "fmt" + "strings" + + "github.com/opencontainers/go-digest" +) + +const ( + // NameTotalLengthMax is the maximum total number of characters in a repository name. + NameTotalLengthMax = 255 +) + +var ( + // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. + ErrReferenceInvalidFormat = errors.New("invalid reference format") + + // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. + ErrTagInvalidFormat = errors.New("invalid tag format") + + // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. + ErrDigestInvalidFormat = errors.New("invalid digest format") + + // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. + ErrNameContainsUppercase = errors.New("repository name must be lowercase") + + // ErrNameEmpty is returned for empty, invalid repository names. + ErrNameEmpty = errors.New("repository name must have at least one component") + + // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax. + ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) + + // ErrNameNotCanonical is returned when a name is not canonical. + ErrNameNotCanonical = errors.New("repository name must be canonical") +) + +// Reference is an opaque object reference identifier that may include +// modifiers such as a hostname, name, tag, and digest. +type Reference interface { + // String returns the full reference + String() string +} + +// Field provides a wrapper type for resolving correct reference types when +// working with encoding. +type Field struct { + reference Reference +} + +// AsField wraps a reference in a Field for encoding. +func AsField(reference Reference) Field { + return Field{reference} +} + +// Reference unwraps the reference type from the field to +// return the Reference object. This object should be +// of the appropriate type to further check for different +// reference types. +func (f Field) Reference() Reference { + return f.reference +} + +// MarshalText serializes the field to byte text which +// is the string of the reference. +func (f Field) MarshalText() (p []byte, err error) { + return []byte(f.reference.String()), nil +} + +// UnmarshalText parses text bytes by invoking the +// reference parser to ensure the appropriately +// typed reference object is wrapped by field. +func (f *Field) UnmarshalText(p []byte) error { + r, err := Parse(string(p)) + if err != nil { + return err + } + + f.reference = r + return nil +} + +// Named is an object with a full name +type Named interface { + Reference + Name() string +} + +// Tagged is an object which has a tag +type Tagged interface { + Reference + Tag() string +} + +// NamedTagged is an object including a name and tag. +type NamedTagged interface { + Named + Tag() string +} + +// Digested is an object which has a digest +// in which it can be referenced by +type Digested interface { + Reference + Digest() digest.Digest +} + +// Canonical reference is an object with a fully unique +// name including a name with domain and digest +type Canonical interface { + Named + Digest() digest.Digest +} + +// namedRepository is a reference to a repository with a name. +// A namedRepository has both domain and path components. +type namedRepository interface { + Named + Domain() string + Path() string +} + +// Domain returns the domain part of the Named reference +func Domain(named Named) string { + if r, ok := named.(namedRepository); ok { + return r.Domain() + } + domain, _ := splitDomain(named.Name()) + return domain +} + +// Path returns the name without the domain part of the Named reference +func Path(named Named) (name string) { + if r, ok := named.(namedRepository); ok { + return r.Path() + } + _, path := splitDomain(named.Name()) + return path +} + +func splitDomain(name string) (string, string) { + match := anchoredNameRegexp.FindStringSubmatch(name) + if len(match) != 3 { + return "", name + } + return match[1], match[2] +} + +// SplitHostname splits a named reference into a +// hostname and name string. If no valid hostname is +// found, the hostname is empty and the full value +// is returned as name +// DEPRECATED: Use Domain or Path +func SplitHostname(named Named) (string, string) { + if r, ok := named.(namedRepository); ok { + return r.Domain(), r.Path() + } + return splitDomain(named.Name()) +} + +// Parse parses s and returns a syntactically valid Reference. +// If an error was encountered it is returned, along with a nil Reference. +// NOTE: Parse will not handle short digests. +func Parse(s string) (Reference, error) { + matches := ReferenceRegexp.FindStringSubmatch(s) + if matches == nil { + if s == "" { + return nil, ErrNameEmpty + } + if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { + return nil, ErrNameContainsUppercase + } + return nil, ErrReferenceInvalidFormat + } + + if len(matches[1]) > NameTotalLengthMax { + return nil, ErrNameTooLong + } + + var repo repository + + nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) + if nameMatch != nil && len(nameMatch) == 3 { + repo.domain = nameMatch[1] + repo.path = nameMatch[2] + } else { + repo.domain = "" + repo.path = matches[1] + } + + ref := reference{ + namedRepository: repo, + tag: matches[2], + } + if matches[3] != "" { + var err error + ref.digest, err = digest.Parse(matches[3]) + if err != nil { + return nil, err + } + } + + r := getBestReferenceType(ref) + if r == nil { + return nil, ErrNameEmpty + } + + return r, nil +} + +// ParseNamed parses s and returns a syntactically valid reference implementing +// the Named interface. The reference must have a name and be in the canonical +// form, otherwise an error is returned. +// If an error was encountered it is returned, along with a nil Reference. +// NOTE: ParseNamed will not handle short digests. +func ParseNamed(s string) (Named, error) { + named, err := ParseNormalizedNamed(s) + if err != nil { + return nil, err + } + if named.String() != s { + return nil, ErrNameNotCanonical + } + return named, nil +} + +// WithName returns a named object representing the given string. If the input +// is invalid ErrReferenceInvalidFormat will be returned. +func WithName(name string) (Named, error) { + if len(name) > NameTotalLengthMax { + return nil, ErrNameTooLong + } + + match := anchoredNameRegexp.FindStringSubmatch(name) + if match == nil || len(match) != 3 { + return nil, ErrReferenceInvalidFormat + } + return repository{ + domain: match[1], + path: match[2], + }, nil +} + +// WithTag combines the name from "name" and the tag from "tag" to form a +// reference incorporating both the name and the tag. +func WithTag(name Named, tag string) (NamedTagged, error) { + if !anchoredTagRegexp.MatchString(tag) { + return nil, ErrTagInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if canonical, ok := name.(Canonical); ok { + return reference{ + namedRepository: repo, + tag: tag, + digest: canonical.Digest(), + }, nil + } + return taggedReference{ + namedRepository: repo, + tag: tag, + }, nil +} + +// WithDigest combines the name from "name" and the digest from "digest" to form +// a reference incorporating both the name and the digest. +func WithDigest(name Named, digest digest.Digest) (Canonical, error) { + if !anchoredDigestRegexp.MatchString(digest.String()) { + return nil, ErrDigestInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if tagged, ok := name.(Tagged); ok { + return reference{ + namedRepository: repo, + tag: tagged.Tag(), + digest: digest, + }, nil + } + return canonicalReference{ + namedRepository: repo, + digest: digest, + }, nil +} + +// TrimNamed removes any tag or digest from the named reference. +func TrimNamed(ref Named) Named { + domain, path := SplitHostname(ref) + return repository{ + domain: domain, + path: path, + } +} + +func getBestReferenceType(ref reference) Reference { + if ref.Name() == "" { + // Allow digest only references + if ref.digest != "" { + return digestReference(ref.digest) + } + return nil + } + if ref.tag == "" { + if ref.digest != "" { + return canonicalReference{ + namedRepository: ref.namedRepository, + digest: ref.digest, + } + } + return ref.namedRepository + } + if ref.digest == "" { + return taggedReference{ + namedRepository: ref.namedRepository, + tag: ref.tag, + } + } + + return ref +} + +type reference struct { + namedRepository + tag string + digest digest.Digest +} + +func (r reference) String() string { + return r.Name() + ":" + r.tag + "@" + r.digest.String() +} + +func (r reference) Tag() string { + return r.tag +} + +func (r reference) Digest() digest.Digest { + return r.digest +} + +type repository struct { + domain string + path string +} + +func (r repository) String() string { + return r.Name() +} + +func (r repository) Name() string { + if r.domain == "" { + return r.path + } + return r.domain + "/" + r.path +} + +func (r repository) Domain() string { + return r.domain +} + +func (r repository) Path() string { + return r.path +} + +type digestReference digest.Digest + +func (d digestReference) String() string { + return digest.Digest(d).String() +} + +func (d digestReference) Digest() digest.Digest { + return digest.Digest(d) +} + +type taggedReference struct { + namedRepository + tag string +} + +func (t taggedReference) String() string { + return t.Name() + ":" + t.tag +} + +func (t taggedReference) Tag() string { + return t.tag +} + +type canonicalReference struct { + namedRepository + digest digest.Digest +} + +func (c canonicalReference) String() string { + return c.Name() + "@" + c.digest.String() +} + +func (c canonicalReference) Digest() digest.Digest { + return c.digest +} diff --git a/vendor/github.com/containers/image/docker/reference/regexp.go b/vendor/github.com/containers/image/docker/reference/regexp.go new file mode 100644 index 0000000..405e995 --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/regexp.go @@ -0,0 +1,143 @@ +package reference + +import "regexp" + +var ( + // alphaNumericRegexp defines the alpha numeric atom, typically a + // component of names. This only allows lower case characters and digits. + alphaNumericRegexp = match(`[a-z0-9]+`) + + // separatorRegexp defines the separators allowed to be embedded in name + // components. This allow one period, one or two underscore and multiple + // dashes. + separatorRegexp = match(`(?:[._]|__|[-]*)`) + + // nameComponentRegexp restricts registry path component names to start + // with at least one letter or number, with following parts able to be + // separated by one period, one or two underscore and multiple dashes. + nameComponentRegexp = expression( + alphaNumericRegexp, + optional(repeated(separatorRegexp, alphaNumericRegexp))) + + // domainComponentRegexp restricts the registry domain component of a + // repository name to start with a component as defined by domainRegexp + // and followed by an optional port. + domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) + + // domainRegexp defines the structure of potential domain components + // that may be part of image names. This is purposely a subset of what is + // allowed by DNS to ensure backwards compatibility with Docker image + // names. + domainRegexp = expression( + domainComponentRegexp, + optional(repeated(literal(`.`), domainComponentRegexp)), + optional(literal(`:`), match(`[0-9]+`))) + + // TagRegexp matches valid tag names. From docker/docker:graph/tags.go. + TagRegexp = match(`[\w][\w.-]{0,127}`) + + // anchoredTagRegexp matches valid tag names, anchored at the start and + // end of the matched string. + anchoredTagRegexp = anchored(TagRegexp) + + // DigestRegexp matches valid digests. + DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`) + + // anchoredDigestRegexp matches valid digests, anchored at the start and + // end of the matched string. + anchoredDigestRegexp = anchored(DigestRegexp) + + // NameRegexp is the format for the name component of references. The + // regexp has capturing groups for the domain and name part omitting + // the separating forward slash from either. + NameRegexp = expression( + optional(domainRegexp, literal(`/`)), + nameComponentRegexp, + optional(repeated(literal(`/`), nameComponentRegexp))) + + // anchoredNameRegexp is used to parse a name value, capturing the + // domain and trailing components. + anchoredNameRegexp = anchored( + optional(capture(domainRegexp), literal(`/`)), + capture(nameComponentRegexp, + optional(repeated(literal(`/`), nameComponentRegexp)))) + + // ReferenceRegexp is the full supported format of a reference. The regexp + // is anchored and has capturing groups for name, tag, and digest + // components. + ReferenceRegexp = anchored(capture(NameRegexp), + optional(literal(":"), capture(TagRegexp)), + optional(literal("@"), capture(DigestRegexp))) + + // IdentifierRegexp is the format for string identifier used as a + // content addressable identifier using sha256. These identifiers + // are like digests without the algorithm, since sha256 is used. + IdentifierRegexp = match(`([a-f0-9]{64})`) + + // ShortIdentifierRegexp is the format used to represent a prefix + // of an identifier. A prefix may be used to match a sha256 identifier + // within a list of trusted identifiers. + ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`) + + // anchoredIdentifierRegexp is used to check or match an + // identifier value, anchored at start and end of string. + anchoredIdentifierRegexp = anchored(IdentifierRegexp) + + // anchoredShortIdentifierRegexp is used to check if a value + // is a possible identifier prefix, anchored at start and end + // of string. + anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp) +) + +// match compiles the string to a regular expression. +var match = regexp.MustCompile + +// literal compiles s into a literal regular expression, escaping any regexp +// reserved characters. +func literal(s string) *regexp.Regexp { + re := match(regexp.QuoteMeta(s)) + + if _, complete := re.LiteralPrefix(); !complete { + panic("must be a literal") + } + + return re +} + +// expression defines a full expression, where each regular expression must +// follow the previous. +func expression(res ...*regexp.Regexp) *regexp.Regexp { + var s string + for _, re := range res { + s += re.String() + } + + return match(s) +} + +// optional wraps the expression in a non-capturing group and makes the +// production optional. +func optional(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `?`) +} + +// repeated wraps the regexp in a non-capturing group to get one or more +// matches. +func repeated(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `+`) +} + +// group wraps the regexp in a non-capturing group. +func group(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(?:` + expression(res...).String() + `)`) +} + +// capture wraps the expression in a capturing group. +func capture(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(` + expression(res...).String() + `)`) +} + +// anchored anchors the regular expression by adding start and end delimiters. +func anchored(res ...*regexp.Regexp) *regexp.Regexp { + return match(`^` + expression(res...).String() + `$`) +} diff --git a/vendor/github.com/containers/image/manifest/fixtures/schema2-to-schema1-by-docker.json b/vendor/github.com/containers/image/manifest/fixtures/schema2-to-schema1-by-docker.json new file mode 120000 index 0000000..79e638a --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/schema2-to-schema1-by-docker.json @@ -0,0 +1 @@ +../../image/fixtures/schema2-to-schema1-by-docker.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/missing-cert/client-cert-1.key b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/missing-cert/client-cert-1.key new file mode 120000 index 0000000..c8cf88d --- /dev/null +++ b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/missing-cert/client-cert-1.key @@ -0,0 +1 @@ +../full/client-cert-1.key \ No newline at end of file diff --git a/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/missing-key/client-cert-1.cert b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/missing-key/client-cert-1.cert new file mode 120000 index 0000000..2c62e46 --- /dev/null +++ b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/missing-key/client-cert-1.cert @@ -0,0 +1 @@ +../full/client-cert-1.cert \ No newline at end of file diff --git a/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-ca/unreadable.crt b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-ca/unreadable.crt new file mode 120000 index 0000000..4cd6460 --- /dev/null +++ b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-ca/unreadable.crt @@ -0,0 +1 @@ +/this/does/not/exist \ No newline at end of file diff --git a/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-cert/client-cert-1.cert b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-cert/client-cert-1.cert new file mode 120000 index 0000000..4cd6460 --- /dev/null +++ b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-cert/client-cert-1.cert @@ -0,0 +1 @@ +/this/does/not/exist \ No newline at end of file diff --git a/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-cert/client-cert-1.key b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-cert/client-cert-1.key new file mode 120000 index 0000000..c8cf88d --- /dev/null +++ b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-cert/client-cert-1.key @@ -0,0 +1 @@ +../full/client-cert-1.key \ No newline at end of file diff --git a/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-key/client-cert-1.cert b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-key/client-cert-1.cert new file mode 120000 index 0000000..2c62e46 --- /dev/null +++ b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-key/client-cert-1.cert @@ -0,0 +1 @@ +../full/client-cert-1.cert \ No newline at end of file diff --git a/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-key/client-cert-1.key b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-key/client-cert-1.key new file mode 120000 index 0000000..4cd6460 --- /dev/null +++ b/vendor/github.com/containers/image/pkg/tlsclientconfig/testdata/unreadable-key/client-cert-1.key @@ -0,0 +1 @@ +/this/does/not/exist \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/manifest.json new file mode 120000 index 0000000..3dee14b --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/manifest.json @@ -0,0 +1 @@ +../v2s1-invalid-signatures.manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/signature-1 new file mode 120000 index 0000000..f010fd4 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/signature-1 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/manifest.json new file mode 120000 index 0000000..ff7d2ff --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/manifest.json @@ -0,0 +1 @@ +../dir-img-valid/manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-1 new file mode 120000 index 0000000..b27cdc4 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-1 @@ -0,0 +1 @@ +../invalid-blob.signature \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-2 b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-2 new file mode 120000 index 0000000..f010fd4 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-2 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/signature-1 new file mode 120000 index 0000000..f010fd4 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/signature-1 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-no-manifest/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-no-manifest/signature-1 new file mode 120000 index 0000000..f010fd4 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-no-manifest/signature-1 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-unsigned/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-unsigned/manifest.json new file mode 120000 index 0000000..ff7d2ff --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-unsigned/manifest.json @@ -0,0 +1 @@ +../dir-img-valid/manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/manifest.json new file mode 120000 index 0000000..ff7d2ff --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/manifest.json @@ -0,0 +1 @@ +../dir-img-valid/manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-1 new file mode 120000 index 0000000..f010fd4 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-1 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-valid/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid/manifest.json new file mode 120000 index 0000000..c5bd254 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid/manifest.json @@ -0,0 +1 @@ +../image.manifest.json \ No newline at end of file