Skip to content

Commit 0cc71d1

Browse files
authored
Merge pull request #1565 from snyk/fea/facade_enum
add a facade to implement enum library api
2 parents cf9dba0 + dcd11f0 commit 0cc71d1

File tree

9 files changed

+340
-5
lines changed

9 files changed

+340
-5
lines changed

enumeration/diagnostic.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package enumeration
2+
3+
import "github.com/snyk/driftctl/enumeration/resource"
4+
5+
type Diagnostic interface {
6+
Code() string
7+
Message() string
8+
ResourceType() string
9+
Resource() *resource.Resource
10+
}
11+
12+
type Diagnostics []Diagnostic

enumeration/enum.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package enumeration
2+
3+
import (
4+
"time"
5+
6+
"github.com/snyk/driftctl/enumeration/resource"
7+
)
8+
9+
type EnumerateInput struct {
10+
ResourceTypes []string
11+
}
12+
13+
type EnumerateOutput struct {
14+
// Resources is a map of resources by type. Every listed resource type will
15+
// have a key in the map. The value will be either nil or an empty slice if
16+
// no resources of that type were found.
17+
Resources map[string][]*resource.Resource
18+
19+
// Timings is map of list durations by resource type. This aids understanding
20+
// which resource types took the most time to list.
21+
Timings map[string]time.Duration
22+
23+
// Diagnostics contains messages and errors that arose during the list operation.
24+
// If the diagnostic is associated with a resource type, the ResourceType()
25+
// call will indicate which type. If associated with a resource, the Resource()
26+
// call will indicate which resource.
27+
Diagnostics Diagnostics
28+
}
29+
30+
type Enumerator interface {
31+
Enumerate(*EnumerateInput) (*EnumerateOutput, error)
32+
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
package enumerator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"sync"
8+
9+
"github.com/snyk/driftctl/enumeration"
10+
11+
"github.com/sirupsen/logrus"
12+
"github.com/snyk/driftctl/enumeration/alerter"
13+
"github.com/snyk/driftctl/enumeration/parallel"
14+
"github.com/snyk/driftctl/enumeration/remote"
15+
"github.com/snyk/driftctl/enumeration/remote/common"
16+
"github.com/snyk/driftctl/enumeration/resource"
17+
"github.com/snyk/driftctl/enumeration/terraform"
18+
)
19+
20+
type CloudEnumerator struct {
21+
alerter *sliceAlerter
22+
progress enumeration.ProgressCounter
23+
remoteLibrary *common.RemoteLibrary
24+
providerLibrary *terraform.ProviderLibrary
25+
enumeratorRunner *parallel.ParallelRunner
26+
detailsFetcherRunner *parallel.ParallelRunner
27+
to string
28+
}
29+
30+
type cloudEnumeratorBuilder struct {
31+
cloud string
32+
providerVersion string
33+
configDirectory string
34+
}
35+
36+
// WithCloud Choose which cloud to use for enumeration and refresh
37+
// TODO could be inferred with types listed
38+
func (b *cloudEnumeratorBuilder) WithCloud(cloud string) *cloudEnumeratorBuilder {
39+
b.cloud = cloud
40+
return b
41+
}
42+
43+
// WithProviderVersion optionally choose the provider version used for refresh
44+
func (b *cloudEnumeratorBuilder) WithProviderVersion(providerVersion string) *cloudEnumeratorBuilder {
45+
b.providerVersion = providerVersion
46+
return b
47+
}
48+
49+
// WithConfigDirectory optionally choose the directory used to download terraform provider used for refresh
50+
func (b *cloudEnumeratorBuilder) WithConfigDirectory(configDir string) *cloudEnumeratorBuilder {
51+
b.configDirectory = configDir
52+
return b
53+
}
54+
55+
func (b *cloudEnumeratorBuilder) Build() (*CloudEnumerator, error) {
56+
enumerator := &CloudEnumerator{
57+
enumeratorRunner: parallel.NewParallelRunner(context.TODO(), 10),
58+
detailsFetcherRunner: parallel.NewParallelRunner(context.TODO(), 10),
59+
providerLibrary: terraform.NewProviderLibrary(),
60+
remoteLibrary: common.NewRemoteLibrary(),
61+
alerter: &sliceAlerter{},
62+
progress: &dummyCounter{},
63+
}
64+
65+
if b.configDirectory == "" {
66+
tempDir, err := os.MkdirTemp("", "enumerator")
67+
if err != nil {
68+
return nil, err
69+
}
70+
b.configDirectory = tempDir
71+
}
72+
73+
err := enumerator.init(fmt.Sprintf("%s+tf", b.cloud), b.providerVersion, b.configDirectory)
74+
75+
return enumerator, err
76+
}
77+
78+
func NewCloudEnumerator() *cloudEnumeratorBuilder {
79+
return &cloudEnumeratorBuilder{}
80+
}
81+
82+
func (e *CloudEnumerator) init(to, providerVersion, configDirectory string) error {
83+
e.to = to
84+
85+
resFactory := terraform.NewTerraformResourceFactory()
86+
87+
err := remote.Activate(to, providerVersion, e.alerter, e.providerLibrary, e.remoteLibrary, e.progress, resFactory, configDirectory)
88+
if err != nil {
89+
return err
90+
}
91+
return nil
92+
}
93+
94+
func (e *CloudEnumerator) Enumerate(input *enumeration.EnumerateInput) (*enumeration.EnumerateOutput, error) {
95+
types := map[string]struct{}{}
96+
for _, resourceType := range input.ResourceTypes {
97+
types[resourceType] = struct{}{}
98+
}
99+
filter := typeFilter{types: types}
100+
101+
for _, enumerator := range e.remoteLibrary.Enumerators() {
102+
if filter.IsTypeIgnored(enumerator.SupportedType()) {
103+
logrus.WithFields(logrus.Fields{
104+
"type": enumerator.SupportedType(),
105+
}).Debug("Ignored enumeration of resources since it is ignored in filter")
106+
continue
107+
}
108+
enumerator := enumerator
109+
e.enumeratorRunner.Run(func() (interface{}, error) {
110+
resources, err := enumerator.Enumerate()
111+
if err != nil {
112+
err := remote.HandleResourceEnumerationError(err, e.alerter)
113+
if err == nil {
114+
return []*resource.Resource{}, nil
115+
}
116+
return nil, err
117+
}
118+
for _, res := range resources {
119+
if res == nil {
120+
continue
121+
}
122+
logrus.WithFields(logrus.Fields{
123+
"id": res.ResourceId(),
124+
"type": res.ResourceType(),
125+
}).Debug("Found cloud resource")
126+
}
127+
return resources, nil
128+
})
129+
}
130+
131+
results, err := e.retrieveRunnerResults(e.enumeratorRunner)
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
mapRes := mapByType(results)
137+
138+
return &enumeration.EnumerateOutput{
139+
Resources: mapRes,
140+
Timings: nil,
141+
Diagnostics: nil,
142+
}, nil
143+
}
144+
145+
func (e *CloudEnumerator) Refresh(input *enumeration.RefreshInput) (*enumeration.RefreshOutput, error) {
146+
for _, resByType := range input.Resources {
147+
for _, res := range resByType {
148+
res := res
149+
e.detailsFetcherRunner.Run(func() (interface{}, error) {
150+
fetcher := e.remoteLibrary.GetDetailsFetcher(resource.ResourceType(res.ResourceType()))
151+
if fetcher == nil {
152+
return []*resource.Resource{res}, nil
153+
}
154+
155+
resourceWithDetails, err := fetcher.ReadDetails(res)
156+
if err != nil {
157+
if err := remote.HandleResourceDetailsFetchingError(err, e.alerter); err != nil {
158+
return nil, err
159+
}
160+
return []*resource.Resource{}, nil
161+
}
162+
return []*resource.Resource{resourceWithDetails}, nil
163+
})
164+
}
165+
}
166+
167+
results, err := e.retrieveRunnerResults(e.detailsFetcherRunner)
168+
if err != nil {
169+
return nil, err
170+
}
171+
172+
mapRes := mapByType(results)
173+
174+
return &enumeration.RefreshOutput{
175+
Resources: mapRes,
176+
Diagnostics: nil,
177+
}, nil
178+
}
179+
180+
func (e *CloudEnumerator) GetSchema() (*enumeration.GetSchemasOutput, error) {
181+
panic("GetSchema is not implemented..")
182+
}
183+
184+
func (e *CloudEnumerator) retrieveRunnerResults(runner *parallel.ParallelRunner) ([]*resource.Resource, error) {
185+
results := make([]*resource.Resource, 0)
186+
loop:
187+
for {
188+
select {
189+
case resources, ok := <-runner.Read():
190+
if !ok || resources == nil {
191+
break loop
192+
}
193+
194+
for _, res := range resources.([]*resource.Resource) {
195+
if res != nil {
196+
results = append(results, res)
197+
}
198+
}
199+
case <-runner.DoneChan():
200+
break loop
201+
}
202+
}
203+
return results, runner.Err()
204+
}
205+
206+
func (e *CloudEnumerator) List(typ string) ([]*resource.Resource, error) {
207+
enumInput := &enumeration.EnumerateInput{ResourceTypes: []string{typ}}
208+
enumerate, err := e.Enumerate(enumInput)
209+
if err != nil {
210+
return nil, err
211+
}
212+
213+
refreshInput := &enumeration.RefreshInput{Resources: enumerate.Resources}
214+
refresh, err := e.Refresh(refreshInput)
215+
if err != nil {
216+
return nil, err
217+
}
218+
return refresh.Resources[typ], nil
219+
}
220+
221+
type sliceAlerter struct {
222+
lock sync.Mutex
223+
alerts alerter.Alerts
224+
}
225+
226+
func (d *sliceAlerter) Alerts() alerter.Alerts {
227+
return d.alerts
228+
}
229+
230+
func (d *sliceAlerter) SendAlert(key string, alert alerter.Alert) {
231+
d.lock.Lock()
232+
defer d.lock.Unlock()
233+
d.alerts[key] = append(d.alerts[key], alert)
234+
}
235+
236+
type typeFilter struct {
237+
types map[string]struct{}
238+
}
239+
240+
func (u *typeFilter) IsTypeIgnored(ty resource.ResourceType) bool {
241+
_, ok := u.types[ty.String()]
242+
return !ok
243+
}
244+
245+
func (u *typeFilter) IsResourceIgnored(res *resource.Resource) bool {
246+
_, ok := u.types[res.Type]
247+
return !ok
248+
}
249+
250+
func (u *typeFilter) IsFieldIgnored(res *resource.Resource, path []string) bool {
251+
return false
252+
}
253+
254+
type dummyCounter struct {
255+
}
256+
257+
func (d *dummyCounter) Inc() {
258+
}
259+
260+
func mapByType(results []*resource.Resource) map[string][]*resource.Resource {
261+
mapRes := map[string][]*resource.Resource{}
262+
for _, result := range results {
263+
mapRes[result.Type] = append(mapRes[result.Type], result)
264+
}
265+
return mapRes
266+
}

enumeration/refresh.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package enumeration
2+
3+
import (
4+
"github.com/hashicorp/terraform/terraform"
5+
"github.com/snyk/driftctl/enumeration/resource"
6+
)
7+
8+
type RefreshInput struct {
9+
// Resources to refresh
10+
Resources map[string][]*resource.Resource
11+
}
12+
13+
type RefreshOutput struct {
14+
Resources map[string][]*resource.Resource
15+
Diagnostics Diagnostics
16+
}
17+
18+
type GetSchemasOutput struct {
19+
Schema *terraform.ProviderSchema
20+
}
21+
22+
type Refresher interface {
23+
Refresh(input *RefreshInput) (*RefreshOutput, error)
24+
GetSchema() (*GetSchemasOutput, error)
25+
}

enumeration/remote/aws/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
* Required to use Scanner
1818
*/
1919

20-
func Init(version string, alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
20+
func Init(version string, alerter alerter.AlerterInterface, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
2121

2222
provider, err := NewAWSTerraformProvider(version, progress, configDir)
2323
if err != nil {

enumeration/remote/azurerm/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"github.com/snyk/driftctl/enumeration/terraform"
1414
)
1515

16-
func Init(version string, alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
16+
func Init(version string, alerter alerter.AlerterInterface, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
1717

1818
provider, err := NewAzureTerraformProvider(version, progress, configDir)
1919
if err != nil {

enumeration/remote/github/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
* Required to use Scanner
1616
*/
1717

18-
func Init(version string, alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
18+
func Init(version string, alerter alerter.AlerterInterface, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
1919

2020
provider, err := NewGithubTerraformProvider(version, progress, configDir)
2121
if err != nil {

enumeration/remote/google/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"google.golang.org/api/cloudresourcemanager/v1"
1919
)
2020

21-
func Init(version string, alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
21+
func Init(version string, alerter alerter.AlerterInterface, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
2222

2323
provider, err := NewGCPTerraformProvider(version, progress, configDir)
2424
if err != nil {

enumeration/remote/remote.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func IsSupported(remote string) bool {
2929
return false
3030
}
3131

32-
func Activate(remote, version string, alerter *alerter.Alerter, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
32+
func Activate(remote, version string, alerter alerter.AlerterInterface, providerLibrary *terraform.ProviderLibrary, remoteLibrary *common.RemoteLibrary, progress enumeration.ProgressCounter, factory resource.ResourceFactory, configDir string) error {
3333
switch remote {
3434
case common.RemoteAWSTerraform:
3535
return aws.Init(version, alerter, providerLibrary, remoteLibrary, progress, factory, configDir)

0 commit comments

Comments
 (0)