Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mantle/kola: optionally attach GCP service account/AWS instance profile to instances #2851

Merged
merged 3 commits into from
May 16, 2022
Merged
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 mantle/cmd/kola/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func init() {
sv(&kola.GCEOptions.MachineType, "gce-machinetype", "n1-standard-1", "GCE machine type")
sv(&kola.GCEOptions.DiskType, "gce-disktype", "pd-ssd", "GCE disk type")
sv(&kola.GCEOptions.Network, "gce-network", "default", "GCE network")
sv(&kola.GCEOptions.ServiceAcct, "gce-service-account", "", "GCE service account to attach to instance (default project default)")
bv(&kola.GCEOptions.ServiceAuth, "gce-service-auth", false, "for non-interactive auth when running within GCE")
sv(&kola.GCEOptions.JSONKeyFile, "gce-json-key", "", "use a service account's JSON key for authentication (default \"~/"+auth.GCEConfigPath+"\")")

Expand Down
8 changes: 8 additions & 0 deletions mantle/kola/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,7 @@ type externalTestMeta struct {
TimeoutMin int `json:"timeoutMin"`
Conflicts []string `json:"conflicts"`
AllowConfigWarnings bool `json:"allowConfigWarnings"`
NoInstanceCreds bool `json:"noInstanceCreds"`
}

// metadataFromTestBinary extracts JSON-in-comment like:
Expand Down Expand Up @@ -987,6 +988,9 @@ ExecStart=%s
} else {
t.Distros = strings.Fields(targetMeta.Distros)
}
if targetMeta.NoInstanceCreds {
t.Flags = append(t.Flags, register.NoInstanceCreds)
}
t.Tags = append(t.Tags, strings.Fields(targetMeta.Tags)...)
// TODO validate tags here
t.RequiredTag = targetMeta.RequiredTag
Expand Down Expand Up @@ -1247,6 +1251,9 @@ func makeNonExclusiveTest(bucket int, tests []*register.Test, flight platform.Fl
if test.HasFlag(register.NoSSHKeyInMetadata) || test.HasFlag(register.NoSSHKeyInUserData) {
plog.Fatalf("Non-exclusive test %v cannot have NoSSHKeyIn* flag", test.Name)
}
if test.HasFlag(register.NoInstanceCreds) {
plog.Fatalf("Non-exclusive test %v cannot have NoInstanceCreds flag", test.Name)
}
if test.HasFlag(register.AllowConfigWarnings) {
plog.Fatalf("Non-exclusive test %v cannot have AllowConfigWarnings flag", test.Name)
}
Expand Down Expand Up @@ -1333,6 +1340,7 @@ func runTest(h *harness.H, t *register.Test, pltfrm string, flight platform.Flig
OutputDir: h.OutputDir(),
NoSSHKeyInUserData: t.HasFlag(register.NoSSHKeyInUserData),
NoSSHKeyInMetadata: t.HasFlag(register.NoSSHKeyInMetadata),
NoInstanceCreds: t.HasFlag(register.NoInstanceCreds),
WarningsAction: conf.FailWarnings,
InternetAccess: testRequiresInternet(t),
}
Expand Down
1 change: 1 addition & 0 deletions mantle/kola/register/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Flag int
const (
NoSSHKeyInUserData Flag = iota // don't inject SSH key into Ignition/cloud-config
NoSSHKeyInMetadata // don't add SSH key to platform metadata
NoInstanceCreds // don't grant credentials (AWS instance profile, GCP service account) to the instance
NoEmergencyShellCheck // don't check console output for emergency shell invocation
RequiresInternetAccess // run the test only if the platform supports Internet access
AllowConfigWarnings // ignore Ignition and Butane warnings instead of failing
Expand Down
34 changes: 19 additions & 15 deletions mantle/platform/api/aws/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (a *API) DeleteKey(name string) error {
}

// CreateInstances creates EC2 instances with a given name tag, optional ssh key name, user data. The image ID, instance type, and security group set in the API will be used. CreateInstances will block until all instances are running and have an IP address.
func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minDiskSize int64) ([]*ec2.Instance, error) {
func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minDiskSize int64, useInstanceProfile bool) ([]*ec2.Instance, error) {
cnt := int64(count)

var ud *string
Expand All @@ -91,9 +91,11 @@ func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minD
ud = &tud
}

err := a.ensureInstanceProfile(a.opts.IAMInstanceProfile)
if err != nil {
return nil, fmt.Errorf("error verifying IAM instance profile: %v", err)
if useInstanceProfile {
err := a.ensureInstanceProfile(a.opts.IAMInstanceProfile)
if err != nil {
return nil, fmt.Errorf("error verifying IAM instance profile: %v", err)
}
}

sgId, err := a.getSecurityGroupID(a.opts.SecurityGroup)
Expand Down Expand Up @@ -131,17 +133,14 @@ func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minD
})
}
inst := ec2.RunInstancesInput{
ImageId: &a.opts.AMI,
MinCount: &cnt,
MaxCount: &cnt,
KeyName: key,
InstanceType: &a.opts.InstanceType,
SecurityGroupIds: []*string{&sgId},
SubnetId: &subnetId,
UserData: ud,
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{
Name: &a.opts.IAMInstanceProfile,
},
ImageId: &a.opts.AMI,
MinCount: &cnt,
MaxCount: &cnt,
KeyName: key,
InstanceType: &a.opts.InstanceType,
SecurityGroupIds: []*string{&sgId},
SubnetId: &subnetId,
UserData: ud,
BlockDeviceMappings: rootBlockDev,
TagSpecifications: []*ec2.TagSpecification{
&ec2.TagSpecification{
Expand All @@ -159,6 +158,11 @@ func (a *API) CreateInstances(name, keyname, userdata string, count uint64, minD
},
},
}
if useInstanceProfile {
inst.IamInstanceProfile = &ec2.IamInstanceProfileSpecification{
Name: &a.opts.IAMInstanceProfile,
}
}

var reservations *ec2.Reservation
err = util.RetryConditional(5, 5*time.Second, func(err error) bool {
Expand Down
9 changes: 9 additions & 0 deletions mantle/platform/api/gcloud/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Options struct {
MachineType string
DiskType string
Network string
ServiceAcct string
JSONKeyFile string
ServiceAuth bool
*platform.Options
Expand Down Expand Up @@ -81,6 +82,14 @@ func New(opts *Options) (*API, error) {
return nil, err
}

if opts.ServiceAcct == "" {
proj, err := computeService.Projects.Get(opts.Project).Do()
if err != nil {
return nil, err
}
opts.ServiceAcct = proj.DefaultServiceAccount
}

api := &API{
client: client,
compute: computeService,
Expand Down
15 changes: 12 additions & 3 deletions mantle/platform/api/gcloud/compute.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (a *API) vmname() string {
}

// Taken from: https://github.com/golang/build/blob/master/buildlet/gce.go
func (a *API) mkinstance(userdata, name string, keys []*agent.Key) *compute.Instance {
func (a *API) mkinstance(userdata, name string, keys []*agent.Key, useServiceAcct bool) *compute.Instance {
mantle := "mantle"
metadataItems := []*compute.MetadataItems{
&compute.MetadataItems{
Expand Down Expand Up @@ -95,6 +95,15 @@ func (a *API) mkinstance(userdata, name string, keys []*agent.Key) *compute.Inst
},
},
}
if useServiceAcct {
// allow the instance to perform authenticated GCS fetches
instance.ServiceAccounts = []*compute.ServiceAccount{
&compute.ServiceAccount{
Email: a.options.ServiceAcct,
Scopes: []string{"https://www.googleapis.com/auth/devstorage.read_only"},
},
}
}
// add cloud config
if userdata != "" {
instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{
Expand All @@ -108,9 +117,9 @@ func (a *API) mkinstance(userdata, name string, keys []*agent.Key) *compute.Inst
}

// CreateInstance creates a Google Compute Engine instance.
func (a *API) CreateInstance(userdata string, keys []*agent.Key) (*compute.Instance, error) {
func (a *API) CreateInstance(userdata string, keys []*agent.Key, useServiceAcct bool) (*compute.Instance, error) {
name := a.vmname()
inst := a.mkinstance(userdata, name, keys)
inst := a.mkinstance(userdata, name, keys, useServiceAcct)

plog.Debugf("Creating instance %q", name)

Expand Down
6 changes: 5 additions & 1 deletion mantle/platform/api/gcloud/pending.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ func (p *Pending) Wait() error {
}
if op.Error != nil {
if len(op.Error.Errors) > 0 {
return fmt.Errorf("Operation %q failed: %+v", p.desc, op.Error.Errors)
var messages []string
for _, err := range op.Error.Errors {
messages = append(messages, err.Message)
}
return fmt.Errorf("Operation %q failed: %+v", p.desc, messages)
}
return fmt.Errorf("Operation %q failed to start", p.desc)
}
Expand Down
2 changes: 1 addition & 1 deletion mantle/platform/machine/aws/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (ac *cluster) NewMachineWithOptions(userdata *conf.UserData, options platfo
fmt.Printf("WARNING: compressed userdata exceeds expected limit of %d\n", MaxUserDataSize)
}
}
instances, err := ac.flight.api.CreateInstances(ac.Name(), keyname, ud, 1, int64(options.MinDiskSize))
instances, err := ac.flight.api.CreateInstances(ac.Name(), keyname, ud, 1, int64(options.MinDiskSize), !ac.RuntimeConf().NoInstanceCreds)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion mantle/platform/machine/gcloud/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (gc *cluster) NewMachineWithOptions(userdata *conf.UserData, options platfo
}
}

instance, err := gc.flight.api.CreateInstance(conf.String(), keys)
instance, err := gc.flight.api.CreateInstance(conf.String(), keys, !gc.RuntimeConf().NoInstanceCreds)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions mantle/platform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ type RuntimeConfig struct {

NoSSHKeyInUserData bool // don't inject SSH key into Ignition/cloud-config
NoSSHKeyInMetadata bool // don't add SSH key to platform metadata
NoInstanceCreds bool // don't grant credentials (AWS instance profile, GCP service account) to the instance
AllowFailedUnits bool // don't fail CheckMachine if a systemd unit has failed
WarningsAction conf.WarningsAction // what to do on Ignition or Butane validation warnings

Expand Down