Skip to content

Commit 740a428

Browse files
authored
feat: Oracle plugin (#6353)
First version of oracle plugin. See more information below. TODOs: - Add descriptions with links to oracle's API - Add mock tests. - Add more resources - Understand why `mysql` and `nosql` resources make the plugin get stuck. - You can modify the `codegen/main.go` `servicesAllowlist` and `servicesDenylist` to generate more resources. - Fix column names (e.g. `oracle_volumes->size_in_m_bs`) - Add nested tables. Codegen explanation: - `codegen` generates table-definitions and fetcher-functions, based on `oracle_clients.go`. - The `oracle_clients` was semi-manually generated by me using the script `generate_oracleClients_struct.py`. - `generate_oracleClients_struct.py` requires cloning [https://github.com/oracle/oci-go-sdk] and running the [pkgreflect] tool on it.
1 parent 9c41854 commit 740a428

263 files changed

Lines changed: 11984 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/pr_labeler.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ heroku:
3232
- plugins/source/heroku/**/*
3333
okta:
3434
- plugins/source/okta/**/*
35+
oracle:
36+
- plugins/source/oracle/**/*
3537
slack:
3638
- plugins/source/slack/**/*
3739
shopify:
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: Source Plugin Oracle Workflow
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "plugins/source/oracle/**"
7+
- ".github/workflows/source_oracle.yml"
8+
push:
9+
branches:
10+
- main
11+
paths:
12+
- "plugins/source/oracle/**"
13+
- ".github/workflows/source_oracle.yml"
14+
15+
jobs:
16+
plugins-source-oracle:
17+
timeout-minutes: 30
18+
name: "plugins/source/oracle"
19+
runs-on: ubuntu-latest
20+
defaults:
21+
run:
22+
working-directory: ./plugins/source/oracle
23+
steps:
24+
- uses: actions/checkout@v3
25+
with:
26+
fetch-depth: 2
27+
- name: Set up Go 1.x
28+
uses: actions/setup-go@v3
29+
with:
30+
go-version-file: plugins/source/oracle/go.mod
31+
cache: true
32+
cache-dependency-path: plugins/source/oracle/go.sum
33+
- name: golangci-lint
34+
uses: golangci/golangci-lint-action@v3
35+
with:
36+
version: v1.50.1
37+
working-directory: plugins/source/oracle
38+
args: "--config ../../.golangci.yml"
39+
- name: Get dependencies
40+
run: go get -t -d ./...
41+
- name: Build
42+
run: go build .
43+
- name: Test
44+
run: make test
45+
- name: gen-docs
46+
if: github.event_name == 'pull_request'
47+
run: make gen-docs
48+
- name: Fail if generation updated files
49+
if: github.event_name == 'pull_request'
50+
run: test "$(git status -s | wc -l)" -eq 0 || (git status -s; exit 1)
51+
validate-release:
52+
timeout-minutes: 30
53+
runs-on: ubuntu-latest
54+
env:
55+
CGO_ENABLED: 0
56+
steps:
57+
- name: Checkout
58+
if: startsWith(github.head_ref, 'release-please--branches--main--components') || github.event_name == 'push'
59+
uses: actions/checkout@v3
60+
- uses: actions/cache@v3
61+
if: startsWith(github.head_ref, 'release-please--branches--main--components') || github.event_name == 'push'
62+
with:
63+
path: |
64+
~/.cache/go-build
65+
~/go/pkg/mod
66+
key: ${{ runner.os }}-go-1.19.3-release-cache-${{ hashFiles('plugins/source/oracle/go.sum') }}
67+
restore-keys: |
68+
${{ runner.os }}-go-1.19.3-release-cache-plugins-source-oracle
69+
- name: Set up Go
70+
if: startsWith(github.head_ref, 'release-please--branches--main--components') || github.event_name == 'push'
71+
uses: actions/setup-go@v3
72+
with:
73+
go-version-file: plugins/source/oracle/go.mod
74+
- name: Install GoReleaser
75+
if: startsWith(github.head_ref, 'release-please--branches--main--components') || github.event_name == 'push'
76+
uses: goreleaser/goreleaser-action@v3
77+
with:
78+
distribution: goreleaser-pro
79+
version: latest
80+
install-only: true
81+
- name: Run GoReleaser Dry-Run
82+
if: startsWith(github.head_ref, 'release-please--branches--main--components') || github.event_name == 'push'
83+
run: goreleaser release --snapshot --rm-dist --skip-validate --skip-publish --skip-sign -f ./plugins/source/oracle/.goreleaser.yaml
84+
env:
85+
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}

.github/workflows/wait_for_required_workflows.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jobs:
5757
"plugins/source/k8s",
5858
"plugins/source/okta",
5959
"plugins/source/shopify",
60+
"plugins/source/oracle",
6061
"plugins/source/slack",
6162
"plugins/source/salesforce",
6263
"plugins/source/snyk",

plugins/source/oracle/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
oracle

plugins/source/oracle/Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Test unit
2+
.PHONY: test
3+
test:
4+
go test -timeout 3m ./...
5+
6+
.PHONY: lint
7+
lint:
8+
golangci-lint run --config ../../.golangci.yml
9+
10+
.PHONY: gen-code
11+
gen-code:
12+
go run codegen/*.go
13+
14+
.PHONY: gen-docs
15+
gen-docs:
16+
rm -rf ./docs/tables/*
17+
go run main.go doc ./docs/tables
18+
sed 's_(\(.*\))_(https://github.com/cloudquery/cloudquery/blob/main/plugins/source/oracle/docs/tables/\1)_' docs/tables/README.md > ../../../website/pages/docs/plugins/sources/oracle/tables.md
19+
20+
# All gen targets
21+
.PHONY: gen
22+
gen: gen-docs

plugins/source/oracle/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Oracle Source Plugin
2+
3+
The Oracle source plugin extracts information from the Oracle Cloud (`oci`) API.
4+
5+
## Links
6+
7+
- [User Guide](https://cloudquery.io/docs/plugins/sources/oracle/overview)
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/cloudquery/plugin-sdk/schema"
9+
"github.com/cloudquery/plugin-sdk/specs"
10+
"github.com/oracle/oci-go-sdk/v65/common"
11+
"github.com/oracle/oci-go-sdk/v65/identity"
12+
"github.com/oracle/oci-go-sdk/v65/objectstorage"
13+
"github.com/rs/zerolog"
14+
)
15+
16+
type Client struct {
17+
// A map of region->`OracleClients` struct.
18+
// Every OracleClients struct contains all the clients we need for a single regionXcompartment
19+
OracleClients map[string]*OracleClients
20+
AllCompartmentOcids []string
21+
22+
TenancyOcid string // Tenancy == RootCompartment
23+
HomeRegion string
24+
25+
ObjectStorageNamespace string // A global value, used for object-storage (i.e. buckets)
26+
27+
// All availibility domains in the tenancy.
28+
RegionAvaililbilityDomainMap map[string][]string
29+
30+
// These are different per "cq-client", i.e. per multiplexed-cq-client.
31+
// By default (if no multiplexer is defined), Region is set to the home region, and CompartmentOcid is set to the tenancy ocid.
32+
Region string
33+
CompartmentOcid string
34+
AvailibilityDomain string // For fetches that are multiplexed by availibility domain (and not region)
35+
36+
logger zerolog.Logger
37+
}
38+
39+
func Configure(ctx context.Context, logger zerolog.Logger, s specs.Source) (schema.ClientMeta, error) {
40+
configProvider := common.DefaultConfigProvider()
41+
42+
tenancyOcid, err := configProvider.TenancyOCID()
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
// The identity-client in the home region, that we use for initialization.
48+
homeIdentityClient, err := identity.NewIdentityClientWithConfigurationProvider(configProvider)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
allCompartmentOcids, err := getAllCompartmentIdsInTenancy(ctx, homeIdentityClient, tenancyOcid)
54+
if err != nil {
55+
return nil, err
56+
}
57+
logger.Info().Int("num_compartments", len(allCompartmentOcids)).Msg("syncing from all compartments in tenancy")
58+
59+
allRegions, homeRegion, err := getAllSubscribedRegions(ctx, homeIdentityClient, tenancyOcid)
60+
if err != nil {
61+
return nil, err
62+
}
63+
logger.Info().Int("num_regions", len(allRegions)).Msg("syncing from all subscribed regions")
64+
65+
oracleClients, err := initOracleClientsInAllRegions(configProvider, allRegions)
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
objectStorageNamespace, err := getObjectStorageNamespace(ctx, oracleClients[homeRegion].ObjectstorageObjectstorageClient)
71+
72+
if err != nil {
73+
return nil, err
74+
}
75+
76+
regionAvailibilityDomainMap, err := getRegionAvailibilityDomainMap(ctx, oracleClients, tenancyOcid)
77+
if err != nil {
78+
return nil, err
79+
}
80+
81+
logger = logger.With().Str("region", homeRegion).Str("compartment_ocid", tenancyOcid).Logger()
82+
83+
return &Client{
84+
OracleClients: oracleClients,
85+
AllCompartmentOcids: allCompartmentOcids,
86+
HomeRegion: homeRegion,
87+
TenancyOcid: tenancyOcid,
88+
RegionAvaililbilityDomainMap: regionAvailibilityDomainMap,
89+
ObjectStorageNamespace: objectStorageNamespace,
90+
Region: homeRegion, // Default value if no multiplexer is defined
91+
CompartmentOcid: tenancyOcid, // Default value if no multiplexer is defined
92+
logger: logger,
93+
}, nil
94+
}
95+
96+
// cq-client id is comprised of region and compartment-ocid.
97+
func (c *Client) ID() string {
98+
return strings.Join([]string{c.Region, c.CompartmentOcid}, ":")
99+
}
100+
101+
func (c *Client) Logger() *zerolog.Logger {
102+
return &c.logger
103+
}
104+
105+
func (c *Client) withRegion(region string) *Client {
106+
newClient := *c
107+
newClient.logger = c.logger.With().Str("region", region).Logger()
108+
newClient.Region = region
109+
return &newClient
110+
}
111+
112+
func (c *Client) withCompartment(compartmentOcid string) *Client {
113+
newClient := *c
114+
newClient.logger = c.logger.With().Str("compartment_ocid", compartmentOcid).Logger()
115+
newClient.CompartmentOcid = compartmentOcid
116+
return &newClient
117+
}
118+
119+
func (c *Client) withAvailibilityDomain(availabilityDomain string) *Client {
120+
newClient := *c
121+
newClient.logger = c.logger.With().Str("availability_domain", availabilityDomain).Logger()
122+
newClient.AvailibilityDomain = availabilityDomain
123+
return &newClient
124+
}
125+
126+
// Returns a list of all compartment-ids in a tenancy, recursively. (including the tenancy itself, which is the 'root compartment').
127+
func getAllCompartmentIdsInTenancy(ctx context.Context, client identity.IdentityClient, tenancyId string) ([]string, error) {
128+
allCompartmentIds := []string{tenancyId}
129+
130+
var page *string
131+
for {
132+
request := identity.ListCompartmentsRequest{
133+
CompartmentId: common.String(tenancyId),
134+
CompartmentIdInSubtree: common.Bool(true), // recursively traverse the compartment hierarchy
135+
LifecycleState: identity.CompartmentLifecycleStateActive,
136+
Page: page,
137+
}
138+
139+
response, err := client.ListCompartments(ctx, request)
140+
if err != nil {
141+
return nil, fmt.Errorf("failed to list compartments in tenancy: %w", err)
142+
}
143+
144+
for i := range response.Items {
145+
allCompartmentIds = append(allCompartmentIds, *response.Items[i].Id)
146+
}
147+
148+
page = response.OpcNextPage
149+
if response.OpcNextPage == nil {
150+
break
151+
}
152+
}
153+
154+
return allCompartmentIds, nil
155+
}
156+
157+
// Returns a list of all subscribed regions in a tenancy. Also returns the name of the home-region.
158+
func getAllSubscribedRegions(ctx context.Context, client identity.IdentityClient, tenancyOcid string) ([]string, string, error) {
159+
request := identity.ListRegionSubscriptionsRequest{
160+
TenancyId: common.String(tenancyOcid),
161+
}
162+
163+
response, err := client.ListRegionSubscriptions(ctx, request)
164+
if err != nil {
165+
return nil, "", err
166+
}
167+
168+
regionNames := make([]string, 0)
169+
var homeRegion string
170+
171+
for _, region := range response.Items {
172+
if region.Status != identity.RegionSubscriptionStatusReady {
173+
continue
174+
}
175+
176+
regionNames = append(regionNames, *region.RegionName)
177+
if *region.IsHomeRegion {
178+
homeRegion = *region.RegionName
179+
}
180+
}
181+
182+
if homeRegion == "" {
183+
return nil, "", fmt.Errorf("no home region found")
184+
}
185+
186+
return regionNames, homeRegion, nil
187+
}
188+
189+
func getObjectStorageNamespace(ctx context.Context, client *objectstorage.ObjectStorageClient) (string, error) {
190+
request := objectstorage.GetNamespaceRequest{}
191+
192+
response, err := client.GetNamespace(ctx, request)
193+
if err != nil {
194+
return "", err
195+
}
196+
197+
return *response.Value, nil
198+
}
199+
200+
func getRegionAvailibilityDomainMap(ctx context.Context, oracleClients map[string]*OracleClients, tenancyOcid string) (map[string][]string, error) {
201+
regionAvailibilityDomainMap := make(map[string][]string)
202+
203+
for region, clients := range oracleClients {
204+
request := identity.ListAvailabilityDomainsRequest{
205+
CompartmentId: common.String(tenancyOcid),
206+
}
207+
208+
response, err := clients.IdentityIdentityClient.ListAvailabilityDomains(ctx, request)
209+
if err != nil {
210+
return nil, err
211+
}
212+
213+
availabilityDomains := make([]string, 0)
214+
215+
for _, domain := range response.Items {
216+
availabilityDomains = append(availabilityDomains, *domain.Name)
217+
}
218+
219+
regionAvailibilityDomainMap[region] = availabilityDomains
220+
}
221+
222+
return regionAvailibilityDomainMap, nil
223+
}

0 commit comments

Comments
 (0)