Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ tags = ["GitLab", "CI", "Docker", "Scaling"]
imageRef = "d5460af5-83f3-47d7-9c4f-80294c66b267" # Flatcar Linux (ID)
image_name = "flatcar" # Resolve imageRef. If set, each time a new VM should be created, the imageRef will be resolved.
flavorRef = "4e9d4fa4-a703-4850-8bc1-58b5e139ab57" # xlarge flavor
flavor_name = "XL" # Resolve flavorRef. If set, each time a new VM should be created, the flavorRef will be resolved.
# key_name = "ci-admin" # SSH public key for worker nodes
networks = [ { uuid = "f05e7f64-9e0f-4c5c-acb0-b636000d7301" } ] # tenant network
security_groups = [ "cee22d91-bb9a-455d-be88-e911d3cb066a" ] # allow SSH ingress from tenant network
Expand Down
25 changes: 25 additions & 0 deletions internal/openstackclient/openstackclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/go-viper/mapstructure/v2"
"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/openstack"
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/v2/openstack/config"
"github.com/gophercloud/gophercloud/v2/openstack/config/clouds"
Expand Down Expand Up @@ -76,6 +77,7 @@ type ImageProperties struct {
type Client interface {
GetImageProperties(ctx context.Context, imageRef string) (*ImageProperties, error)
GetImageByName(ctx context.Context, imageName string) (string, *ImageProperties, error)
GetFlavorByName(ctx context.Context, flavorName string) (string, error)
ShowServerConsoleOutput(ctx context.Context, serverId string) (string, error)
GetServer(ctx context.Context, serverId string) (*servers.Server, error)
ListServers(ctx context.Context) ([]servers.Server, error)
Expand Down Expand Up @@ -284,6 +286,29 @@ func (c *client) GetImageByName(ctx context.Context, imageName string) (string,
return imgs[0].ID, out, nil
}

func (c *client) GetFlavorByName(ctx context.Context, flavorName string) (string, error) {
// Nova API doesn't support filtering by Name, so we have to do that on our end
page, err := flavors.ListDetail(c.compute, nil).AllPages(ctx)
if err != nil {
return "", fmt.Errorf("failed to list flavors: %w", err)
}

flavs, err := flavors.ExtractFlavors(page)
if err != nil {
return "", fmt.Errorf("failed to parse flavors: %w", err)
}

for _, flav := range flavs {
if flav.Name == flavorName {
// Flavor Names are unique per Nova db schema, so we can immediately return on a match
return flav.ID, nil
}
}

err = gophercloud.ErrResourceNotFound{Name: flavorName, ResourceType: "flavor"}
return "", err
}

func (c *client) ShowServerConsoleOutput(ctx context.Context, serverId string) (string, error) {
return servers.ShowConsoleOutput(ctx, c.compute, serverId, servers.ShowConsoleOutputOpts{
Length: 100,
Expand Down
41 changes: 41 additions & 0 deletions internal/openstackclient/openstackclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,44 @@ func TestGetImageByName_Many(t *testing.T) {
_, _, err = client.GetImageByName(ctx, "flatcar")
assert.ErrorIs(err, gophercloud.ErrMultipleResourcesFound{Name: "flatcar", Count: 8, ResourceType: "image"})
}

func TestGetFlavorByName(t *testing.T) {
assert := assert.New(t)

flav, err := os.ReadFile("../../testdata/flavor_list_one.json")
require.NoError(t, err)

testhelper.SetupHTTP()
defer testhelper.TeardownHTTP()

testhelper.ServeFile(t, "", "", "application/json", string(flav))

client := &client{
compute: thclient.ServiceClient(),
}

ctx := context.TODO()
flavorRef, err := client.GetFlavorByName(ctx, "S")
assert.NoError(err)
assert.Equal("113", flavorRef)
}

func TestGetFlavorByName_None(t *testing.T) {
assert := assert.New(t)

flav, err := os.ReadFile("../../testdata/flavor_list_none.json")
require.NoError(t, err)

testhelper.SetupHTTP()
defer testhelper.TeardownHTTP()

testhelper.ServeFile(t, "", "", "application/json", string(flav))

client := &client{
compute: thclient.ServiceClient(),
}

ctx := context.TODO()
_, err = client.GetFlavorByName(ctx, "S")
assert.ErrorIs(err, gophercloud.ErrResourceNotFound{Name: "S", ResourceType: "flavor"})
}
11 changes: 11 additions & 0 deletions provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,17 @@ func (g *InstanceGroup) createInstance(ctx context.Context) (string, error) {
g.log.Debug("Image resolved by name", "image_name", spec.ImageName, "image_ref", spec.ImageRef)
}

if spec.FlavorName != "" {
flavorRef, err := g.client.GetFlavorByName(ctx, spec.FlavorName)
if err != nil {
return "", err
}

spec.FlavorRef = flavorRef

g.log.Debug("Flavor resolved by name", "flavor_name", spec.FlavorName, "image_ref", spec.FlavorRef)
}

srv, err := g.client.CreateServer(ctx, spec, hintOpts)
if err != nil {
return "", err
Expand Down
3 changes: 3 additions & 0 deletions testdata/flavor_list_none.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"flavors": []
}
26 changes: 26 additions & 0 deletions testdata/flavor_list_one.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"flavors": [
{
"id": "113",
"name": "S",
"ram": 2048,
"disk": 15,
"swap": "",
"OS-FLV-EXT-DATA:ephemeral": 0,
"OS-FLV-DISABLED:disabled": false,
"vcpus": 1,
"os-flavor-access:is_public": true,
"rxtx_factor": 1.0,
"links": [
{
"rel": "self",
"href": "https://nova.example.org:8774/v2.1/743713f9aba64e4e927b68cb233d8d27/flavors/113"
},
{
"rel": "bookmark",
"href": "https://nova.example.org:8774/743713f9aba64e4e927b68cb233d8d27/flavors/113"
}
]
}
]
}
3 changes: 3 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ type ExtCreateOpts struct {
// search for imageRef by name each time
ImageName string `json:"image_name,omitempty"`

// search for flavorRef by name each time
FlavorName string `json:"flavor_name,omitempty"`

// annotation overrides
Networks []servers.Network `json:"networks,omitempty"`
SecurityGroups []string `json:"security_groups,omitempty"`
Expand Down