Merge pull request #401 from cyli/list-targets-lists-role

When listing targets, the role the target belongs to is also listed
This commit is contained in:
Diogo Mónica 2016-01-04 19:18:38 -08:00
commit 2dfd22dbef
6 changed files with 153 additions and 62 deletions

View File

@ -127,9 +127,16 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
// Target represents a simplified version of the data TUF operates on, so external
// applications don't have to depend on tuf data types.
type Target struct {
Name string
Hashes data.Hashes
Length int64
Name string // the name of the target
Hashes data.Hashes // the hash of the target
Length int64 // the size in bytes of the target
}
// TargetWithRole represents a Target that exists in a particular role - this is
// produced by ListTargets and GetTargetByName
type TargetWithRole struct {
Target
Role string
}
// NewTarget is a helper method that returns a Target
@ -411,7 +418,7 @@ func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) erro
// its entries will be strictly shadowed by those in other parts of the "targets/a"
// subtree and also the "targets/x" subtree, as we will defer parsing it until
// we explicitly reach it in our iteration of the provided list of roles.
func (r *NotaryRepository) ListTargets(roles ...string) ([]*Target, error) {
func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, error) {
c, err := r.bootstrapClient()
if err != nil {
return nil, err
@ -428,7 +435,7 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*Target, error) {
if len(roles) == 0 {
roles = []string{data.CanonicalTargetsRole}
}
targets := make(map[string]*Target)
targets := make(map[string]*TargetWithRole)
for _, role := range roles {
// we don't need to do anything special with removing role from
// roles because listSubtree always processes role and only excludes
@ -436,7 +443,7 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*Target, error) {
r.listSubtree(targets, role, roles...)
}
var targetList []*Target
var targetList []*TargetWithRole
for _, v := range targets {
targetList = append(targetList, v)
}
@ -444,7 +451,7 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*Target, error) {
return targetList, nil
}
func (r *NotaryRepository) listSubtree(targets map[string]*Target, role string, exclude ...string) {
func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role string, exclude ...string) {
excl := make(map[string]bool)
for _, r := range exclude {
excl[r] = true
@ -460,7 +467,8 @@ func (r *NotaryRepository) listSubtree(targets map[string]*Target, role string,
}
for name, meta := range tgts.Signed.Targets {
if _, ok := targets[name]; !ok {
targets[name] = &Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}
targets[name] = &TargetWithRole{
Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: role}
}
}
for _, d := range tgts.Signed.Delegations.Roles {
@ -478,7 +486,7 @@ func (r *NotaryRepository) listSubtree(targets map[string]*Target, role string,
// the target entry found in the subtree of the highest priority role
// will be returned
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Target, error) {
func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) {
c, err := r.bootstrapClient()
if err != nil {
return nil, err
@ -495,13 +503,11 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe
if len(roles) == 0 {
roles = append(roles, data.CanonicalTargetsRole)
}
var (
meta *data.FileMeta
)
for _, role := range roles {
meta = c.TargetMeta(role, name, roles...)
meta, foundRole := c.TargetMeta(role, name, roles...)
if meta != nil {
return &Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, nil
return &TargetWithRole{
Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: foundRole}, nil
}
}
return nil, fmt.Errorf("No trust data for %s", name)

View File

@ -869,7 +869,7 @@ func fakeServerData(t *testing.T, repo *NotaryRepository, mux *http.ServeMux,
}
// We want to sort by name, so we can guarantee ordering.
type targetSorter []*Target
type targetSorter []*TargetWithRole
func (k targetSorter) Len() int { return len(k) }
func (k targetSorter) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
@ -910,18 +910,25 @@ func testListTarget(t *testing.T, rootType string) {
sort.Stable(targetSorter(targets))
// the targets should both be found in the targets role
for _, foundTarget := range targets {
assert.Equal(t, data.CanonicalTargetsRole, foundTarget.Role)
}
// current should be first
assert.Equal(t, currentTarget, targets[0], "current target does not match")
assert.Equal(t, latestTarget, targets[1], "latest target does not match")
assert.True(t, reflect.DeepEqual(*currentTarget, targets[0].Target), "current target does not match")
assert.True(t, reflect.DeepEqual(*latestTarget, targets[1].Target), "latest target does not match")
// Also test GetTargetByName
newLatestTarget, err := repo.GetTargetByName("latest")
assert.NoError(t, err)
assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match")
assert.Equal(t, data.CanonicalTargetsRole, newLatestTarget.Role)
assert.True(t, reflect.DeepEqual(*latestTarget, newLatestTarget.Target), "latest target does not match")
newCurrentTarget, err := repo.GetTargetByName("current")
assert.NoError(t, err)
assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match")
assert.Equal(t, data.CanonicalTargetsRole, newCurrentTarget.Role)
assert.True(t, reflect.DeepEqual(*currentTarget, newCurrentTarget.Target), "current target does not match")
}
func testListTargetWithDelegates(t *testing.T, rootType string) {
@ -982,48 +989,66 @@ func testListTargetWithDelegates(t *testing.T, rootType string) {
targets, err := repo.ListTargets()
assert.NoError(t, err)
// Should be two targets
// Should be four targets
assert.Len(t, targets, 4, "unexpected number of targets returned by ListTargets")
sort.Stable(targetSorter(targets))
// current should be first.
assert.Equal(t, currentTarget, targets[0], "current target does not match")
assert.Equal(t, latestTarget, targets[1], "latest target does not match")
assert.Equal(t, level2Target, targets[2], "level2 target does not match")
assert.Equal(t, otherTarget, targets[3], "other target does not match")
assert.True(t, reflect.DeepEqual(*currentTarget, targets[0].Target), "current target does not match")
assert.Equal(t, data.CanonicalTargetsRole, targets[0].Role)
assert.True(t, reflect.DeepEqual(*latestTarget, targets[1].Target), "latest target does not match")
assert.Equal(t, data.CanonicalTargetsRole, targets[1].Role)
assert.True(t, reflect.DeepEqual(*level2Target, targets[2].Target), "level2 target does not match")
assert.Equal(t, "targets/level2", targets[2].Role)
assert.True(t, reflect.DeepEqual(*otherTarget, targets[3].Target), "other target does not match")
assert.Equal(t, "targets/level1", targets[3].Role)
// test listing with priority specified
targets, err = repo.ListTargets("targets/level1", data.CanonicalTargetsRole)
assert.NoError(t, err)
// Should be two targets
// Should be four targets
assert.Len(t, targets, 4, "unexpected number of targets returned by ListTargets")
sort.Stable(targetSorter(targets))
// current should be first
assert.Equal(t, delegatedTarget, targets[0], "current target does not match")
assert.Equal(t, latestTarget, targets[1], "latest target does not match")
assert.Equal(t, level2Target, targets[2], "level2 target does not match")
assert.Equal(t, otherTarget, targets[3], "other target does not match")
// current (in delegated role) should be first
assert.True(t, reflect.DeepEqual(*delegatedTarget, targets[0].Target), "current target does not match")
assert.Equal(t, "targets/level1", targets[0].Role)
assert.True(t, reflect.DeepEqual(*latestTarget, targets[1].Target), "latest target does not match")
assert.Equal(t, data.CanonicalTargetsRole, targets[1].Role)
assert.True(t, reflect.DeepEqual(*level2Target, targets[2].Target), "level2 target does not match")
assert.Equal(t, "targets/level2", targets[2].Role)
assert.True(t, reflect.DeepEqual(*otherTarget, targets[3].Target), "other target does not match")
assert.Equal(t, "targets/level1", targets[3].Role)
// Also test GetTargetByName
newLatestTarget, err := repo.GetTargetByName("latest")
assert.NoError(t, err)
assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match")
assert.True(t, reflect.DeepEqual(*latestTarget, newLatestTarget.Target), "latest target does not match")
assert.Equal(t, data.CanonicalTargetsRole, newLatestTarget.Role)
newCurrentTarget, err := repo.GetTargetByName("current", "targets/level1", "targets")
assert.NoError(t, err)
assert.Equal(t, delegatedTarget, newCurrentTarget, "current target does not match")
assert.True(t, reflect.DeepEqual(*delegatedTarget, newCurrentTarget.Target), "current target does not match")
assert.Equal(t, "targets/level1", newCurrentTarget.Role)
newOtherTarget, err := repo.GetTargetByName("other")
assert.NoError(t, err)
assert.True(t, reflect.DeepEqual(otherTarget, newOtherTarget), "other target does not match")
assert.True(t, reflect.DeepEqual(*otherTarget, newOtherTarget.Target), "other target does not match")
assert.Equal(t, "targets/level1", newOtherTarget.Role)
newLevel2Target, err := repo.GetTargetByName("level2")
assert.NoError(t, err)
assert.True(t, reflect.DeepEqual(level2Target, newLevel2Target), "level2 target does not match")
assert.True(t, reflect.DeepEqual(*level2Target, newLevel2Target.Target), "level2 target does not match")
assert.Equal(t, "targets/level2", newLevel2Target.Role)
}
// TestValidateRootKey verifies that the public data in root.json for the root
@ -1270,19 +1295,21 @@ func assertPublishToRolesSucceeds(t *testing.T, repo1 *NotaryRepository,
sort.Stable(targetSorter(targets))
assert.Equal(t, currentTarget, targets[0], "current target does not match")
assert.Equal(t, latestTarget, targets[1], "latest target does not match")
assert.True(t, reflect.DeepEqual(*currentTarget, targets[0].Target), "current target does not match")
assert.Equal(t, role, targets[0].Role)
assert.True(t, reflect.DeepEqual(*latestTarget, targets[1].Target), "latest target does not match")
assert.Equal(t, role, targets[1].Role)
// Also test GetTargetByName
if role == data.CanonicalTargetsRole {
newLatestTarget, err := repo.GetTargetByName("latest")
assert.NoError(t, err)
assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match")
newLatestTarget, err := repo.GetTargetByName("latest", role)
assert.NoError(t, err)
assert.True(t, reflect.DeepEqual(*latestTarget, newLatestTarget.Target), "latest target does not match")
assert.Equal(t, role, newLatestTarget.Role)
newCurrentTarget, err := repo.GetTargetByName("current")
assert.NoError(t, err)
assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match")
}
newCurrentTarget, err := repo.GetTargetByName("current", role)
assert.NoError(t, err)
assert.True(t, reflect.DeepEqual(*currentTarget, newCurrentTarget.Target), "current target does not match")
assert.Equal(t, role, newCurrentTarget.Role)
}
}
}
@ -1320,7 +1347,7 @@ func testPublishAfterPullServerHasSnapshotKey(t *testing.T, rootType string) {
// list, so that the snapshot metadata is pulled from server
targets, err := repo.ListTargets(data.CanonicalTargetsRole)
assert.NoError(t, err)
assert.Equal(t, []*Target{published}, targets)
assert.Equal(t, []*TargetWithRole{{Target: *published, Role: data.CanonicalTargetsRole}}, targets)
// listing downloaded the timestamp and snapshot metadata info
assertRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, true)
assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)

View File

@ -130,7 +130,7 @@ func prettyPrintKeys(keyStores []trustmanager.KeyStore, writer io.Writer) {
// --- pretty printing targets ---
type targetsSorter []*client.Target
type targetsSorter []*client.TargetWithRole
func (t targetsSorter) Len() int { return len(t) }
func (t targetsSorter) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
@ -140,7 +140,7 @@ func (t targetsSorter) Less(i, j int) bool {
// Given a list of KeyStores in order of listing preference, pretty-prints the
// root keys and then the signing keys.
func prettyPrintTargets(ts []*client.Target, writer io.Writer) {
func prettyPrintTargets(ts []*client.TargetWithRole, writer io.Writer) {
if len(ts) == 0 {
writer.Write([]byte("\nNo targets present in this repository.\n\n"))
return
@ -148,13 +148,14 @@ func prettyPrintTargets(ts []*client.Target, writer io.Writer) {
sort.Stable(targetsSorter(ts))
table := getTable([]string{"Name", "Digest", "Size (bytes)"}, writer)
table := getTable([]string{"Name", "Digest", "Size (bytes)", "Role"}, writer)
for _, t := range ts {
table.Append([]string{
t.Name,
hex.EncodeToString(t.Hashes["sha256"]),
fmt.Sprintf("%d", t.Length),
t.Role,
})
}
table.Render()

View File

@ -147,7 +147,7 @@ func TestPrettyPrintRootAndSigningKeys(t *testing.T) {
// are no targets.
func TestPrettyPrintZeroTargets(t *testing.T) {
var b bytes.Buffer
prettyPrintTargets([]*client.Target{}, &b)
prettyPrintTargets([]*client.TargetWithRole{}, &b)
text, err := ioutil.ReadAll(&b)
assert.NoError(t, err)
@ -157,7 +157,7 @@ func TestPrettyPrintZeroTargets(t *testing.T) {
}
// Targets are sorted by name, and the name, SHA256 digest, and size are
// Targets are sorted by name, and the name, SHA256 digest, size, and role are
// printed.
func TestPrettyPrintSortedTargets(t *testing.T) {
hashes := make([][]byte, 3)
@ -166,10 +166,11 @@ func TestPrettyPrintSortedTargets(t *testing.T) {
hashes[i], err = hex.DecodeString(letter)
assert.NoError(t, err)
}
unsorted := []*client.Target{
{Name: "zebra", Hashes: data.Hashes{"sha256": hashes[0]}, Length: 8},
{Name: "abracadabra", Hashes: data.Hashes{"sha256": hashes[1]}, Length: 1},
{Name: "bee", Hashes: data.Hashes{"sha256": hashes[2]}, Length: 5},
unsorted := []*client.TargetWithRole{
{Target: client.Target{Name: "zebra", Hashes: data.Hashes{"sha256": hashes[0]}, Length: 8}, Role: "targets/b"},
{Target: client.Target{Name: "aardvark", Hashes: data.Hashes{"sha256": hashes[1]}, Length: 1},
Role: "targets"},
{Target: client.Target{Name: "bee", Hashes: data.Hashes{"sha256": hashes[2]}, Length: 5}, Role: "targets/a"},
}
var b bytes.Buffer
@ -178,9 +179,9 @@ func TestPrettyPrintSortedTargets(t *testing.T) {
assert.NoError(t, err)
expected := [][]string{
{"abracadabra", "b012", "1"},
{"bee", "c012", "5"},
{"zebra", "a012", "8"},
{"aardvark", "b012", "1", "targets"},
{"bee", "c012", "5", "targets/a"},
{"zebra", "a012", "8", "targets/b"},
}
lines := strings.Split(strings.TrimSpace(string(text)), "\n")
@ -188,7 +189,7 @@ func TestPrettyPrintSortedTargets(t *testing.T) {
// starts with headers
assert.True(t, reflect.DeepEqual(strings.Fields(lines[0]), strings.Fields(
"NAME DIGEST SIZE (BYTES)")))
"NAME DIGEST SIZE (BYTES) ROLE")))
assert.Equal(t, "----", lines[1][:4])
for i, line := range lines[2:] {

View File

@ -525,7 +525,7 @@ func (c Client) RoleTargetsPath(role string, hashSha256 string, consistent bool)
// TargetMeta ensures the repo is up to date. It assumes downloadTargets
// has already downloaded all delegated roles
func (c Client) TargetMeta(role, path string, excludeRoles ...string) *data.FileMeta {
func (c Client) TargetMeta(role, path string, excludeRoles ...string) (*data.FileMeta, string) {
excl := make(map[string]bool)
for _, r := range excludeRoles {
excl[r] = true
@ -548,16 +548,16 @@ func (c Client) TargetMeta(role, path string, excludeRoles ...string) *data.File
meta = c.local.TargetMeta(curr, path)
if meta != nil {
// we found the target!
return meta
return meta, curr
}
delegations := c.local.TargetDelegations(role, path, pathHex)
delegations := c.local.TargetDelegations(curr, path, pathHex)
for _, d := range delegations {
if !excl[d.Name] {
roles = append(roles, d.Name)
}
}
}
return meta
return meta, ""
}
// DownloadTarget downloads the target to dst from the remote

View File

@ -3,6 +3,7 @@ package client
import (
"crypto/sha256"
"encoding/json"
"strconv"
"testing"
"time"
@ -660,3 +661,58 @@ func TestDownloadSnapshotBadChecksum(t *testing.T) {
err = client.downloadSnapshot()
assert.IsType(t, ErrChecksumMismatch{}, err)
}
// TargetMeta returns the file metadata for a file path in the role subtree,
// if it exists. It also returns the role in that subtree in which the target
// was found. If the path doesn't exist in that role subtree, returns
// nil and an empty string.
func TestTargetMeta(t *testing.T) {
kdb, repo, cs := testutils.EmptyRepo()
localStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, nil, kdb, localStorage)
delegations := []string{
"targets/level1",
"targets/level1/a",
"targets/level1/a/i",
}
k, err := cs.Create("", data.ED25519Key)
assert.NoError(t, err)
hash := sha256.Sum256([]byte{})
f := data.FileMeta{
Length: 1,
Hashes: map[string][]byte{
"sha256": hash[:],
},
}
for i, r := range delegations {
// create role
role, err := data.NewRole(r, 1, []string{k.ID()}, []string{""}, nil)
assert.NoError(t, err)
// add role to repo
repo.UpdateDelegations(role, []data.PublicKey{k})
repo.InitTargets(r)
// add a target to the role
_, err = repo.AddTargets(r, data.Files{strconv.Itoa(i): f})
assert.NoError(t, err)
}
// returns the right level
fileMeta, role := client.TargetMeta("targets", "1")
assert.Equal(t, &f, fileMeta)
assert.Equal(t, "targets/level1/a", role)
// looks only in subtree
fileMeta, role = client.TargetMeta("targets/level1/a", "0")
assert.Nil(t, fileMeta)
assert.Equal(t, "", role)
fileMeta, role = client.TargetMeta("targets/level1/a", "2")
assert.Equal(t, &f, fileMeta)
assert.Equal(t, "targets/level1/a/i", role)
}