// GetSimpleDBDomains returns a slice of SimpleDB Domains that match the provided search term func GetSimpleDBDomains(search string) (*SimpleDBDomains, []error) { var wg sync.WaitGroup var errs []error domainList := new(SimpleDBDomains) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionSimpleDBDomains(region.RegionName, domainList, search) if err != nil { // TODO handle regions without service endpoints that work terminal.ShowErrorMessage(fmt.Sprintf("Error gathering simpledb domain list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return domainList, errs }
// RotateLaunchConfigurations rotates out older Launch Configurations func RotateLaunchConfigurations(class string, cfg config.LaunchConfigurationClass, dryRun bool) error { var wg sync.WaitGroup var errs []error autoScaleGroups, err := GetAutoScaleGroups("") if err != nil { return errors.New("Error while retrieving the list of launch configurations to exclude from rotation!") } excludedConfigs := autoScaleGroups.LockedLaunchConfigurations() regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() // Get all the launch configs of this class in this region launchConfigs, err := GetLaunchConfigurationsByName(*region.RegionName, class) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering launch configuration list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } // Exclude the launch configs being used in Autoscale Groups for i, lc := range launchConfigs { if excludedConfigs[lc.Name] { terminal.Information("Launch Configuration [" + lc.Name + ") ] is being used in an autoscale group, skipping!") launchConfigs = append(launchConfigs[:i], launchConfigs[i+1:]...) } } // Delete the oldest ones if we have more than the retention number if len(launchConfigs) > cfg.Retain { sort.Sort(launchConfigs) // important! ds := launchConfigs[cfg.Retain:] deleteLaunchConfigurations(&ds, dryRun) } }(region) } wg.Wait() if errs != nil { return errors.New("Error rotating snapshots for [" + class + "]!") } return nil }
// rotateSnapshots rotates out older Snapshots func rotateSnapshots(class string, cfg config.SnapshotClass, dryRun bool) error { var wg sync.WaitGroup var errs []error launchConfigs, err := GetLaunchConfigurations("") if err != nil { return errors.New("Error while retrieving the list of assets to exclude from rotation!") } excludedSnaps := launchConfigs.LockedSnapshotIds() regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() // Get all the snapshots of this class in this region snapshots, err := GetSnapshotsByTag(*region.RegionName, "Class", class) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering snapshot list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } // Exclude the snapshots being used in Launch Configurations for i, snap := range snapshots { if excludedSnaps[snap.SnapshotID] { terminal.Information("Snapshot [" + snap.Name + " (" + snap.SnapshotID + ") ] is being used in a launch configuration, skipping!") snapshots = append(snapshots[:i], snapshots[i+1:]...) } } // Delete the oldest ones if we have more than the retention number if len(snapshots) > cfg.Retain { sort.Sort(snapshots) // important! ds := snapshots[cfg.Retain:] deleteSnapshots(&ds, dryRun) } }(region) } wg.Wait() if errs != nil { return errors.New("Error rotating snapshots for [" + class + "]!") } return nil }
// GetIAMUsers returns a list of IAM Users that match the provided search term func GetIAMUsers(search string) (*IAMUsers, error) { svc := iam.New(session.New()) result, err := svc.ListUsers(&iam.ListUsersInput{}) // TODO truncated? if err != nil { terminal.ShowErrorMessage("Error gathering IAM Users list", err.Error()) return &IAMUsers{}, err } iamList := make(IAMUsers, len(result.Users)) for i, user := range result.Users { iamList[i].Marshal(user) } return &iamList, nil }
// PrintTable Prints an ascii table of the list of Application Load Balancers func (i *LoadBalancersV2) PrintTable() { if len(*i) == 0 { terminal.ShowErrorMessage("Warning", "No Application Load Balancers Found!") return } var header []string rows := make([][]string, len(*i)) for index, lb := range *i { models.ExtractAwsmTable(index, lb, &header, &rows) } table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) table.AppendBulk(rows) table.Render() }
// PrintTable Prints an ascii table of the list of SimpleDB Domains func (i *SimpleDBDomains) PrintTable() { if len(*i) == 0 { terminal.ShowErrorMessage("Warning", "No SimpleDB Domains Found!") return } var header []string rows := make([][]string, len(*i)) for index, domain := range *i { models.ExtractAwsmTable(index, domain, &header, &rows) } table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) table.AppendBulk(rows) table.Render() }
// PrintTable Prints an ascii table of the list of AutoScaling Groups func (a *AutoScaleGroups) PrintTable() { if len(*a) == 0 { terminal.ShowErrorMessage("Warning", "No Autoscale Groups Found!") return } var header []string rows := make([][]string, len(*a)) for index, asg := range *a { models.ExtractAwsmTable(index, asg, &header, &rows) } table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) table.AppendBulk(rows) table.Render() }
// PrintTable Prints an ascii table of the list of EBS Snapshots func (s *Snapshots) PrintTable() { if len(*s) == 0 { terminal.ShowErrorMessage("Warning", "No Snapshots Found!") return } var header []string rows := make([][]string, len(*s)) for index, snapshot := range *s { models.ExtractAwsmTable(index, snapshot, &header, &rows) } table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) table.AppendBulk(rows) table.Render() }
// PrintTable Prints an ascii table of the list of EBS Volumes func (v *Volumes) PrintTable() { if len(*v) == 0 { terminal.ShowErrorMessage("Warning", "No Volumes Found!") return } var header []string rows := make([][]string, len(*v)) for index, vol := range *v { models.ExtractAwsmTable(index, vol, &header, &rows) } table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) table.AppendBulk(rows) table.Render() }
// PrintTable Prints an ascii table of the list of Launch Configurations func (l *LaunchConfigs) PrintTable() { if len(*l) == 0 { terminal.ShowErrorMessage("Warning", "No Launch Configurations Found!") return } var header []string rows := make([][]string, len(*l)) for index, lc := range *l { models.ExtractAwsmTable(index, lc, &header, &rows) } table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) table.AppendBulk(rows) table.Render() }
// GetSnapshots returns a slice of EBS Snapshots that match the provided search term and optional completed flag func GetSnapshots(search string, completed bool) (*Snapshots, []error) { var wg sync.WaitGroup var errs []error snapList := new(Snapshots) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionSnapshots(*region.RegionName, snapList, search, completed) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering snapshot list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return snapList, errs }
// GetLoadBalancersV2 returns a slice of Application Load Balancers func GetLoadBalancersV2() (*LoadBalancersV2, []error) { var wg sync.WaitGroup var errs []error lbList := new(LoadBalancersV2) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionLoadBalancersV2(*region.RegionName, lbList) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering loadbalancer list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return lbList, errs }
// GetVolumes returns a slice of Volumes that match the provided search term and optional available flag func GetVolumes(search string, available bool) (*Volumes, []error) { var wg sync.WaitGroup var errs []error volList := new(Volumes) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionVolumes(*region.RegionName, volList, search, available) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering volume list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return volList, errs }
// GetAutoScaleGroups returns a slice of AutoScale Groups based on the given search term func GetAutoScaleGroups(search string) (*AutoScaleGroups, []error) { var wg sync.WaitGroup var errs []error asgList := new(AutoScaleGroups) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionAutoScaleGroups(*region.RegionName, asgList, search) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering autoscale group list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return asgList, errs }
// GetAlarms returns a slice of CloudWatch Alarms func GetAlarms() (*Alarms, []error) { var wg sync.WaitGroup var errs []error alList := new(Alarms) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionAlarms(*region.RegionName, alList) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering alarm list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return alList, errs }
// GetScalingPolicies returns a slice of Scaling Policies func GetScalingPolicies() (*ScalingPolicies, []error) { var wg sync.WaitGroup var errs []error spList := new(ScalingPolicies) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionScalingPolicies(*region.RegionName, spList) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering scaling policy list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return spList, errs }
// GetLaunchConfigurations returns a slice of Launch Configurations that match the provided search term func GetLaunchConfigurations(search string) (*LaunchConfigs, []error) { var wg sync.WaitGroup var errs []error lcList := new(LaunchConfigs) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionLaunchConfigurations(*region.RegionName, lcList, search) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering launch config list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return lcList, errs }
// GetInstances returns a list of EC2 Instances that match the provided search term and optional running flag func GetInstances(search string, running bool) (*Instances, []error) { var wg sync.WaitGroup var errs []error instList := new(Instances) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionInstances(*region.RegionName, instList, search, running) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering instance list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return instList, errs }
// GetKeyPairs returns a slice of KeyPairs that match the provided search term func GetKeyPairs(search string) (*KeyPairs, []error) { var wg sync.WaitGroup var errs []error keyList := new(KeyPairs) regions := regions.GetRegionList() for _, region := range regions { wg.Add(1) go func(region *ec2.Region) { defer wg.Done() err := GetRegionKeyPairs(*region.RegionName, keyList, search) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error gathering key pair list for region [%s]", *region.RegionName), err.Error()) errs = append(errs, err) } }(region) } wg.Wait() return keyList, errs }
// CreateSnapshot creates a new EBS Snapshot func CreateSnapshot(search, class, name string, dryRun bool) error { // --dry-run flag if dryRun { terminal.Information("--dry-run flag is set, not making any actual changes!") } // Locate the Volume volumes, _ := GetVolumes(search, false) if len(*volumes) == 0 { return errors.New("No volumes found for your search terms.") } if len(*volumes) > 1 { volumes.PrintTable() return errors.New("Please limit your search to return only one volume.") } volume := (*volumes)[0] region := volume.Region // Class Config cfg, err := config.LoadSnapshotClass(class) if err != nil { return err } terminal.Information("Found Snapshot Class Configuration for [" + class + "]!") // Create the snapshot createSnapshotResp, err := createSnapshot(volume.VolumeID, region, dryRun) if err != nil { return err } terminal.Information("Created Snapshot [" + *createSnapshotResp.SnapshotId + "] named [" + name + "] in [" + region + "]!") // Add Tags err = SetEc2NameAndClassTags(createSnapshotResp.SnapshotId, name, class, region) if err != nil { return err } sourceSnapshot := Snapshot{Name: name, Class: class, SnapshotID: *createSnapshotResp.SnapshotId} // Check for Propagate flag if cfg.Propagate && cfg.PropagateRegions != nil { var wg sync.WaitGroup var errs []error terminal.Information("Propagate flag is set, waiting for initial snapshot to complete.") // Wait for the snapshot to complete. err = waitForSnapshot(*createSnapshotResp.SnapshotId, region, dryRun) if err != nil { return err } // Copy to other regions for _, propRegion := range cfg.PropagateRegions { wg.Add(1) go func(region string) { defer wg.Done() // Copy snapshot to the destination region copySnapResp, err := copySnapshot(sourceSnapshot, propRegion, dryRun) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error propagating snapshot [%s] to region [%s]", sourceSnapshot.SnapshotID, propRegion), err.Error()) errs = append(errs, err) } // Add Tags err = SetEc2NameAndClassTags(copySnapResp.SnapshotId, name, class, propRegion) terminal.Information(fmt.Sprintf("Copied snapshot [%s] to region [%s].", sourceSnapshot.SnapshotID, propRegion)) }(propRegion) } wg.Wait() if errs != nil { return errors.New("Error propagating snapshot to other regions!") } } // Rotate out older snapshots if cfg.Retain > 1 { err := rotateSnapshots(class, cfg, dryRun) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error rotating [%s] snapshots!", sourceSnapshot.Class), err.Error()) return err } } return nil }
// CreateImage creates a new Amazon Machine Image from an instance matching the provided search term. It assigns the Image the class and name that was provided func CreateImage(search, class, name string, dryRun bool) error { // --dry-run flag if dryRun { terminal.Information("--dry-run flag is set, not making any actual changes!") } // Class Config cfg, err := config.LoadImageClass(class) if err != nil { return err } terminal.Information("Found Image Class Configuration for [" + class + "]!") // Locate the Instance instances, _ := GetInstances(search, true) instCount := len(*instances) if instCount == 0 { return errors.New("No running instances found for your search terms.") } if instCount > 1 { instances.PrintTable() return errors.New("Please limit your search to return only one instance.") } instance := (*instances)[0] region := instance.Region createImageResp, err := createImage(instance.InstanceID, name, region, dryRun) if err != nil { return err } terminal.Information("Created Image [" + *createImageResp.ImageId + "] named [" + name + "] in [" + region + "]!") // Add Tags err = SetEc2NameAndClassTags(createImageResp.ImageId, name, class, region) if err != nil { return err } sourceImage := Image{Name: name, Class: class, ImageID: *createImageResp.ImageId, Region: region} // Check for Propagate flag if cfg.Propagate && cfg.PropagateRegions != nil { var wg sync.WaitGroup var errs []error terminal.Information("Propagate flag is set, waiting for initial snapshot to complete...") // Wait for the image to complete. err = waitForImage(*createImageResp.ImageId, region, dryRun) if err != nil { return err } // Copy to other regions for _, propRegion := range cfg.PropagateRegions { wg.Add(1) go func(region string) { defer wg.Done() // Copy image to the destination region copyImageResp, err := copyImage(sourceImage, propRegion, dryRun) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error propagating image [%s] to region [%s]", sourceImage.ImageID, propRegion), err.Error()) errs = append(errs, err) } // Add Tags err = SetEc2NameAndClassTags(copyImageResp.ImageId, name, class, propRegion) terminal.Information(fmt.Sprintf("Copied image [%s] to region [%s].", sourceImage.ImageID, propRegion)) }(propRegion) } wg.Wait() if errs != nil { return errors.New("Error propagating snapshot to other regions!") } } // Rotate out older images if cfg.Retain > 1 { err := rotateImages(class, cfg, dryRun) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error rotating [%s] images!", sourceImage.Class), err.Error()) return err } } return nil }
// CreateLaunchConfigurations creates a new Launch Configuration of a given class func CreateLaunchConfigurations(class string, dryRun bool) (err error) { // Verify the launch config class input cfg, err := config.LoadLaunchConfigurationClass(class) if err != nil { return err } terminal.Information("Found Launch Configuration class configuration for [" + class + "]") // Instance Class Config instanceCfg, err := config.LoadInstanceClass(cfg.InstanceClass) if err != nil { return err } terminal.Information("Found Instance class configuration for [" + cfg.InstanceClass + "]") // Increment the version terminal.Information(fmt.Sprintf("Previous version of launch configuration is [%d]", cfg.Version)) cfg.Increment(class) terminal.Information(fmt.Sprintf("New version of launch configuration is [%d]", cfg.Version)) params := &autoscaling.CreateLaunchConfigurationInput{ LaunchConfigurationName: aws.String(fmt.Sprintf("%s-v%d", class, cfg.Version)), AssociatePublicIpAddress: aws.Bool(instanceCfg.PublicIPAddress), InstanceMonitoring: &autoscaling.InstanceMonitoring{ Enabled: aws.Bool(instanceCfg.Monitoring), }, InstanceType: aws.String(instanceCfg.InstanceType), UserData: aws.String(instanceCfg.UserData), //KernelId: aws.String("XmlStringMaxLen255"), //PlacementTenancy: aws.String("XmlStringMaxLen64"), //RamdiskId: aws.String("XmlStringMaxLen255"), //SpotPrice: aws.String("SpotPrice"), //ClassicLinkVPCId: aws.String("XmlStringMaxLen255"), //ClassicLinkVPCSecurityGroups: []*string{ //aws.String("XmlStringMaxLen255"), //}, } // IAM Profile if len(instanceCfg.IAMUser) > 0 { iam, err := GetIAMUser(instanceCfg.IAMUser) if err != nil { return err } terminal.Information("Found IAM User [" + iam.UserName + "]") params.IamInstanceProfile = aws.String(iam.Arn) } for _, region := range cfg.Regions { if !regions.ValidRegion(region) { return errors.New("Region [" + region + "] is not valid!") } // EBS ebsVolumes := make([]*autoscaling.BlockDeviceMapping, len(instanceCfg.EBSVolumes)) for i, ebsClass := range instanceCfg.EBSVolumes { volCfg, err := config.LoadVolumeClass(ebsClass) if err != nil { return err } terminal.Information("Found Volume Class Configuration for [" + ebsClass + "]") latestSnapshot, err := GetLatestSnapshotByTag(region, "Class", volCfg.Snapshot) if err != nil { return err } terminal.Information("Found Snapshot [" + latestSnapshot.SnapshotID + "] with class [" + latestSnapshot.Class + "] created [" + latestSnapshot.CreatedHuman + "]") ebsVolumes[i] = &autoscaling.BlockDeviceMapping{ DeviceName: aws.String(volCfg.DeviceName), Ebs: &autoscaling.Ebs{ DeleteOnTermination: aws.Bool(volCfg.DeleteOnTermination), SnapshotId: aws.String(latestSnapshot.SnapshotID), VolumeSize: aws.Int64(int64(volCfg.VolumeSize)), VolumeType: aws.String(volCfg.VolumeType), //Encrypted: aws.Bool(volCfg.Encrypted), }, //NoDevice: aws.String("String"), //VirtualName: aws.String("String"), } if volCfg.VolumeType == "io1" { ebsVolumes[i].Ebs.Iops = aws.Int64(int64(volCfg.Iops)) } } // EBS Optimized if instanceCfg.EbsOptimized { terminal.Information("Launching as EBS Optimized") params.EbsOptimized = aws.Bool(instanceCfg.EbsOptimized) } params.BlockDeviceMappings = ebsVolumes // AMI ami, err := GetLatestImageByTag(region, "Class", instanceCfg.AMI) if err != nil { return err } terminal.Information("Found AMI [" + ami.ImageID + "] with class [" + ami.Class + "] created [" + ami.CreatedHuman + "]") params.ImageId = aws.String(ami.ImageID) // KeyPair keyPair, err := GetKeyPairByName(region, instanceCfg.KeyName) if err != nil { return err } terminal.Information("Found KeyPair [" + keyPair.KeyName + "] in [" + keyPair.Region + "]") params.KeyName = aws.String(keyPair.KeyName) // VPC / Subnet var vpc Vpc var subnet Subnet secGroupIds := make([]*string, len(instanceCfg.SecurityGroups)) if instanceCfg.Vpc != "" && instanceCfg.Subnet != "" { // VPC vpc, err = GetVpcByTag(region, "Class", instanceCfg.Vpc) if err != nil { return err } terminal.Information("Found VPC [" + vpc.VpcID + "] in Region [" + region + "]") // Subnet subnet, err = vpc.GetVpcSubnetByTag("Class", instanceCfg.Subnet) if err != nil { return err } terminal.Information("Found Subnet [" + subnet.SubnetID + "] in VPC [" + subnet.VpcID + "]") // VPC Security Groups secGroups, err := vpc.GetVpcSecurityGroupByTagMulti("Class", instanceCfg.SecurityGroups) if err != nil { return err } for i, secGroup := range secGroups { terminal.Information("Found VPC Security Group [" + secGroup.GroupID + "] with name [" + secGroup.Name + "]") secGroupIds[i] = aws.String(secGroup.GroupID) } } else { terminal.Information("No VPC and/or Subnet specified for instance Class [" + class + "]") // EC2-Classic security groups secGroups, err := GetSecurityGroupByTagMulti(region, "Class", instanceCfg.SecurityGroups) if err != nil { return err } for i, secGroup := range secGroups { terminal.Information("Found Security Group [" + secGroup.GroupID + "] with name [" + secGroup.Name + "]") secGroupIds[i] = aws.String(secGroup.GroupID) } } params.SecurityGroups = secGroupIds svc := autoscaling.New(session.New(&aws.Config{Region: aws.String(region)})) _, err = svc.CreateLaunchConfiguration(params) if err != nil { if awsErr, ok := err.(awserr.Error); ok { return errors.New(awsErr.Message()) } return err } } // Rotate out older launch configurations if cfg.Retain > 1 { err := RotateLaunchConfigurations(class, cfg, dryRun) if err != nil { terminal.ShowErrorMessage(fmt.Sprintf("Error rotating [%s] launch configurations!", class), err.Error()) return err } } return nil }