Skip to content

Commit

Permalink
feat: marketplace - add FindLocalImageIDByName (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
DenBeke authored and jerome-quere committed May 14, 2019
1 parent d626dfa commit 29930cd
Show file tree
Hide file tree
Showing 13 changed files with 2,630 additions and 3 deletions.
81 changes: 81 additions & 0 deletions api/marketplace/v1/marketplace_utils.go
@@ -0,0 +1,81 @@
package marketplace

import (
"fmt"

"github.com/scaleway/scaleway-sdk-go/utils"
)

// getLocalImage returns the correct local version of an image matching
// the current zone and the compatible commercial type
func (version *Version) getLocalImage(zone utils.Zone, commercialType string) (*LocalImage, error) {

for _, localImage := range version.LocalImages {

// Check if in correct zone
if localImage.Zone != zone {
continue
}

// Check if compatible with wanted commercial type
for _, compatibleCommercialType := range localImage.CompatibleCommercialTypes {
if compatibleCommercialType == commercialType {
return localImage, nil
}
}
}

return nil, fmt.Errorf("couldn't find compatible local image for this image version (%s)", version.Id)

}

// getLatestVersion returns the current/latests version on an image,
// or an error in case the image doesn't have a public version.
func (image *Image) getLatestVersion() (*Version, error) {

for _, version := range image.Versions {
if version.Id == image.CurrentPublicVersion {
return version, nil
}
}

return nil, fmt.Errorf("latest version could not be found for image %s", image.Name)
}

// FindLocalImageIDByName search for an image with the given name (exact match) in the given region
// it returns the latest version of this specific image.
func (s *Api) FindLocalImageIDByName(imageName string, zone utils.Zone, commercialType string) (string, error) {

listImageRequest := &ListImagesRequest{}
listImageResponse, err := s.ListImages(listImageRequest)
if err != nil {
return "", err
}

// TODO: handle pagination

images := listImageResponse.Images
_ = images

for _, image := range images {

// Match name of the image
if image.Name == imageName {

latestVersion, err := image.getLatestVersion()
if err != nil {
return "", fmt.Errorf("couldn't find a matching image for the given name (%s), zone (%s) and commercial type (%s): %s", imageName, zone, commercialType, err)
}

localImage, err := latestVersion.getLocalImage(zone, commercialType)
if err != nil {
return "", fmt.Errorf("couldn't find a matching image for the given name (%s), zone (%s) and commercial type (%s): %s", imageName, zone, commercialType, err)
}

return localImage.Id, nil
}

}

return "", fmt.Errorf("couldn't find a matching image for the given name (%s), zone (%s) and commercial type (%s)", imageName, zone, commercialType)
}
55 changes: 55 additions & 0 deletions api/marketplace/v1/marketplace_utils_test.go
@@ -0,0 +1,55 @@
package marketplace

import (
"net/http"
"testing"

"github.com/dnaeon/go-vcr/recorder"
"github.com/scaleway/scaleway-sdk-go/internal/testhelpers"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/scaleway-sdk-go/utils"
)

func TestGetImageByName(t *testing.T) {

// Setup recorder and scw client
r, err := recorder.NewAsMode("testdata/go-vcr", recorder.ModeReplaying, nil)
if err != nil {
testhelpers.Ok(t, err)
}
defer r.Stop() // Make sure recorder is stopped once done with it

httpClient := &http.Client{Transport: r}

client, err := scw.NewClient(
scw.WithoutAuth(),
scw.WithHTTPClient(httpClient),
)
if err != nil {
testhelpers.Ok(t, err)
}

t.Run("matching input for GetImageByName", func(t *testing.T) {

// Create SDK objects for Scaleway Instance product
marketplaceAPI := NewApi(client)

imageID, err := marketplaceAPI.FindLocalImageIDByName("Docker", utils.ZoneFrPar1, "C1")
testhelpers.Ok(t, err)

// Docker C1 at par1: 45a7e942-1fb0-48c0-bbf6-0acb9af24604
testhelpers.Equals(t, "45a7e942-1fb0-48c0-bbf6-0acb9af24604", imageID)

})

t.Run("non-matching name for GetImageByName", func(t *testing.T) {

// Create SDK objects for Scaleway Instance product
marketplaceAPI := NewApi(client)

_, err := marketplaceAPI.FindLocalImageIDByName("foo-bar-image", "", "")
testhelpers.Assert(t, err != nil, "Should have error")

})

}
1,679 changes: 1,679 additions & 0 deletions api/marketplace/v1/testdata/go-vcr.yaml

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion go.mod
Expand Up @@ -2,4 +2,7 @@ module github.com/scaleway/scaleway-sdk-go

go 1.12

require gopkg.in/yaml.v2 v2.2.2
require (
github.com/dnaeon/go-vcr v1.0.1
gopkg.in/yaml.v2 v2.2.2
)
3 changes: 3 additions & 0 deletions go.sum
@@ -1,3 +1,6 @@
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
116 changes: 114 additions & 2 deletions utils/locality.go
@@ -1,5 +1,11 @@
package utils

import (
"encoding/json"

"github.com/scaleway/scaleway-sdk-go/logger"
)

// Zone is an availability zone
type Zone string

Expand All @@ -12,6 +18,25 @@ const (
ZoneNlAms1 = Zone("nl-ams-1")
)

var (
// AllZones is an array that list all zones
AllZones = []Zone{
ZoneFrPar1,
ZoneFrPar2,
ZoneNlAms1,
}
)

// Exists checks whether a zone exists
func (zone *Zone) Exists() bool {
for _, z := range AllZones {
if z == *zone {
return true
}
}
return false
}

// Region is a geographical location
type Region string

Expand All @@ -30,9 +55,19 @@ var (
}
)

// Exists checks whether a region exists
func (region *Region) Exists() bool {
for _, r := range AllRegions {
if r == *region {
return true
}
}
return false
}

// GetZones is a function that returns the zones for the specified region
func (r Region) GetZones() []Zone {
switch r {
func (region Region) GetZones() []Zone {
switch region {
case RegionFrPar:
return []Zone{ZoneFrPar1, ZoneFrPar2}
case RegionNlAms:
Expand All @@ -41,3 +76,80 @@ func (r Region) GetZones() []Zone {
return []Zone{}
}
}

// ParseZone parse a string value into a Zone object
func ParseZone(zone string) (Zone, error) {
switch zone {
case "par1":
// would be triggered by API market place
// logger.Warningf("par1 is a deprecated name for zone, use fr-par-1 instead")
return ZoneFrPar1, nil
case "ams1":
// would be triggered by API market place
// logger.Warningf("ams1 is a deprecated name for zone, use nl-ams-1 instead")
return ZoneNlAms1, nil
default:
newZone := Zone(zone)
if !newZone.Exists() {
logger.Warningf("%s is an unknown zone", newZone)
}
return newZone, nil
}
}

// UnmarshalJSON implements the Unmarshaler interface for a Zone.
// this to call ParseZone on the string input and return the correct Zone object.
func (zone *Zone) UnmarshalJSON(input []byte) error {

// parse input value as string
var stringValue string
err := json.Unmarshal(input, &stringValue)
if err != nil {
return err
}

// parse string as Zone
*zone, err = ParseZone(stringValue)
if err != nil {
return err
}
return nil
}

// ParseRegion parse a string value into a Zone object
func ParseRegion(region string) (Region, error) {
switch region {
case "par1":
// would be triggered by API market place
// logger.Warningf("par1 is a deprecated name for region, use fr-par instead")
return RegionFrPar, nil
case "ams1":
// would be triggered by API market place
// logger.Warningf("ams1 is a deprecated name for region, use nl-ams instead")
return RegionNlAms, nil
default:
newRegion := Region(region)
if !newRegion.Exists() {
logger.Warningf("%s is an unknown region", newRegion)
}
return newRegion, nil
}
}

// UnmarshalJSON implements the Unmarshaler interface for a Region.
// this to call ParseRegion on the string input and return the correct Region object.
func (region *Region) UnmarshalJSON(input []byte) error {
// parse input value as string
var stringValue string
err := json.Unmarshal(input, &stringValue)
if err != nil {
return err
}

// parse string as Region
*region, err = ParseRegion(stringValue)
if err != nil {
return err
}
return nil
}
92 changes: 92 additions & 0 deletions utils/locality_test.go
@@ -0,0 +1,92 @@
package utils

import (
"encoding/json"
"testing"

"github.com/scaleway/scaleway-sdk-go/internal/testhelpers"
)

func TestParseZone(t *testing.T) {

tests := []struct {
input string
expected Zone
}{
{
input: "fr-par-1",
expected: ZoneFrPar1,
},
{
input: "par1",
expected: ZoneFrPar1,
},
{
input: "ams1",
expected: ZoneNlAms1,
},
}

for _, test := range tests {
z, err := ParseZone(test.input)
testhelpers.Ok(t, err)
testhelpers.Equals(t, test.expected, z)
}

}

func TestZoneJSONUnmarshall(t *testing.T) {

t.Run("test with zone", func(t *testing.T) {

input := `{"Test": "par1"}`
value := struct{ Test Zone }{}

err := json.Unmarshal([]byte(input), &value)
testhelpers.Ok(t, err)

testhelpers.Equals(t, ZoneFrPar1, value.Test)

})

t.Run("test with region", func(t *testing.T) {

input := `{"Test": "par1"}`
value := struct{ Test Region }{}

err := json.Unmarshal([]byte(input), &value)
testhelpers.Ok(t, err)

testhelpers.Equals(t, RegionFrPar, value.Test)

})

}

func TestParseRegion(t *testing.T) {

tests := []struct {
input string
expected Region
}{
{
input: "fr-par",
expected: RegionFrPar,
},
{
input: "par1",
expected: RegionFrPar,
},
{
input: "ams1",
expected: RegionNlAms,
},
}

for _, test := range tests {
r, err := ParseRegion(test.input)
testhelpers.Ok(t, err)
testhelpers.Equals(t, test.expected, r)
}

}

0 comments on commit 29930cd

Please sign in to comment.