Skip to content
This repository has been archived by the owner on May 3, 2022. It is now read-only.

Commit

Permalink
Update to support outputs in claims. (#815)
Browse files Browse the repository at this point in the history
- Bump to cnab-go released version where outputs are implemented.
- Add --output flag to claim show.
- Run flag validation in PreRunE hook.
- Uses subtests to separate happy path from error cases.

Signed-off-by: Leah Hanson <lhanson@pivotal.io>
  • Loading branch information
astrieanna authored and jeremyrickard committed Aug 21, 2019
1 parent 07948ed commit 313847b
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 36 deletions.
58 changes: 45 additions & 13 deletions cmd/duffle/claims_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"encoding/json"
"errors"
"fmt"
"io"

"github.com/deislabs/cnab-go/claim"
Expand All @@ -15,42 +17,72 @@ Display the content of a claim.
This dumps the entire content of a claim as a JSON object.
`

type claimsShowCmd struct {
Name string
OnlyBundle bool
Output string
Storage claim.Store
}

func newClaimsShowCmd(w io.Writer) *cobra.Command {
var onlyBundle bool
var cmdData claimsShowCmd
cmd := &cobra.Command{
Use: "show NAME",
Short: "show a claim",
Long: claimsShowDesc,
Aliases: []string{"get"},
Args: cobra.ExactArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
return cmdData.validateShowFlags()
},
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
storage := claimStorage()
return displayClaim(name, w, storage, onlyBundle)
cmdData.Name = args[0]
cmdData.Storage = claimStorage()
return cmdData.runClaimShow(w)
},
}

cmd.Flags().BoolVarP(&onlyBundle, "bundle", "b", false, "only show the bundle from the claim")
cmd.Flags().BoolVarP(&cmdData.OnlyBundle, "bundle", "b", false, "only show the bundle from the claim")
cmd.Flags().StringVarP(&cmdData.Output, "output", "o", "", "show the contents of the named output")

return cmd
}

func displayClaim(name string, out io.Writer, storage claim.Store, onlyBundle bool) error {
c, err := storage.Read(name)
func (csc claimsShowCmd) validateShowFlags() error {
if csc.OnlyBundle && csc.Output != "" {
return errors.New("invalid flags: at most one of --bundle and --output can be specified")
}
return nil
}

func (csc claimsShowCmd) runClaimShow(w io.Writer) error {
c, err := csc.Storage.Read(csc.Name)
if err != nil {
return err
}

var data []byte
if onlyBundle {
data, err = json.MarshalIndent(c.Bundle, "", " ")
} else {
data, err = json.MarshalIndent(c, "", " ")
if csc.OnlyBundle {
return displayAsJSON(w, c.Bundle)
}
if err != nil {

if csc.Output != "" {
output, found := c.Outputs[csc.Output]
if !found {
return fmt.Errorf("unknown output name: %s", csc.Output)
}

_, err := fmt.Fprint(w, output)
return err
}

return displayAsJSON(w, c)
}

func displayAsJSON(out io.Writer, v interface{}) error {
data, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
_, err = out.Write(data)
out.Write([]byte("\n"))
return err
Expand Down
187 changes: 164 additions & 23 deletions cmd/duffle/claims_show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"encoding/json"
"errors"
"testing"

"github.com/deislabs/cnab-go/claim"
Expand All @@ -11,50 +12,190 @@ import (
"github.com/stretchr/testify/assert"
)

func TestDisplayClaim(t *testing.T) {
func TestRunClaimShow(t *testing.T) {
var buf bytes.Buffer
storage := mockClaimStore()

storage.Store(claim.Claim{
t.Run("happy path", func(t *testing.T) {
csc := claimsShowCmd{
Name: "myclaim",
OnlyBundle: false,
Storage: mockClaimStore(),
}
expectedClaim := claim.Claim{
Name: "myclaim",
Bundle: &bundle.Bundle{
Name: "mybundle",
Version: "0.1.2",
Outputs: map[string]bundle.Output{
"some-output": {
Path: "/some-output-path",
},
},
},
Outputs: map[string]interface{}{
"some-output": "some output contents",
},
}
csc.Storage.Store(expectedClaim)
err := csc.runClaimShow(&buf)
assert.NoError(t, err)

var got claim.Claim
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatalf("Attempted to unmarshal claim, but got %s", err)
}
assert.Equal(t, expectedClaim, got)
})

t.Run("error case: when storage returns error", func(t *testing.T) {
csc := claimsShowCmd{
Name: "unknownclaim",
OnlyBundle: false,
Storage: mockClaimStore(),
}

err := csc.runClaimShow(&buf)
assert.EqualError(t, err, "not found")
})
}

// happy path: when --bundle is specified
func TestRunClaimShow_Bundle(t *testing.T) {
var buf bytes.Buffer

csc := claimsShowCmd{
Name: "myclaim",
OnlyBundle: true,
Storage: mockClaimStore(),
}
expectedClaim := claim.Claim{
Name: "myclaim",
Bundle: &bundle.Bundle{
Name: "mybundle",
Version: "0.1.2",
Outputs: map[string]bundle.Output{
"some-output": {
Path: "/some-output-path",
},
},
},
})

displayClaim("myclaim", &buf, storage, false)
Outputs: map[string]interface{}{
"some-output": "some output contents",
},
}
csc.Storage.Store(expectedClaim)
err := csc.runClaimShow(&buf)
assert.NoError(t, err)

var got claim.Claim
var got bundle.Bundle
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatal(err)
t.Fatalf("Attempted to unmarshal bundle, but got %s", err)
}

is := assert.New(t)
is.Equal("myclaim", got.Name)
is.Equal("mybundle", got.Bundle.Name)
assert.Equal(t, *expectedClaim.Bundle, got)
}

func TestDisplayClaim_Bundle(t *testing.T) {
func TestRunClaimShow_Output(t *testing.T) {
var buf bytes.Buffer
storage := mockClaimStore()

storage.Store(claim.Claim{
expectedClaim := claim.Claim{
Name: "myclaim",
Bundle: &bundle.Bundle{
Name: "mybundle",
Version: "0.1.2",
Outputs: map[string]bundle.Output{
"some-output": {
Path: "/some-output-path",
},
},
},
Outputs: map[string]interface{}{
"some-output": "some output contents",
},
}

t.Run("happy path", func(t *testing.T) {
csc := claimsShowCmd{
Name: "myclaim",
Output: "some-output",
Storage: mockClaimStore(),
}
csc.Storage.Store(expectedClaim)

err := csc.runClaimShow(&buf)
assert.NoError(t, err)
assert.Equal(t, expectedClaim.Outputs["some-output"], buf.String())
})

displayClaim("myclaim", &buf, storage, true)
t.Run("error case: when --output name is an unknown output", func(t *testing.T) {
csc := claimsShowCmd{
Name: "myclaim",
Output: "not-an-output",
Storage: mockClaimStore(),
}
csc.Storage.Store(expectedClaim)

var got bundle.Bundle
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatal(err)
err := csc.runClaimShow(&buf)
assert.EqualError(t, err, "unknown output name: not-an-output")
})

}

//error case: when --bundle and --output are both specified
func TestRunClaimShow_InvalidFlags(t *testing.T) {
csc := claimsShowCmd{
Name: "myclaim",
Output: "some-output",
OnlyBundle: true,
Storage: mockClaimStore(),
}

is := assert.New(t)
is.Equal("mybundle", got.Name)
is.Equal("0.1.2", got.Version)
err := csc.validateShowFlags()
assert.EqualError(t, err, "invalid flags: at most one of --bundle and --output can be specified")
}

func TestDisplayJSON(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
expectedClaim := claim.Claim{
Name: "myclaim",
Bundle: &bundle.Bundle{
Name: "mybundle",
Version: "0.1.2",
Outputs: map[string]bundle.Output{
"some-output": {
Path: "/some-output-path",
},
},
},
Outputs: map[string]interface{}{
"some-output": "some output contents",
},
}

t.Run("happy path", func(t *testing.T) {
err := displayAsJSON(buf, expectedClaim)
assert.NoError(t, err)

var got claim.Claim
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Fatalf("Attempted to unmarshal claim, but got %s", err)
}
assert.Equal(t, expectedClaim, got)
})

t.Run("error case: can't marshal claim", func(t *testing.T) {
err := displayAsJSON(buf, func() {})
assert.EqualError(t, err, "json: unsupported type: func()")
})

t.Run("error case: can't write to output", func(t *testing.T) {
err := displayAsJSON(mockWriter{err: errors.New("failed to write")}, expectedClaim)
assert.EqualError(t, err, "failed to write")
})
}

type mockWriter struct {
err error
}

func (mw mockWriter) Write([]byte) (int, error) {
return 0, mw.err
}

0 comments on commit 313847b

Please sign in to comment.