github.com/jcarley/cli@v0.0.0-20180201210820-966d90434c30/test/tuf-mock.go (about)

     1  package test
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"os"
     7  	"runtime"
     8  	"strings"
     9  
    10  	"github.com/Sirupsen/logrus"
    11  	"github.com/daticahealth/cli/lib/images"
    12  	"github.com/daticahealth/cli/lib/prompts"
    13  	"github.com/daticahealth/cli/models"
    14  	notaryClient "github.com/docker/notary/client"
    15  	"github.com/docker/notary/client/changelist"
    16  	"github.com/docker/notary/trustpinning"
    17  	"github.com/docker/notary/tuf/data"
    18  	"github.com/olekukonko/tablewriter"
    19  	digest "github.com/opencontainers/go-digest"
    20  )
    21  
    22  // FakeImages is a mock images struct
    23  type FakeImages struct {
    24  	Settings *models.Settings
    25  }
    26  
    27  // Testing constants
    28  const (
    29  	Registry = "registry.datica.com"
    30  	Image    = "hello-world"
    31  	Tag      = "latest"
    32  )
    33  
    34  var testDigest = &models.ContentDigest{
    35  	HashType: "sha256",
    36  	Hash:     "8072a54ebb3bc136150e2f2860f00a7bf45f13eeb917cca2430fcd0054c8e51b",
    37  	Size:     524,
    38  }
    39  
    40  var registries = map[string]string{}
    41  
    42  var notaryServers = map[string]string{}
    43  
    44  var localImages = []string{}
    45  
    46  var remoteImages = []string{}
    47  
    48  // AddRegistry adds a registry to the map
    49  func AddRegistry(pod, registry string) {
    50  	registries[pod] = registry
    51  }
    52  
    53  // AddNotary adds a notary server to the map
    54  func AddNotary(pod, notary string) {
    55  	notaryServers[pod] = notary
    56  }
    57  
    58  // SetLocalImages sets the mock list of local images
    59  func SetLocalImages(images []string) {
    60  	localImages = images
    61  }
    62  
    63  // SetRemoteImages sets the mock list of remote images
    64  func SetRemoteImages(images []string) {
    65  	remoteImages = images
    66  }
    67  
    68  // Errors for image handling
    69  const (
    70  	InvalidImageName             = "Invalid image name"
    71  	IncorrectNamespace           = "Incorrect namespace for your environment"
    72  	IncorrectRegistryOrNamespace = "Incorrect registry or namespace for your environment"
    73  	MissingTrustData             = "does not have trust data for"
    74  	ImageDoesNotExist            = "No such image"
    75  )
    76  
    77  // Constants for image handling
    78  const (
    79  	DefaultTag = "latest"
    80  	trustPath  = ".docker/trust"
    81  )
    82  
    83  // Push parses a given name into registry/namespace/image:tag and attempts to push it to the remote registry
    84  func (d *FakeImages) Push(name string, user *models.User, env *models.Environment, ip prompts.IPrompts) (*models.Image, error) {
    85  	repositoryName, tag, err := d.GetGloballyUniqueNamespace(name, env, true)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	if tag == "" {
    91  		tag = DefaultTag
    92  	}
    93  	fullImageName := strings.Join([]string{repositoryName, tag}, ":")
    94  
    95  	if !imageExists(name, localImages) {
    96  		imageError := fmt.Errorf(ImageDoesNotExist)
    97  		if fullImageName != name && imageExists(fullImageName, localImages) {
    98  			if yesNo := ip.YesNo(imageError.Error(), fmt.Sprintf("Would you like to push %s instead? (y/n) ", fullImageName)); yesNo != nil {
    99  				return nil, imageError
   100  			}
   101  		} else {
   102  			return nil, imageError
   103  		}
   104  	}
   105  
   106  	return &models.Image{
   107  		Name:   repositoryName,
   108  		Tag:    tag,
   109  		Digest: testDigest,
   110  	}, nil
   111  }
   112  
   113  // Pull parses a name into registry/namespace/image:tag and attempts to retrieve it from the remote registry
   114  func (d *FakeImages) Pull(name string, target *images.Target, user *models.User, env *models.Environment) error {
   115  	if !imageExists(fmt.Sprintf("%s:%s", name, target.Name), remoteImages) {
   116  		return fmt.Errorf(ImageDoesNotExist)
   117  	}
   118  	return nil
   119  }
   120  
   121  // InitNotaryRepo intializes a notary repository
   122  func (d *FakeImages) InitNotaryRepo(repo notaryClient.Repository, rootKeyPath string) error {
   123  	rootTrustDir := fmt.Sprintf("%s/%s", userHomeDir(), trustPath)
   124  	if err := os.MkdirAll(rootTrustDir, 0700); err != nil {
   125  		return err
   126  	}
   127  
   128  	if err := repo.Initialize(nil); err != nil {
   129  		return err
   130  	}
   131  	return nil
   132  }
   133  
   134  // AddTargetHash adds the given content hash to a notary repo and sends a signing request to the server
   135  func (d *FakeImages) AddTargetHash(repo notaryClient.Repository, digest *models.ContentDigest, tag string, publish bool) error {
   136  	targetHash := data.Hashes{}
   137  	sha256, err := hex.DecodeString(digest.Hash)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	targetHash[digest.HashType] = sha256
   142  
   143  	// var targetCustom *canonicalJson.RawMessage
   144  	target := &notaryClient.Target{Name: tag, Hashes: targetHash, Length: digest.Size}
   145  	if err = repo.AddTarget(target, data.CanonicalTargetsRole); err != nil {
   146  		return err
   147  	}
   148  	if publish {
   149  		return d.Publish(repo)
   150  	}
   151  	return nil
   152  }
   153  
   154  // ListTargets intializes a notary repository
   155  func (d *FakeImages) ListTargets(repo notaryClient.Repository, roles ...string) ([]*images.Target, error) {
   156  	target, _ := d.LookupTarget(repo, Tag)
   157  	targets := []*images.Target{target}
   158  	return targets, nil
   159  }
   160  
   161  // LookupTarget searches for a specific target in a repository by tag name
   162  func (d *FakeImages) LookupTarget(repo notaryClient.Repository, tag string) (*images.Target, error) {
   163  	target := &images.Target{
   164  		Name:   tag,
   165  		Digest: digest.NewDigestFromHex(testDigest.HashType, testDigest.Hash),
   166  		Size:   testDigest.Size,
   167  		Role:   "targets",
   168  	}
   169  	return target, nil
   170  }
   171  
   172  // DeleteTargets deletes the signed targets for a list of tags
   173  func (d *FakeImages) DeleteTargets(repo notaryClient.Repository, tags []string, publish bool) error {
   174  	return nil
   175  }
   176  
   177  // PrintChangelist prints out the users unpublished changes in a formatted table
   178  func (d *FakeImages) PrintChangelist(changes []changelist.Change) {
   179  	data := [][]string{[]string{"#", "Action", "Scope", "Type", "Target"}, []string{"-", "------", "-----", "----", "------"}}
   180  	data = append(data)
   181  	for i, c := range changes {
   182  		data = append(data, []string{fmt.Sprintf("%d\n", i), c.Action(), c.Scope().String(), c.Type(), c.Path()})
   183  	}
   184  
   185  	logrus.Println()
   186  	table := tablewriter.NewWriter(logrus.StandardLogger().Out)
   187  	table.SetBorder(false)
   188  	table.SetRowLine(false)
   189  	table.SetHeaderLine(false)
   190  	table.SetAlignment(1)
   191  	table.SetCenterSeparator("")
   192  	table.SetColumnSeparator("")
   193  	table.SetRowSeparator("")
   194  	table.AppendBulk(data)
   195  	table.Render()
   196  	logrus.Println()
   197  }
   198  
   199  // CheckChangelist prompts the user if they have unpublished changes to let them clear
   200  //	undesired changes before publishing, or verify that the changes should be published
   201  func (d *FakeImages) CheckChangelist(repo notaryClient.Repository, ip prompts.IPrompts) error {
   202  	if changelist, err := repo.GetChangelist(); err == nil {
   203  		changes := changelist.List()
   204  		if len(changes) > 0 {
   205  			logrus.Println("The following unpublished changes were found in your local trust repository:")
   206  			d.PrintChangelist(changes)
   207  			publishWarning := "These changes will be published along with your current request."
   208  			if err = ip.YesNo(publishWarning, "Would you like to proceed? (y/n) "); err != nil {
   209  				logrus.Println("Use the `datica images targets reset <image>` command to clear undesired changes")
   210  				return err
   211  			}
   212  		}
   213  	}
   214  	return nil
   215  }
   216  
   217  // GetNotaryRepository returns a pointer to the notary repository for an image
   218  func (d *FakeImages) GetNotaryRepository(pod, imageName string, user *models.User) notaryClient.Repository {
   219  	notaryServer := getServer(pod, notaryServers)
   220  	rootTrustDir := fmt.Sprintf("%s/%s", userHomeDir(), trustPath)
   221  	repo, err := notaryClient.NewFileCachedRepository(
   222  		rootTrustDir,
   223  		data.GUN(imageName),
   224  		notaryServer,
   225  		nil,
   226  		nil,
   227  		trustpinning.TrustPinConfig{},
   228  	)
   229  	if err != nil {
   230  		logrus.Fatalln(err)
   231  	}
   232  	return repo
   233  }
   234  
   235  // GetGloballyUniqueNamespace returns the fully formatted name for an image <registry>/<namespace>/<image> and a tag if present
   236  func (d *FakeImages) GetGloballyUniqueNamespace(name string, env *models.Environment, includeRegistry bool) (string, string, error) {
   237  	var repositoryName string
   238  	var image string
   239  	var tag string
   240  
   241  	imageParts := strings.Split(name, ":")
   242  	switch len(imageParts) {
   243  	case 1:
   244  		image = imageParts[0]
   245  	case 2:
   246  		image = imageParts[0]
   247  		tag = imageParts[1]
   248  	case 3:
   249  		image = strings.Join([]string{imageParts[0], imageParts[1]}, ":")
   250  		tag = imageParts[2]
   251  	default:
   252  		return "", "", fmt.Errorf(InvalidImageName)
   253  	}
   254  
   255  	repoParts := strings.Split(image, "/")
   256  	registry := getServer(env.Pod, registries)
   257  
   258  	var repo string
   259  	switch len(repoParts) {
   260  	case 1:
   261  		repo = repoParts[0]
   262  		// repositoryName = fmt.Sprintf("%s/%s/%s", registry, env.Namespace, repoParts[0])
   263  	case 2:
   264  		if repoParts[0] != env.Namespace {
   265  			if repoParts[0] != registry {
   266  				return "", "", fmt.Errorf(IncorrectNamespace)
   267  			}
   268  			return image, tag, nil
   269  		}
   270  		repo = repoParts[1]
   271  	case 3:
   272  		if repoParts[0] != registry || repoParts[1] != env.Namespace {
   273  			return "", "", fmt.Errorf(IncorrectRegistryOrNamespace)
   274  		}
   275  		repo = repoParts[2]
   276  	default:
   277  		return "", "", fmt.Errorf(InvalidImageName)
   278  	}
   279  	if includeRegistry {
   280  		repositoryName = fmt.Sprintf("%s/%s/%s", registry, env.Namespace, repo)
   281  	} else {
   282  		repositoryName = fmt.Sprintf("%s/%s", env.Namespace, repo)
   283  	}
   284  	return repositoryName, tag, nil
   285  }
   286  
   287  // Publish pretends to publish changes. But actually just clear the changes
   288  func (d *FakeImages) Publish(repo notaryClient.Repository) error {
   289  	changelist, err := repo.GetChangelist()
   290  	if err != nil {
   291  		fmt.Println(err.Error())
   292  		return err
   293  	}
   294  	return changelist.Clear("")
   295  }
   296  
   297  // DeleteLocalRepo deletes the local trust repo created in testing
   298  func DeleteLocalRepo(namespace, image, registry, notaryServer string) error {
   299  	repositoryName := strings.Join([]string{registry, namespace, image}, "/")
   300  	rootTrustDir := fmt.Sprintf("%s/%s", userHomeDir(), trustPath)
   301  	if err := notaryClient.DeleteTrustData(
   302  		rootTrustDir,
   303  		data.GUN(repositoryName),
   304  		notaryServer,
   305  		nil,
   306  		false,
   307  	); err != nil {
   308  		return err
   309  	}
   310  
   311  	//Delete parent directories only if empty (just in case)
   312  	repoDir := strings.Join([]string{rootTrustDir, "tuf", registry}, "/")
   313  	namespaceDir := strings.Join([]string{repoDir, namespace}, "/")
   314  	if err := os.Remove(namespaceDir); err != nil {
   315  		fmt.Printf("Unable to remove directory %s:\n		%s\n", namespaceDir, err.Error())
   316  		return nil
   317  	}
   318  	if err := os.Remove(repoDir); err != nil {
   319  		fmt.Printf("Unable to remove directory %s:\n		%s\n", repoDir, err.Error())
   320  	}
   321  	return nil
   322  }
   323  
   324  // Fake image verification
   325  func imageExists(image string, imageList []string) bool {
   326  	for _, img := range imageList {
   327  		if image == img {
   328  			return true
   329  		}
   330  	}
   331  	return false
   332  }
   333  
   334  func getServer(pod string, serverMap map[string]string) string {
   335  	if server, ok := serverMap[pod]; ok {
   336  		return server
   337  	}
   338  	return serverMap["default"]
   339  }
   340  
   341  func userHomeDir() string {
   342  	env := "HOME"
   343  	if runtime.GOOS == "windows" {
   344  		env = "USERPROFILE"
   345  	} else if runtime.GOOS == "plan9" {
   346  		env = "home"
   347  	}
   348  	return os.Getenv(env)
   349  }
   350  
   351  // ListImages stub to make golinter happy
   352  func (d *FakeImages) ListImages() (*[]string, error) {
   353  	return nil, nil
   354  }
   355  
   356  // ListTags stub to make golinter happy
   357  func (d *FakeImages) ListTags(imageName string) (*[]string, error) {
   358  	return nil, nil
   359  }
   360  
   361  // DeleteTag stub to make golinter happy
   362  func (d *FakeImages) DeleteTag(imageName, tagName string) error {
   363  	return nil
   364  }