Skip to content

Commit

Permalink
Merge pull request #2512 from wking/aws-metadata-caching
Browse files Browse the repository at this point in the history
pkg/asset/installconfig/aws/metadata: Store AWS metadata
  • Loading branch information
openshift-merge-robot authored Oct 15, 2019
2 parents b48b2e0 + 1b4c1bf commit 0626e59
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 170 deletions.
41 changes: 41 additions & 0 deletions pkg/asset/installconfig/aws/availabilityzones.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package aws

import (
"context"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/pkg/errors"
)

// availabilityZones retrieves a list of availability zones for the given region.
func availabilityZones(ctx context.Context, session *session.Session, region string) ([]string, error) {
client := ec2.New(session, aws.NewConfig().WithRegion(region))
resp, err := client.DescribeAvailabilityZonesWithContext(ctx, &ec2.DescribeAvailabilityZonesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("region-name"),
Values: []*string{aws.String(region)},
},
{
Name: aws.String("state"),
Values: []*string{aws.String("available")},
},
},
})
if err != nil {
return nil, errors.Wrap(err, "fetching availability zones")
}

zones := []string{}
for _, zone := range resp.AvailabilityZones {
zones = append(zones, *zone.ZoneName)
}

if len(zones) == 0 {
return nil, errors.Errorf("no available zones in %s", region)
}

return zones, nil
}
2 changes: 2 additions & 0 deletions pkg/asset/installconfig/aws/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package aws collects AWS-specific configuration.
package aws
65 changes: 65 additions & 0 deletions pkg/asset/installconfig/aws/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package aws

import (
"context"
"sync"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/pkg/errors"
)

// Metadata holds additional metadata for InstallConfig resources that
// does not need to be user-supplied (e.g. because it can be retrieved
// from external APIs).
type Metadata struct {
session *session.Session
availabilityZones []string
region string
mutex sync.Mutex
}

// NewMetadata initializes a new Metadata object.
func NewMetadata(region string) *Metadata {
return &Metadata{region: region}
}

// Session holds an AWS session which can be used for AWS API calls
// during asset generation.
func (m *Metadata) Session(ctx context.Context) (*session.Session, error) {
m.mutex.Lock()
defer m.mutex.Unlock()

return m.unlockedSession(ctx)
}

func (m *Metadata) unlockedSession(ctx context.Context) (*session.Session, error) {
if m.session == nil {
var err error
m.session, err = GetSession()
if err != nil {
return nil, errors.Wrap(err, "creating AWS session")
}
}

return m.session, nil
}

// AvailabilityZones retrieves a list of availability zones for the configured region.
func (m *Metadata) AvailabilityZones(ctx context.Context) ([]string, error) {
m.mutex.Lock()
defer m.mutex.Unlock()

if len(m.availabilityZones) == 0 {
session, err := m.unlockedSession(ctx)
if err != nil {
return nil, err
}

m.availabilityZones, err = availabilityZones(ctx, session, m.region)
if err != nil {
return nil, errors.Wrap(err, "creating AWS session")
}
}

return m.availabilityZones, nil
}
78 changes: 78 additions & 0 deletions pkg/asset/installconfig/aws/platform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package aws

import (
"fmt"
"sort"
"strings"

"github.com/openshift/installer/pkg/types/aws"
"github.com/openshift/installer/pkg/types/aws/validation"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
survey "gopkg.in/AlecAivazis/survey.v1"
)

// Platform collects AWS-specific configuration.
func Platform() (*aws.Platform, error) {
longRegions := make([]string, 0, len(validation.Regions))
shortRegions := make([]string, 0, len(validation.Regions))
for id, location := range validation.Regions {
longRegions = append(longRegions, fmt.Sprintf("%s (%s)", id, location))
shortRegions = append(shortRegions, id)
}
regionTransform := survey.TransformString(func(s string) string {
return strings.SplitN(s, " ", 2)[0]
})

defaultRegion := "us-east-1"
_, ok := validation.Regions[defaultRegion]
if !ok {
panic(fmt.Sprintf("installer bug: invalid default AWS region %q", defaultRegion))
}

ssn, err := GetSession()
if err != nil {
return nil, err
}

defaultRegionPointer := ssn.Config.Region
if defaultRegionPointer != nil && *defaultRegionPointer != "" {
_, ok := validation.Regions[*defaultRegionPointer]
if ok {
defaultRegion = *defaultRegionPointer
} else {
logrus.Warnf("Unrecognized AWS region %q, defaulting to %s", *defaultRegionPointer, defaultRegion)
}
}

sort.Strings(longRegions)
sort.Strings(shortRegions)

var region string
err = survey.Ask([]*survey.Question{
{
Prompt: &survey.Select{
Message: "Region",
Help: "The AWS region to be used for installation.",
Default: fmt.Sprintf("%s (%s)", defaultRegion, validation.Regions[defaultRegion]),
Options: longRegions,
},
Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error {
choice := regionTransform(ans).(string)
i := sort.SearchStrings(shortRegions, choice)
if i == len(shortRegions) || shortRegions[i] != choice {
return errors.Errorf("invalid region %q", choice)
}
return nil
}),
Transform: regionTransform,
},
}, &region)
if err != nil {
return nil, err
}

return &aws.Platform{
Region: region,
}, nil
}
Original file line number Diff line number Diff line change
@@ -1,91 +1,20 @@
// Package aws collects AWS-specific configuration.
package aws

import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"

"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/openshift/installer/pkg/types/aws"
"github.com/openshift/installer/pkg/types/aws/validation"
"github.com/openshift/installer/pkg/version"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
survey "gopkg.in/AlecAivazis/survey.v1"
ini "gopkg.in/ini.v1"
)

// Platform collects AWS-specific configuration.
func Platform() (*aws.Platform, error) {
longRegions := make([]string, 0, len(validation.Regions))
shortRegions := make([]string, 0, len(validation.Regions))
for id, location := range validation.Regions {
longRegions = append(longRegions, fmt.Sprintf("%s (%s)", id, location))
shortRegions = append(shortRegions, id)
}
regionTransform := survey.TransformString(func(s string) string {
return strings.SplitN(s, " ", 2)[0]
})

defaultRegion := "us-east-1"
_, ok := validation.Regions[defaultRegion]
if !ok {
panic(fmt.Sprintf("installer bug: invalid default AWS region %q", defaultRegion))
}

ssn, err := GetSession()
if err != nil {
return nil, err
}

defaultRegionPointer := ssn.Config.Region
if defaultRegionPointer != nil && *defaultRegionPointer != "" {
_, ok := validation.Regions[*defaultRegionPointer]
if ok {
defaultRegion = *defaultRegionPointer
} else {
logrus.Warnf("Unrecognized AWS region %q, defaulting to %s", *defaultRegionPointer, defaultRegion)
}
}

sort.Strings(longRegions)
sort.Strings(shortRegions)

var region string
err = survey.Ask([]*survey.Question{
{
Prompt: &survey.Select{
Message: "Region",
Help: "The AWS region to be used for installation.",
Default: fmt.Sprintf("%s (%s)", defaultRegion, validation.Regions[defaultRegion]),
Options: longRegions,
},
Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error {
choice := regionTransform(ans).(string)
i := sort.SearchStrings(shortRegions, choice)
if i == len(shortRegions) || shortRegions[i] != choice {
return errors.Errorf("invalid region %q", choice)
}
return nil
}),
Transform: regionTransform,
},
}, &region)
if err != nil {
return nil, err
}

return &aws.Platform{
Region: region,
}, nil
}

// GetSession returns an AWS session by checking credentials
// and, if no creds are found, asks for them and stores them on disk in a config file
func GetSession() (*session.Session, error) {
Expand Down
55 changes: 21 additions & 34 deletions pkg/asset/installconfig/installconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/openshift/installer/pkg/asset"
"github.com/openshift/installer/pkg/asset/installconfig/aws"
"github.com/openshift/installer/pkg/types"
"github.com/openshift/installer/pkg/types/conversion"
"github.com/openshift/installer/pkg/types/defaults"
Expand All @@ -23,6 +24,7 @@ const (
type InstallConfig struct {
Config *types.InstallConfig `json:"config"`
File *asset.File `json:"file"`
AWS *aws.Metadata
}

var _ asset.WritableAsset = (*InstallConfig)(nil)
Expand Down Expand Up @@ -75,23 +77,7 @@ func (a *InstallConfig) Generate(parents asset.Parents) error {
a.Config.GCP = platform.GCP
a.Config.BareMetal = platform.BareMetal

if err := a.setDefaults(); err != nil {
return errors.Wrap(err, "failed to set defaults for install config")
}

if err := validation.ValidateInstallConfig(a.Config, openstackvalidation.NewValidValuesFetcher()).ToAggregate(); err != nil {
return errors.Wrap(err, "invalid install config")
}

data, err := yaml.Marshal(a.Config)
if err != nil {
return errors.Wrap(err, "failed to Marshal InstallConfig")
}
a.File = &asset.File{
Filename: installConfigFilename,
Data: data,
}
return nil
return a.finish("")
}

// Name returns the human-friendly name of the asset.
Expand Down Expand Up @@ -124,37 +110,38 @@ func (a *InstallConfig) Load(f asset.FileFetcher) (found bool, err error) {
a.Config = config

// Upconvert any deprecated fields
if err := a.convert(); err != nil {
if err := conversion.ConvertInstallConfig(a.Config); err != nil {
return false, errors.Wrap(err, "failed to upconvert install config")
}

if err := a.setDefaults(); err != nil {
return false, errors.Wrap(err, "failed to set defaults for install config")
err = a.finish(installConfigFilename)
if err != nil {
return false, err
}
return true, nil
}

func (a *InstallConfig) finish(filename string) error {
defaults.SetInstallConfigDefaults(a.Config)

if a.Config.AWS != nil {
a.AWS = aws.NewMetadata(a.Config.Platform.AWS.Region)
}

if err := validation.ValidateInstallConfig(a.Config, openstackvalidation.NewValidValuesFetcher()).ToAggregate(); err != nil {
return false, errors.Wrapf(err, "invalid %q file", installConfigFilename)
if filename == "" {
return errors.Wrap(err, "invalid install config")
}
return errors.Wrapf(err, "invalid %q file", filename)
}

data, err := yaml.Marshal(a.Config)
if err != nil {
return false, errors.Wrap(err, "failed to Marshal InstallConfig")
return errors.Wrap(err, "failed to Marshal InstallConfig")
}
a.File = &asset.File{
Filename: installConfigFilename,
Data: data,
}

return true, nil
}

func (a *InstallConfig) setDefaults() error {
defaults.SetInstallConfigDefaults(a.Config)
return nil
}

// convert converts possibly older versions of the install config to
// the current version, relocating deprecated fields.
func (a *InstallConfig) convert() error {
return conversion.ConvertInstallConfig(a.Config)
}
Loading

0 comments on commit 0626e59

Please sign in to comment.