Skip to content

Commit e762b83

Browse files
authored
Merge branch 'master' into support_dataproc_clusters
2 parents ef2cebb + 4fca7cc commit e762b83

21 files changed

+350
-144
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ vet:
5555
GO111MODULE=on go vet -mod=vendor ./...
5656

5757
test:
58-
GO111MODULE=on go test -mod=vendor -timeout 30s -coverprofile coverage -race
58+
GO111MODULE=on go test -mod=vendor -timeout 30s -coverprofile coverage -race ./...
5959

6060
_build: build-darwin build-linux
6161

aws/aws.go

+56-2
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8-
"github.com/aws/aws-sdk-go/service/cloudformation"
9-
"github.com/hortonworks/cloud-haunter/utils"
108
"net/http"
119
"os"
1210
"strings"
1311
"sync"
1412
"time"
1513

14+
"github.com/aws/aws-sdk-go/service/cloudformation"
15+
"github.com/hortonworks/cloud-haunter/utils"
16+
1617
"github.com/aws/aws-sdk-go/aws"
1718
"github.com/aws/aws-sdk-go/aws/session"
1819
"github.com/aws/aws-sdk-go/service/autoscaling"
@@ -454,6 +455,19 @@ func deleteVolumes(ec2Clients map[string]ec2Client, volumes []*types.Disk) []err
454455
if ctx.DryRun {
455456
log.Infof("[AWS] Dry-run set, volume is not deleted: %s:%s, region: %s", vol.Name, vol.ID, region)
456457
} else {
458+
log.Infof("[AWS] Initiate delete volume: %s:%s", vol.Name, vol.ID)
459+
var detachError error
460+
if vol.State == types.InUse {
461+
log.Infof("[AWS] Volume %s:%s is in-use, trying to detach", vol.Name, vol.ID)
462+
if _, detachError = ec2Client.DetachVolume(&ec2.DetachVolumeInput{VolumeId: &vol.ID}); detachError == nil {
463+
detachError = waitForVolumeUnusedState(ec2Client, vol)
464+
}
465+
}
466+
467+
if detachError != nil {
468+
log.Infof("[AWS] Skip volume %s:%s as it can not be detached by [%s].", vol.Name, vol.ID, detachError)
469+
continue
470+
}
457471
log.Infof("[AWS] Delete volume: %s:%s", vol.Name, vol.ID)
458472
if _, err := ec2Client.DeleteVolume(&ec2.DeleteVolumeInput{VolumeId: &vol.ID}); err != nil {
459473
errChan <- err
@@ -475,6 +489,26 @@ func deleteVolumes(ec2Clients map[string]ec2Client, volumes []*types.Disk) []err
475489
return errs
476490
}
477491

492+
func waitForVolumeUnusedState(ec2Client ec2Client, vol *types.Disk) error {
493+
log.Infof("[AWS] Waiting for Volume %s:%s 'available' state...", vol.Name, vol.ID)
494+
//Polling state max 10 times with 1 sec interval
495+
var counter int = 0
496+
d, e := getDisk(ec2Client, vol.ID)
497+
for e == nil && d.State != types.Unused && counter < 10 {
498+
time.Sleep(1 * time.Second)
499+
d, e = getDisk(ec2Client, vol.ID)
500+
counter++
501+
}
502+
if e != nil {
503+
return errors.New(fmt.Sprintf("Detach verification failed: %s", e))
504+
} else if d.State != types.Unused {
505+
return errors.New(fmt.Sprintf("Detach verification failed, disk state is: %s", d.State))
506+
} else {
507+
log.Infof("[AWS] Volume %s:%s is detached so it can be deleted.", vol.Name, vol.ID)
508+
}
509+
return nil
510+
}
511+
478512
func deleteImages(ec2Clients map[string]ec2Client, images []*types.Image) []error {
479513
regionImages := map[string][]*types.Image{}
480514
for _, image := range images {
@@ -535,6 +569,7 @@ type ec2Client interface {
535569
DeleteVolume(input *ec2.DeleteVolumeInput) (*ec2.DeleteVolumeOutput, error)
536570
DescribeImages(input *ec2.DescribeImagesInput) (*ec2.DescribeImagesOutput, error)
537571
DeregisterImage(input *ec2.DeregisterImageInput) (*ec2.DeregisterImageOutput, error)
572+
DetachVolume(input *ec2.DetachVolumeInput) (*ec2.VolumeAttachment, error)
538573
}
539574

540575
type cfClient interface {
@@ -672,6 +707,21 @@ func getImages(ec2Clients map[string]ec2Client) ([]*types.Image, error) {
672707
return images, nil
673708
}
674709

710+
func getDisk(ec2Client ec2Client, volumeId string) (*types.Disk, error) {
711+
result, err := ec2Client.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&volumeId}})
712+
if err != nil {
713+
log.Errorf("[AWS] Failed to fetch the volume, err: %s", err)
714+
return nil, err
715+
}
716+
log.Debugf("[AWS] Processing volumes (%d): [%s]", len(result.Volumes), result.Volumes)
717+
718+
if len(result.Volumes) == 0 {
719+
return nil, errors.New(fmt.Sprintf("Volume not found with id '%s'", volumeId))
720+
}
721+
return newDisk(result.Volumes[0]), nil
722+
723+
}
724+
675725
func getDisks(ec2Clients map[string]ec2Client) ([]*types.Disk, error) {
676726
diskChan := make(chan *types.Disk)
677727
wg := sync.WaitGroup{}
@@ -768,6 +818,7 @@ func getAccesses(iamClient iamClient) ([]*types.Access, error) {
768818
Name: name,
769819
Owner: *akm.UserName,
770820
Created: getCreated(akm.CreateDate),
821+
Tags: types.Tags{},
771822
})
772823
}
773824
}
@@ -1029,6 +1080,8 @@ func newDisk(volume *ec2.Volume) *types.Disk {
10291080
Created: getCreated(volume.CreateTime),
10301081
Size: *volume.Size,
10311082
Type: *volume.VolumeType,
1083+
Owner: tags[ctx.OwnerLabel],
1084+
Tags: tags,
10321085
}
10331086
}
10341087

@@ -1045,6 +1098,7 @@ func newImage(image *ec2.Image, region string) *types.Image {
10451098
CloudType: types.AWS,
10461099
Region: region,
10471100
Created: createdAt,
1101+
Tags: getEc2Tags(image.Tags),
10481102
}
10491103
}
10501104

aws/aws_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ func (t mockEc2Client) DeleteVolume(input *ec2.DeleteVolumeInput) (*ec2.DeleteVo
174174
return nil, nil
175175
}
176176

177+
func (t mockEc2Client) DetachVolume(input *ec2.DetachVolumeInput) (*ec2.VolumeAttachment, error) {
178+
return nil, nil
179+
}
180+
177181
func (t mockEc2Client) DeregisterImage(input *ec2.DeregisterImageInput) (*ec2.DeregisterImageOutput, error) {
178182
t.deregisterImagesChannel <- *input.ImageId
179183
return nil, nil

azure/azure.go

+1
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ func newImage(image compute.Image) *types.Image {
467467
Name: *image.Name,
468468
Region: *image.Location,
469469
CloudType: types.AZURE,
470+
Tags: utils.ConvertTags(image.Tags),
470471
}
471472
}
472473

context/context.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ var DryRun = false
3333
var Verbose = false
3434

3535
// IgnoreLabelDisabled is a global flag for enabling/disabling ignore label usage
36-
var IgnoreLabelDisabled = true
36+
var IgnoreLabelDisabled = false
3737

3838
// Operations contains all the available operations
3939
var Operations = make(map[types.OpType]types.Operation)
@@ -51,4 +51,4 @@ var Dispatchers = make(map[string]types.Dispatcher)
5151
var Actions = make(map[types.ActionType]types.Action)
5252

5353
// FilterConfig contains the include/exclude configurations from config file
54-
var FilterConfig *types.FilterConfig
54+
var FilterConfig types.IFilterConfig

filter/common.go

+56-119
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/hortonworks/cloud-haunter/types"
66
"github.com/hortonworks/cloud-haunter/utils"
77
log "github.com/sirupsen/logrus"
8+
"reflect"
89
)
910

1011
func filter(filterName string, items []types.CloudItem, filterType types.FilterConfigType, isNeeded func(types.CloudItem) bool) []types.CloudItem {
@@ -28,138 +29,74 @@ func filter(filterName string, items []types.CloudItem, filterType types.FilterC
2829
return filtered
2930
}
3031

31-
func isFilterMatch(filterName string, item types.CloudItem, filterType types.FilterConfigType, filterConfig *types.FilterConfig) bool {
32-
switch item.GetItem().(type) {
33-
case types.Instance:
34-
inst := item.GetItem().(types.Instance)
35-
name := item.GetName()
36-
ignoreLabelFound := utils.IsAnyMatch(inst.Tags, ctx.IgnoreLabel)
37-
if ignoreLabelFound {
38-
log.Debugf("[%s] Found ignore label on item: %s, label: %s", filterName, name, ctx.IgnoreLabel)
39-
if ctx.IgnoreLabelDisabled {
40-
log.Debugf("[%s] Ignore label usage is disabled, continuing to apply filter on item: %s", filterName, name)
41-
} else {
42-
if filterType.IsInclusive() {
43-
log.Debugf("[%s] inclusive filter applied on item: %s", filterName, name)
44-
return false
45-
}
46-
log.Debugf("[%s] exclusive filter applied on item: %s", filterName, name)
47-
return true
48-
}
49-
}
50-
filtered, applied := applyFilterConfig(filterConfig, filterType, item, filterName, inst.Tags)
51-
if applied {
52-
return filtered
53-
}
54-
case types.Stack:
55-
stack := item.GetItem().(types.Stack)
56-
name := item.GetName()
57-
ignoreLabelFound := utils.IsAnyMatch(stack.Tags, ctx.IgnoreLabel)
58-
if ignoreLabelFound {
59-
log.Debugf("[%s] Found ignore label on item: %s, label: %s", filterName, name, ctx.IgnoreLabel)
60-
if ctx.IgnoreLabelDisabled {
61-
log.Debugf("[%s] Ignore label usage is disabled, continuing to apply filter on item: %s", filterName, name)
62-
} else {
63-
if filterType.IsInclusive() {
64-
log.Debugf("[%s] inclusive filter applied on item: %s", filterName, name)
65-
return false
66-
}
67-
log.Debugf("[%s] exclusive filter applied on item: %s", filterName, name)
68-
return true
32+
func isFilterMatch(filterName string, item types.CloudItem, filterType types.FilterConfigType, filterConfig types.IFilterConfig) bool {
33+
name := item.GetName()
34+
ignoreLabelFound := utils.IsAnyMatch(item.GetTags(), ctx.IgnoreLabel)
35+
if ignoreLabelFound {
36+
log.Debugf("[%s] Found ignore label on item: %s, label: %s", filterName, name, ctx.IgnoreLabel)
37+
if ctx.IgnoreLabelDisabled {
38+
log.Debugf("[%s] Ignore label usage is disabled, continuing to apply filter on item: %s", filterName, name)
39+
} else {
40+
if filterType.IsInclusive() {
41+
log.Debugf("[%s] inclusive filter applied on item: %s", filterName, name)
42+
return false
6943
}
44+
log.Debugf("[%s] exclusive filter applied on item: %s", filterName, name)
45+
return true
7046
}
71-
filtered, applied := applyFilterConfig(filterConfig, filterType, item, filterName, stack.Tags)
72-
if applied {
73-
return filtered
74-
}
47+
}
48+
49+
if filterConfig == nil {
50+
return false
51+
}
52+
53+
var filterEntityType types.FilterEntityType
54+
55+
switch item.GetItem().(type) {
7556
case types.Access:
76-
accessFilter, _ := getFilterConfigs(filterConfig, filterType)
77-
if accessFilter != nil {
78-
switch item.GetCloudType() {
79-
case types.AWS:
80-
return isNameOrOwnerMatch(filterName, item, accessFilter.Aws.Names, accessFilter.Aws.Owners)
81-
case types.AZURE:
82-
return isNameOrOwnerMatch(filterName, item, accessFilter.Azure.Names, accessFilter.Azure.Owners)
83-
case types.GCP:
84-
return isNameOrOwnerMatch(filterName, item, accessFilter.Gcp.Names, accessFilter.Gcp.Owners)
85-
default:
86-
log.Warnf("[%s] Cloud type not supported: %s", filterName, item.GetCloudType())
87-
}
88-
}
89-
case types.Database:
90-
database := item.GetItem().(types.Database)
91-
name := item.GetName()
92-
ignoreLabelFound := utils.IsAnyMatch(database.Tags, ctx.IgnoreLabel)
93-
if ignoreLabelFound {
94-
log.Debugf("[%s] Found ignore label on item: %s, label: %s", filterName, name, ctx.IgnoreLabel)
95-
if ctx.IgnoreLabelDisabled {
96-
log.Debugf("[%s] Ignore label usage is disabled, continuing to apply filter on item: %s", filterName, name)
97-
} else {
98-
if filterType.IsInclusive() {
99-
log.Debugf("[%s] inclusive filter applied on item: %s", filterName, name)
100-
return false
101-
}
102-
log.Debugf("[%s] exclusive filter applied on item: %s", filterName, name)
103-
return true
104-
}
105-
}
106-
filtered, applied := applyFilterConfig(filterConfig, filterType, item, filterName, database.Tags)
107-
if applied {
108-
return filtered
57+
if filterType.IsInclusive() {
58+
filterEntityType = types.IncludeAccess
59+
} else {
60+
filterEntityType = types.ExcludeAccess
10961
}
110-
case types.Disk:
111-
filtered, applied := applyFilterConfig(filterConfig, filterType, item, filterName, types.Tags{})
112-
if applied {
113-
return filtered
62+
case types.Instance, types.Stack, types.Database, types.Disk:
63+
if filterType.IsInclusive() {
64+
filterEntityType = types.IncludeInstance
65+
} else {
66+
filterEntityType = types.ExcludeInstance
11467
}
68+
default:
69+
log.Warnf("Filtering is not implemented for type %s", reflect.TypeOf(item))
70+
return false
11571
}
116-
return false
117-
}
11872

119-
func applyFilterConfig(filterConfig *types.FilterConfig, filterType types.FilterConfigType, item types.CloudItem, filterName string, tags types.Tags) (applied, filtered bool) {
120-
_, instanceFilter := getFilterConfigs(filterConfig, filterType)
121-
if instanceFilter != nil {
122-
switch item.GetCloudType() {
123-
case types.AWS:
124-
return isMatchWithIgnores(filterName, item, tags,
125-
instanceFilter.Aws.Names, instanceFilter.Aws.Owners, instanceFilter.Aws.Labels), true
126-
case types.AZURE:
127-
return isMatchWithIgnores(filterName, item, tags,
128-
instanceFilter.Azure.Names, instanceFilter.Azure.Owners, instanceFilter.Azure.Labels), true
129-
case types.GCP:
130-
return isMatchWithIgnores(filterName, item, tags,
131-
instanceFilter.Gcp.Names, instanceFilter.Gcp.Owners, instanceFilter.Gcp.Labels), true
132-
default:
133-
log.Warnf("[%s] Cloud type not supported: %s", filterName, item.GetCloudType())
134-
}
73+
filtered, applied := false, false
74+
75+
if names := filterConfig.GetFilterValues(filterEntityType, item.GetCloudType(), types.Name); names != nil {
76+
log.Debugf("[%s] filtering item %s to names [%s]", filterName, item.GetName(), names)
77+
filtered, applied = filtered || utils.IsStartsWith(item.GetName(), names...), true
13578
}
136-
return false, false
137-
}
13879

139-
func getFilterConfigs(filterConfig *types.FilterConfig, filterType types.FilterConfigType) (accessConfig *types.FilterAccessConfig, instanceConfig *types.FilterInstanceConfig) {
140-
if filterConfig != nil {
141-
if filterType.IsInclusive() {
142-
return filterConfig.IncludeAccess, filterConfig.IncludeInstance
143-
}
144-
return filterConfig.ExcludeAccess, filterConfig.ExcludeInstance
80+
if owners := filterConfig.GetFilterValues(filterEntityType, item.GetCloudType(), types.Owner); owners != nil {
81+
log.Debugf("[%s] filtering item %s to owners [%s]", filterName, item.GetName(), owners)
82+
filtered, applied = filtered || utils.IsStartsWith(item.GetOwner(), owners...), true
14583
}
146-
return nil, nil
147-
}
14884

149-
func isMatchWithIgnores(filterName string, item types.CloudItem, tags map[string]string, names, owners []string, labels []string) bool {
150-
if isNameOrOwnerMatch(filterName, item, names, owners) || utils.IsAnyStartsWith(tags, labels...) {
151-
log.Debugf("[%s] item %s match with name/owner or tag %s", filterName, item.GetName(), labels)
152-
return true
85+
if labels := filterConfig.GetFilterValues(filterEntityType, item.GetCloudType(), types.Label); labels != nil {
86+
log.Debugf("[%s] filtering item %s to labels [%s]", filterName, item.GetName(), labels)
87+
filtered, applied = filtered || utils.IsAnyStartsWith(item.GetTags(), labels...), true
15388
}
154-
log.Debugf("[%s] item %s does not match with name/owner or tag %s", filterName, item.GetName(), labels)
155-
return false
156-
}
15789

158-
func isNameOrOwnerMatch(filterName string, item types.CloudItem, names, owners []string) bool {
159-
if utils.IsStartsWith(item.GetName(), names...) || utils.IsStartsWith(item.GetOwner(), owners...) {
160-
log.Debugf("[%s] item %s match with filter config name %s or owner %s", filterName, item.GetName(), names, owners)
161-
return true
90+
if applied {
91+
if filtered {
92+
log.Debugf("[%s] item %s matches filter", filterName, item.GetName())
93+
} else {
94+
log.Debugf("[%s] item %s does not match filter", filterName, item.GetName())
95+
}
96+
return filtered
97+
} else {
98+
log.Debugf("[%s] item %s could not be filtered", filterName, item.GetName())
16299
}
163-
log.Debugf("[%s] item %s does not match with filter config name %s or owner %s", filterName, item.GetName(), names, owners)
100+
164101
return false
165102
}

filter/ownerless.go

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func (o ownerless) Execute(items []types.CloudItem) []types.CloudItem {
3636
clust := item.(*types.Cluster)
3737
match := !utils.IsAnyMatch(clust.Tags, ctx.OwnerLabel)
3838
log.Debugf("[OWNERLESS] Cluster: %s match: %v (%s)", clust.Name, match, clust.State)
39+
case types.Disk:
40+
disk := item.(*types.Disk)
41+
match := len(disk.Owner) == 0
42+
log.Debugf("[OWNERLESS] Disk: %s match: %v", disk.Name, match)
3943
return match
4044
default:
4145
log.Fatalf("[OWNERLESS] Filter does not apply for cloud item: %s", item.GetName())

0 commit comments

Comments
 (0)