...

Source file src/github.com/google/go-containerregistry/pkg/v1/validate/index.go

Documentation: github.com/google/go-containerregistry/pkg/v1/validate

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validate
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-containerregistry/pkg/logs"
    25  	v1 "github.com/google/go-containerregistry/pkg/v1"
    26  	"github.com/google/go-containerregistry/pkg/v1/types"
    27  )
    28  
    29  // Index validates that idx does not violate any invariants of the index format.
    30  func Index(idx v1.ImageIndex, opt ...Option) error {
    31  	errs := []string{}
    32  
    33  	if err := validateChildren(idx, opt...); err != nil {
    34  		errs = append(errs, fmt.Sprintf("validating children: %v", err))
    35  	}
    36  
    37  	if err := validateIndexManifest(idx); err != nil {
    38  		errs = append(errs, fmt.Sprintf("validating index manifest: %v", err))
    39  	}
    40  
    41  	if len(errs) != 0 {
    42  		return errors.New(strings.Join(errs, "\n\n"))
    43  	}
    44  	return nil
    45  }
    46  
    47  type withLayer interface {
    48  	Layer(v1.Hash) (v1.Layer, error)
    49  }
    50  
    51  func validateChildren(idx v1.ImageIndex, opt ...Option) error {
    52  	manifest, err := idx.IndexManifest()
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	errs := []string{}
    58  	for i, desc := range manifest.Manifests {
    59  		switch desc.MediaType {
    60  		case types.OCIImageIndex, types.DockerManifestList:
    61  			idx, err := idx.ImageIndex(desc.Digest)
    62  			if err != nil {
    63  				return err
    64  			}
    65  			if err := Index(idx, opt...); err != nil {
    66  				errs = append(errs, fmt.Sprintf("failed to validate index Manifests[%d](%s): %v", i, desc.Digest, err))
    67  			}
    68  			if err := validateMediaType(idx, desc.MediaType); err != nil {
    69  				errs = append(errs, fmt.Sprintf("failed to validate index MediaType[%d](%s): %v", i, desc.Digest, err))
    70  			}
    71  		case types.OCIManifestSchema1, types.DockerManifestSchema2:
    72  			img, err := idx.Image(desc.Digest)
    73  			if err != nil {
    74  				return err
    75  			}
    76  			if err := Image(img, opt...); err != nil {
    77  				errs = append(errs, fmt.Sprintf("failed to validate image Manifests[%d](%s): %v", i, desc.Digest, err))
    78  			}
    79  			if err := validateMediaType(img, desc.MediaType); err != nil {
    80  				errs = append(errs, fmt.Sprintf("failed to validate image MediaType[%d](%s): %v", i, desc.Digest, err))
    81  			}
    82  			if err := validatePlatform(img, desc.Platform); err != nil {
    83  				errs = append(errs, fmt.Sprintf("failed to validate image platform[%d](%s): %v", i, desc.Digest, err))
    84  			}
    85  		default:
    86  			// Workaround for #819.
    87  			if wl, ok := idx.(withLayer); ok {
    88  				layer, err := wl.Layer(desc.Digest)
    89  				if err != nil {
    90  					return fmt.Errorf("failed to get layer Manifests[%d]: %w", i, err)
    91  				}
    92  				if err := Layer(layer, opt...); err != nil {
    93  					lerr := fmt.Sprintf("failed to validate layer Manifests[%d](%s): %v", i, desc.Digest, err)
    94  					if desc.MediaType.IsDistributable() {
    95  						errs = append(errs, lerr)
    96  					} else {
    97  						logs.Warn.Printf("nondistributable layer failure: %v", lerr)
    98  					}
    99  				}
   100  			} else {
   101  				logs.Warn.Printf("Unexpected manifest: %s", desc.MediaType)
   102  			}
   103  		}
   104  	}
   105  
   106  	if len(errs) != 0 {
   107  		return errors.New(strings.Join(errs, "\n"))
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  type withMediaType interface {
   114  	MediaType() (types.MediaType, error)
   115  }
   116  
   117  func validateMediaType(i withMediaType, want types.MediaType) error {
   118  	got, err := i.MediaType()
   119  	if err != nil {
   120  		return err
   121  	}
   122  	if want != got {
   123  		return fmt.Errorf("mismatched mediaType: MediaType() = %v != %v", got, want)
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  func validateIndexManifest(idx v1.ImageIndex) error {
   130  	digest, err := idx.Digest()
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	size, err := idx.Size()
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	rm, err := idx.RawManifest()
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	hash, _, err := v1.SHA256(bytes.NewReader(rm))
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	m, err := idx.IndexManifest()
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	pm, err := v1.ParseIndexManifest(bytes.NewReader(rm))
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	errs := []string{}
   161  	if digest != hash {
   162  		errs = append(errs, fmt.Sprintf("mismatched manifest digest: Digest()=%s, SHA256(RawManifest())=%s", digest, hash))
   163  	}
   164  
   165  	if diff := cmp.Diff(pm, m); diff != "" {
   166  		errs = append(errs, fmt.Sprintf("mismatched manifest content: (-ParseIndexManifest(RawManifest()) +Manifest()) %s", diff))
   167  	}
   168  
   169  	if size != int64(len(rm)) {
   170  		errs = append(errs, fmt.Sprintf("mismatched manifest size: Size()=%d, len(RawManifest())=%d", size, len(rm)))
   171  	}
   172  
   173  	if len(errs) != 0 {
   174  		return errors.New(strings.Join(errs, "\n"))
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func validatePlatform(img v1.Image, want *v1.Platform) error {
   181  	if want == nil {
   182  		return nil
   183  	}
   184  
   185  	cf, err := img.ConfigFile()
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	got := cf.Platform()
   191  
   192  	if got == nil {
   193  		return fmt.Errorf("config file missing platform fields")
   194  	}
   195  
   196  	if got.Equals(*want) {
   197  		return nil
   198  	}
   199  
   200  	errs := []string{}
   201  
   202  	if got.OS != want.OS {
   203  		errs = append(errs, fmt.Sprintf("mismatched OS: %s != %s", got.OS, want.OS))
   204  	}
   205  
   206  	if got.Architecture != want.Architecture {
   207  		errs = append(errs, fmt.Sprintf("mismatched Architecture: %s != %s", got.Architecture, want.Architecture))
   208  	}
   209  
   210  	if got.OSVersion != want.OSVersion {
   211  		errs = append(errs, fmt.Sprintf("mismatched OSVersion: %s != %s", got.OSVersion, want.OSVersion))
   212  	}
   213  
   214  	if got.OSVersion != want.OSVersion {
   215  		errs = append(errs, fmt.Sprintf("mismatched OSVersion: %s != %s", got.OSVersion, want.OSVersion))
   216  	}
   217  
   218  	if len(errs) == 0 {
   219  		// If we got here, some features might be mismatched. Just add those...
   220  		if len(got.Features) != 0 || len(want.Features) != 0 {
   221  			errs = append(errs, fmt.Sprintf("mismatched Features: %v, %v", got.Features, want.Features))
   222  		}
   223  		if len(got.OSFeatures) != 0 || len(want.OSFeatures) != 0 {
   224  			errs = append(errs, fmt.Sprintf("mismatched OSFeatures: %v, %v", got.OSFeatures, want.OSFeatures))
   225  		}
   226  	}
   227  
   228  	return errors.New(strings.Join(errs, "\n"))
   229  }
   230  

View as plain text