Skip to content

Commit

Permalink
adding find func as described in #2
Browse files Browse the repository at this point in the history
  • Loading branch information
matfax committed Jan 6, 2019
1 parent 7f69244 commit f8f1aee
Show file tree
Hide file tree
Showing 8 changed files with 763 additions and 248 deletions.
78 changes: 73 additions & 5 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@

[[constraint]]
name = "github.com/gofunky/automi"
version = "0.3.0"
version = "0.3.2"

81 changes: 67 additions & 14 deletions pkg/tupliplib/lib.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package tupliplib

import (
"errors"
"github.com/deckarep/golang-set"
"github.com/go-ozzo/ozzo-validation"
"github.com/gofunky/automi/emitters"
"github.com/gofunky/automi/stream"
"github.com/nokia/docker-registry-client/registry"
"io"
"strings"
)

// DockerRegistry is the Docker Hub registry URL.
const DockerRegistry = "https://registry-1.docker.io/"

// VersionSeparator is the separator that separates the alias form the semantic version.
const VersionSeparator = ":"

Expand Down Expand Up @@ -35,6 +42,8 @@ type Tuplip struct {
AddLatest bool
// Separator to split the separate tag vector aliases. The default separator is single space.
Separator string
// Docker repository of the root tag vector in the format `organization/repository`.
Repository string
}

// tuplipSource is the intermediarily-built Tuplip stream containing only the source parsing steps.
Expand All @@ -50,25 +59,69 @@ func (t *Tuplip) check() {
}
}

// FromReader builds a tuplip source from a pipe reader.
// FromReader builds a tuplip source from a io.Reader as scanner.
func (t *Tuplip) FromReader(src io.Reader) *tuplipSource {
t.check()
stm := stream.New(emitters.Scanner(src, nil))
stm.FlatMap(t.splitBySeparator)
stm.Filter(t.nonEmpty)
stm.Filter(nonEmpty)
return &tuplipSource{t, stm}
}

// Build builds a tuplip stream from a io.Reader as scanner. The returned stream has no configured sink.
func (s *tuplipSource) Build() *stream.Stream {
stm := s.stream
stm.Map(s.tuplip.splitVersion)
stm.Map(s.tuplip.packInSet)
stm.Reduce(mapset.NewSet(), s.tuplip.mergeSets)
stm.Map(s.tuplip.power)
stm.Map(s.tuplip.addLatestTag)
stm.FlatMap(s.tuplip.failOnEmpty)
stm.FlatMap(s.tuplip.join)
stm.Filter(s.tuplip.nonEmpty)
return stm
// Build defines a tuplip stream that builds a complete set of Docker tags. The returned stream has no configured sink.
func (s *tuplipSource) Build() (stream *stream.Stream) {
stream = s.stream
stream.Map(s.tuplip.splitVersion)
stream.Map(packInSet)
stream.Reduce(mapset.NewSet(), mergeSets)
stream.Map(power)
stream.Map(s.tuplip.addLatestTag)
stream.FlatMap(failOnEmpty)
stream.FlatMap(s.tuplip.join)
stream.Filter(nonEmpty)
return
}

// getTags fetches the set of tags for the given Docker repository.
func (t *Tuplip) getTags() (tagMap map[string]mapset.Set, err error) {
if err = validation.Validate(t.Repository,
validation.Required,
); err != nil {
return nil, err
}
if hub, err := registry.New(DockerRegistry, "", ""); err != nil {
return nil, err
} else {
if tags, err := hub.Tags(t.Repository); err != nil {
return nil, err
} else {
if len(tags) == 0 {
return nil, errors.New("no Docker tags could be found on the given remote")
}
tagMap := make(map[string]mapset.Set)
for _, tag := range tags {
tagVectors := strings.Split(tag, DockerTagSeparator)
vectorSet := mapset.NewSet()
for _, v := range tagVectors {
vectorSet.Add(v)
}
tagMap[tag] = vectorSet
}
return tagMap, nil
}
}
}

// Find defines a tuplip stream that finds the matching Docker tag in the Docker Hub.
// The returned stream has no configured sink.
func (s *tuplipSource) Find() (stream *stream.Stream, err error) {
stream = s.stream
if tagMap, err := s.tuplip.getTags(); err != nil {
return nil, err
} else {
stream.Map(s.tuplip.splitVersion)
stream.Reduce(tagMap, removeCommon)
stream.Map(keyForSmallest)
}
return
}
130 changes: 129 additions & 1 deletion pkg/tupliplib/lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func TestTuplipStream_BuildFromReader(t *testing.T) {
select {
case gotErr := <-tStream.Open():
if (gotErr != nil) != tt.wantErr {
t.Errorf("Tuplip.Open() error = %v, wantErr %v", gotErr, tt.wantErr)
t.Errorf("Tuplip.Build() error = %v, wantErr %v", gotErr, tt.wantErr)
return
}
case <-time.After(500 * time.Millisecond):
Expand All @@ -119,9 +119,137 @@ func TestTuplipStream_BuildFromReader(t *testing.T) {
wantSet.Add(w)
}
if !gotOutput.Equal(wantSet) {
t.Errorf("Tuplip.Build() = %v, want %v, difference %v",
gotOutput, wantSet, gotOutput.Difference(wantSet))
}
})
}
}

func TestTuplipStream_FindFromReader(t *testing.T) {
type args struct {
input []string
}
tests := []struct {
name string
t Tuplip
args args
want string
wantErr bool
}{
{
name: "Empty Repository",
args: args{[]string{""}},
wantErr: true,
},
{
name: "Input No Match",
t: Tuplip{Repository: "gofunky/git"},
args: args{[]string{"unknown"}},
wantErr: true,
},
{
name: "Simple Unary Tag",
t: Tuplip{Repository: "gofunky/git"},
args: args{[]string{"envload"}},
want: "envload",
},
{
name: "Simple Binary Tag",
t: Tuplip{Repository: "gofunky/git"},
args: args{[]string{"envload", "alpine:3.8"}},
want: "alpine3.8-envload",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
src := strings.NewReader(strings.Join(tt.args.input, " "))
tuplipSrc := tt.t.FromReader(src)
tStream, srcErr := tuplipSrc.Find()
collector := collectors.Slice()
var gotErr error
if tStream != nil {
tStream.Into(collector)
select {
case gotErr = <-tStream.Open():

case <-time.After(500 * time.Millisecond):
t.Fatal("Waited too long ...")
return
}
}
if (gotErr != nil || srcErr != nil) != tt.wantErr {
t.Errorf("Tuplip.Open() error = %v, wantErr %v", gotErr, tt.wantErr)
return
}
gotOutput := mapset.NewSetFromSlice(collector.Get())
wantSet := mapset.NewSet(tt.want)
if !tt.wantErr && !gotOutput.Equal(wantSet) {
t.Errorf("Tuplip.Open() = %v, want %v, difference %v",
gotOutput, wantSet, gotOutput.Difference(wantSet))
}
})
}
}

func TestTuplip_getTags(t *testing.T) {
type fields struct {
ExcludeMajor bool
ExcludeMinor bool
ExcludeBase bool
AddLatest bool
Separator string
Repository string
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "Unknown Repository",
fields: fields{Repository: "gofunky/unknown"},
wantErr: true,
},
{
name: "Invalid Repository Name",
fields: fields{Repository: "$%"},
wantErr: true,
},
{
name: "Empty Repository Name",
fields: fields{Repository: ""},
wantErr: true,
},
{
name: "Unary Repository",
fields: fields{Repository: "alpine"},
wantErr: true, // TODO: find a way to check official images
},
{
name: "Binary Repository",
fields: fields{Repository: "gofunky/git"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tu := &Tuplip{
ExcludeMajor: tt.fields.ExcludeMajor,
ExcludeMinor: tt.fields.ExcludeMinor,
ExcludeBase: tt.fields.ExcludeBase,
AddLatest: tt.fields.AddLatest,
Separator: tt.fields.Separator,
Repository: tt.fields.Repository,
}
tagSet, err := tu.getTags()
if (err != nil) != tt.wantErr {
t.Errorf("Tuplip.getTags() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && len(tagSet) == 0 {
t.Errorf("Tuplip.getTags() returned no tags")
return
}
})
}
}
Loading

0 comments on commit f8f1aee

Please sign in to comment.