From 552c852010521341b5b4c80dceb57b1a8d3ebfb3 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Thu, 30 Mar 2023 12:42:23 +0530 Subject: [PATCH 01/17] Add table aws_organizations_organizational_unit. closes #1674 --- aws/plugin.go | 1 + ...e_aws_organizations_organizational_unit.go | 181 ++++++++++++++++++ .../aws_organizations_organizational_unit.md | 21 ++ 3 files changed, 203 insertions(+) create mode 100644 aws/table_aws_organizations_organizational_unit.go create mode 100644 docs/tables/aws_organizations_organizational_unit.md diff --git a/aws/plugin.go b/aws/plugin.go index a36ccdbd9..b12216f2f 100644 --- a/aws/plugin.go +++ b/aws/plugin.go @@ -303,6 +303,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "aws_oam_sink": tableAwsOAMSink(ctx), "aws_opensearch_domain": tableAwsOpenSearchDomain(ctx), "aws_organizations_account": tableAwsOrganizationsAccount(ctx), + "aws_organizations_organizational_unit": tableAwsOrganizationsOrganizationalUnit(ctx), "aws_organizations_policy": tableAwsOrganizationsPolicy(ctx), "aws_pinpoint_app": tableAwsPinpointApp(ctx), "aws_pipes_pipe": tableAwsPipes(ctx), diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go new file mode 100644 index 000000000..dcd965570 --- /dev/null +++ b/aws/table_aws_organizations_organizational_unit.go @@ -0,0 +1,181 @@ +package aws + +import ( + "context" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/organizations" + "github.com/aws/aws-sdk-go-v2/service/organizations/types" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "aws_organizations_organizational_unit", + Description: "AWS Organizations Organizational Unit", + Get: &plugin.GetConfig{ + KeyColumns: plugin.SingleColumn("id"), + Hydrate: getOrganizationsOrganizationalUnit, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"InvalidInputException"}), + }, + }, + List: &plugin.ListConfig{ + Hydrate: listOrganizationsOrganizationalUnits, + KeyColumns: plugin.SingleColumn("parent_id"), + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"ParentNotFoundException", "InvalidInputException"}), + }, + }, + Columns: awsGlobalRegionColumns([]*plugin.Column{ + { + Name: "name", + Description: "The friendly name of this OU.", + Type: proto.ColumnType_STRING, + }, + { + Name: "id", + Description: "The unique identifier (ID) associated with this OU.", + Type: proto.ColumnType_STRING, + }, + { + Name: "arn", + Description: "The Amazon Resource Name (ARN) of this OU.", + Type: proto.ColumnType_STRING, + }, + { + Name: "parent_id", + Description: "The unique identifier (ID) of the root or OU whose child OUs you want to list.", + Type: proto.ColumnType_STRING, + Hydrate: getOrganizationsOrganizationalUnit, + Transform: transform.From(getParentId), + }, + + // Steampipe standard columns + { + Name: "title", + Description: resourceInterfaceDescription("title"), + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + { + Name: "akas", + Description: resourceInterfaceDescription("akas"), + Type: proto.ColumnType_JSON, + Transform: transform.FromField("Arn").Transform(transform.EnsureStringArray), + }, + }), + } +} + +//// LIST FUNCTION + +func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Get Client + svc, err := OrganizationClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits", "client_error", err) + return nil, err + } + + parentId := d.EqualsQualString("parent_id") + + // Empty Check + if parentId == "" { + return nil, nil + } + + // Limiting the result + maxItems := int32(20) + + // Reduce the basic request limit down if the user has only requested a small number of rows + if d.QueryContext.Limit != nil { + limit := int32(*d.QueryContext.Limit) + if limit < maxItems { + maxItems = int32(limit) + } + } + + params := &organizations.ListOrganizationalUnitsForParentInput{ + ParentId: aws.String(parentId), + MaxResults: &maxItems, + } + + paginator := organizations.NewListOrganizationalUnitsForParentPaginator(svc, params, func(o *organizations.ListOrganizationalUnitsForParentPaginatorOptions) { + o.Limit = maxItems + o.StopOnDuplicateToken = true + }) + + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits", "api_error", err) + return nil, err + } + + for _, unit := range output.OrganizationalUnits { + d.StreamListItem(ctx, unit) + + // Context may get cancelled due to manual cancellation or if the limit has been reached + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getOrganizationsOrganizationalUnit(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + var orgUnitId string + + if h.Item != nil { + orgUnitId = *h.Item.(*types.Policy).PolicySummary.Id + } else { + orgUnitId = d.EqualsQuals["id"].GetStringValue() + } + + // Get Client + svc, err := OrganizationClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_organizational_unit.getOrganizationsOrganizationalUnit", "client_error", err) + return nil, err + } + + params := &organizations.DescribeOrganizationalUnitInput{ + OrganizationalUnitId: aws.String(orgUnitId), + } + + op, err := svc.DescribeOrganizationalUnit(ctx, params) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_organizational_unit.getOrganizationsOrganizationalUnit", "api_error", err) + return nil, err + } + + return *op.OrganizationalUnit, nil +} + +//// TRANSFORM FUNCTION + +func getParentId(_ context.Context, d *transform.TransformData) (interface{}, error) { + quals := d.KeyColumnQuals["parent_id"] + for _, data := range quals { + parentId := data.Value.GetStringValue() + if parentId != "" { + return parentId, nil + } + } + + if d.HydrateItem != nil { + data := d.HydrateItem.(*types.OrganizationalUnit) + return strings.Split(*data.Arn, "/")[1], nil + } + + return nil, nil +} diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md new file mode 100644 index 000000000..b39ff6465 --- /dev/null +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -0,0 +1,21 @@ +# Table: aws_organizations_organizational_unit + +A container for accounts within a root. An OU also can contain other OUs, enabling you to create a hierarchy that resembles an upside-down tree, with a root at the top and branches of OUs that reach down, ending in accounts that are the leaves of the tree. When you attach a policy to one of the nodes in the hierarchy, it flows down and affects all the branches (OUs) and leaves (accounts) beneath it. An OU can have exactly one parent, and currently each account can be a member of exactly one OU. + +**Note**: The `parent_id` is the required to make the API call. It is the unique identifier (ID) of the root or OU whose child OUs you want to list. + +## Examples + +### Basic info + +```sql +select + name, + id, + arn, + parent_id, + title, + akas +from + aws_organizations_organizational_unit; +``` \ No newline at end of file From 28fe36b30ad9e426dd310eeb3de073624ce28222 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Thu, 30 Mar 2023 12:58:20 +0530 Subject: [PATCH 02/17] Updated the parent_id column value from transform function --- aws/table_aws_organizations_organizational_unit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index dcd965570..2616e47ca 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -174,7 +174,7 @@ func getParentId(_ context.Context, d *transform.TransformData) (interface{}, er if d.HydrateItem != nil { data := d.HydrateItem.(*types.OrganizationalUnit) - return strings.Split(*data.Arn, "/")[1], nil + return strings.Split(*data.Arn, "/")[2], nil } return nil, nil From f01e018b9fd4737e43cea8c3d4b0f72af5e97c43 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Thu, 30 Mar 2023 13:39:39 +0530 Subject: [PATCH 03/17] Managed the hydrated data from the API call --- aws/table_aws_organizations_organizational_unit.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index 2616e47ca..3197138f1 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -136,7 +136,7 @@ func getOrganizationsOrganizationalUnit(ctx context.Context, d *plugin.QueryData var orgUnitId string if h.Item != nil { - orgUnitId = *h.Item.(*types.Policy).PolicySummary.Id + orgUnitId = *h.Item.(types.OrganizationalUnit).Id } else { orgUnitId = d.EqualsQuals["id"].GetStringValue() } @@ -173,7 +173,7 @@ func getParentId(_ context.Context, d *transform.TransformData) (interface{}, er } if d.HydrateItem != nil { - data := d.HydrateItem.(*types.OrganizationalUnit) + data := d.HydrateItem.(types.OrganizationalUnit) return strings.Split(*data.Arn, "/")[2], nil } From 14cc4db9fc28ba35b31eb4372efd722150586152 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Fri, 22 Dec 2023 18:50:16 +0530 Subject: [PATCH 04/17] Updated the table aws_organizations_account by adding an optional qual parent_id. And now this table can be join with the table aws_organizations_organizational_unit --- aws/plugin.go | 1 + aws/table_aws_organizations_account.go | 66 +++++++++- ...e_aws_organizations_organizational_unit.go | 37 ++++-- aws/table_aws_organizations_root.go | 114 ++++++++++++++++++ .../aws_organizations_organizational_unit.md | 2 +- 5 files changed, 204 insertions(+), 16 deletions(-) create mode 100644 aws/table_aws_organizations_root.go diff --git a/aws/plugin.go b/aws/plugin.go index bb368c9a4..c079b43a4 100644 --- a/aws/plugin.go +++ b/aws/plugin.go @@ -334,6 +334,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "aws_organizations_organizational_unit": tableAwsOrganizationsOrganizationalUnit(ctx), "aws_organizations_policy": tableAwsOrganizationsPolicy(ctx), "aws_organizations_policy_target": tableAwsOrganizationsPolicyTarget(ctx), + "aws_organizations_root": tableAwsOrganizationsRoot(ctx), "aws_pinpoint_app": tableAwsPinpointApp(ctx), "aws_pipes_pipe": tableAwsPipes(ctx), "aws_pricing_product": tableAwsPricingProduct(ctx), diff --git a/aws/table_aws_organizations_account.go b/aws/table_aws_organizations_account.go index 23c38d36d..c749de384 100644 --- a/aws/table_aws_organizations_account.go +++ b/aws/table_aws_organizations_account.go @@ -25,7 +25,10 @@ func tableAwsOrganizationsAccount(_ context.Context) *plugin.Table { }, List: &plugin.ListConfig{ Hydrate: listOrganizationsAccounts, - Tags: map[string]string{"service": "organizations", "action": "ListAccounts"}, + KeyColumns: plugin.KeyColumnSlice{ + {Name: "parent_id", Require: plugin.Optional}, + }, + Tags: map[string]string{"service": "organizations", "action": "ListAccounts"}, }, HydrateConfig: []plugin.HydrateConfig{ { @@ -45,6 +48,12 @@ func tableAwsOrganizationsAccount(_ context.Context) *plugin.Table { Description: "The unique identifier (account ID) of the member account.", Type: proto.ColumnType_STRING, }, + { + Name: "parent_id", + Description: "The unique identifier (ID) for the parent root or organization unit (OU) whose accounts you want to list.", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("parent_id"), + }, { Name: "arn", Description: "The Amazon Resource Name (ARN) of the account.", @@ -104,6 +113,8 @@ func tableAwsOrganizationsAccount(_ context.Context) *plugin.Table { //// LIST FUNCTION func listOrganizationsAccounts(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + parentId := d.EqualsQualString("parent_id") + // Get Client svc, err := OrganizationClient(ctx, d) if err != nil { @@ -113,8 +124,7 @@ func listOrganizationsAccounts(ctx context.Context, d *plugin.QueryData, _ *plug // The maximum number for MaxResults parameter is not defined by the API // We have set the MaxResults to 1000 based on our test - maxItems := int32(20) - params := &organizations.ListAccountsInput{} + maxItems := int32(1000) // Reduce the basic request limit down if the user has only requested a small number of rows if d.QueryContext.Limit != nil { @@ -128,6 +138,34 @@ func listOrganizationsAccounts(ctx context.Context, d *plugin.QueryData, _ *plug } } + // Lists the accounts in an organization that are contained by the specified target root or organizational unit (OU). + // If you specify the root, you get a list of all the accounts that aren't in any OU. + // If you specify an OU, you get a list of all the accounts in only that OU and not in any child OUs. + if parentId != "" { + maxItem := int32(20) // TODO for limit value in where clause + op, err := listOrganizationsAccountsForParent(ctx, d, svc, maxItem, &organizations.ListAccountsForParentInput{ + ParentId: &parentId, + MaxResults: &maxItems, + }) + if err != nil { + return nil, err + } + + accounts := op.([]types.Account) + for _, account := range accounts { + d.StreamListItem(ctx, account) + + // Context may get cancelled due to manual cancellation or if the limit has been reached + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + return nil, nil + } + + params := &organizations.ListAccountsInput{} + params.MaxResults = &maxItems paginator := organizations.NewListAccountsPaginator(svc, params, func(o *organizations.ListAccountsPaginatorOptions) { o.Limit = maxItems @@ -157,6 +195,28 @@ func listOrganizationsAccounts(ctx context.Context, d *plugin.QueryData, _ *plug return nil, nil } +func listOrganizationsAccountsForParent(ctx context.Context, d *plugin.QueryData, svc *organizations.Client, maxItems int32, params *organizations.ListAccountsForParentInput) (interface{}, error) { + paginator := organizations.NewListAccountsForParentPaginator(svc, params, func(o *organizations.ListAccountsForParentPaginatorOptions) { + o.Limit = maxItems + o.StopOnDuplicateToken = true + }) + + var accounts []types.Account + for paginator.HasMorePages() { + // apply rate limiting + d.WaitForListRateLimit(ctx) + + output, err := paginator.NextPage(ctx) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_account.listOrganizationsAccountsForParent", "api_error", err) + return nil, err + } + + accounts = append(accounts, output.Accounts...) + } + return accounts, nil +} + //// HYDRATE FUNCTIONS func getOrganizationsAccount(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index 3197138f1..77ac3fcef 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -13,6 +13,8 @@ import ( "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" ) +// The table will return the Organizational Units for the root account if parent_id is not specifiecd in the query parameter. +// If parent_id is specified in the query parameter then it will return the Organizational Units for the given parent. func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { return &plugin.Table{ Name: "aws_organizations_organizational_unit", @@ -25,11 +27,18 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { }, }, List: &plugin.ListConfig{ - Hydrate: listOrganizationsOrganizationalUnits, - KeyColumns: plugin.SingleColumn("parent_id"), + ParentHydrate: listOrganizationsRoots, + Hydrate: listOrganizationsOrganizationalUnits, IgnoreConfig: &plugin.IgnoreConfig{ ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"ParentNotFoundException", "InvalidInputException"}), }, + KeyColumns: plugin.KeyColumnSlice{ + { + Name: "parent_id", + Require: plugin.Optional, + Operators: []string{"="}, + }, + }, }, Columns: awsGlobalRegionColumns([]*plugin.Column{ { @@ -51,7 +60,6 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { Name: "parent_id", Description: "The unique identifier (ID) of the root or OU whose child OUs you want to list.", Type: proto.ColumnType_STRING, - Hydrate: getOrganizationsOrganizationalUnit, Transform: transform.From(getParentId), }, @@ -74,21 +82,25 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { //// LIST FUNCTION -func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Get Client - svc, err := OrganizationClient(ctx, d) - if err != nil { - plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits", "client_error", err) - return nil, err - } - +func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { parentId := d.EqualsQualString("parent_id") + if parentId == "" && h.Item != nil { + parentId = *h.Item.(types.Root).Id + } + // Empty Check if parentId == "" { return nil, nil } + // Get Client + svc, err := OrganizationClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits", "client_error", err) + return nil, err + } + // Limiting the result maxItems := int32(20) @@ -163,11 +175,12 @@ func getOrganizationsOrganizationalUnit(ctx context.Context, d *plugin.QueryData //// TRANSFORM FUNCTION +// This function will be useful if user query the table with 'NOT' operator for the optionsl qual 'parent_id'. Like: "select * from aws_organizations_organizational_unit where parent_id <> 'ou-skjaa-siiewfhgw'" func getParentId(_ context.Context, d *transform.TransformData) (interface{}, error) { quals := d.KeyColumnQuals["parent_id"] for _, data := range quals { parentId := data.Value.GetStringValue() - if parentId != "" { + if parentId != "" && data.Operator == "="{ return parentId, nil } } diff --git a/aws/table_aws_organizations_root.go b/aws/table_aws_organizations_root.go new file mode 100644 index 000000000..2bbcf5528 --- /dev/null +++ b/aws/table_aws_organizations_root.go @@ -0,0 +1,114 @@ +package aws + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/organizations" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +// The table will return the result if the account is a member of an organization. +// You must use the credentials of an account that belongs to an organization. +// The table will return an empty row if the account isn't a member of an organization instead of AWSOrganizationsNotInUseException. +func tableAwsOrganizationsRoot(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "aws_organizations_root", + Description: "AWS Organizations Root", + List: &plugin.ListConfig{ + Hydrate: listOrganizationsRoots, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"AWSOrganizationsNotInUseException"}), + }, + }, + Columns: awsGlobalRegionColumns([]*plugin.Column{ + { + Name: "name", + Description: "The friendly name of the root.", + Type: proto.ColumnType_STRING, + }, + { + Name: "id", + Description: "The unique identifier (ID) for the root.", + Type: proto.ColumnType_STRING, + }, + { + Name: "arn", + Description: "The Amazon Resource Name (ARN) of the root.", + Type: proto.ColumnType_STRING, + }, + { + Name: "policy_types", + Description: "The types of policies that are currently enabled for the root and therefore can be attached to the root or to its OUs or accounts.", + Type: proto.ColumnType_JSON, + }, + + // Steampipe standard columns + { + Name: "title", + Description: resourceInterfaceDescription("title"), + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + { + Name: "akas", + Description: resourceInterfaceDescription("akas"), + Type: proto.ColumnType_JSON, + Transform: transform.FromField("Arn").Transform(transform.EnsureStringArray), + }, + }), + } +} + +//// LIST FUNCTION + +func listOrganizationsRoots(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + + // Get Client + svc, err := OrganizationClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_root.listOrganizationsRoots", "client_error", err) + return nil, err + } + + // Limiting the result + maxItems := int32(20) + + // Reduce the basic request limit down if the user has only requested a small number of rows + if d.QueryContext.Limit != nil { + limit := int32(*d.QueryContext.Limit) + if limit < maxItems { + maxItems = int32(limit) + } + } + + params := &organizations.ListRootsInput{ + MaxResults: &maxItems, + } + + paginator := organizations.NewListRootsPaginator(svc, params, func(o *organizations.ListRootsPaginatorOptions) { + o.Limit = maxItems + o.StopOnDuplicateToken = true + }) + + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_root.listOrganizationsRoots", "api_error", err) + return nil, err + } + + for _, root := range output.Roots { + d.StreamListItem(ctx, root) + + // Context may get cancelled due to manual cancellation or if the limit has been reached + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + } + + return nil, nil +} diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md index b39ff6465..faab4fcc8 100644 --- a/docs/tables/aws_organizations_organizational_unit.md +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -2,7 +2,7 @@ A container for accounts within a root. An OU also can contain other OUs, enabling you to create a hierarchy that resembles an upside-down tree, with a root at the top and branches of OUs that reach down, ending in accounts that are the leaves of the tree. When you attach a policy to one of the nodes in the hierarchy, it flows down and affects all the branches (OUs) and leaves (accounts) beneath it. An OU can have exactly one parent, and currently each account can be a member of exactly one OU. -**Note**: The `parent_id` is the required to make the API call. It is the unique identifier (ID) of the root or OU whose child OUs you want to list. +You **_must_** specify a single `parent_id` or `id` in a where or join clause in order to use this table. ## Examples From 9598dfb8e53b76ecfa06ef8114a3ecdd82dc3962 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Fri, 22 Dec 2023 20:05:31 +0530 Subject: [PATCH 05/17] Cleaned up the comment and handled not found error code with the parent hydrate combinations --- ...able_aws_organizations_organizational_unit.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index 77ac3fcef..63b97981e 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -2,18 +2,20 @@ package aws import ( "context" + "errors" "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/organizations" "github.com/aws/aws-sdk-go-v2/service/organizations/types" + "github.com/aws/smithy-go" "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v5/plugin" "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" ) -// The table will return the Organizational Units for the root account if parent_id is not specifiecd in the query parameter. +// The table will return the Organizational Units for the root account if parent_id is not specified in the query parameter. // If parent_id is specified in the query parameter then it will return the Organizational Units for the given parent. func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { return &plugin.Table{ @@ -34,8 +36,8 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { }, KeyColumns: plugin.KeyColumnSlice{ { - Name: "parent_id", - Require: plugin.Optional, + Name: "parent_id", + Require: plugin.Optional, Operators: []string{"="}, }, }, @@ -125,6 +127,12 @@ func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryDa for paginator.HasMorePages() { output, err := paginator.NextPage(ctx) if err != nil { + var ae smithy.APIError + if errors.As(err, &ae) { + if ae.ErrorCode() == "ParentNotFoundException" { + return nil, nil + } + } plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits", "api_error", err) return nil, err } @@ -180,7 +188,7 @@ func getParentId(_ context.Context, d *transform.TransformData) (interface{}, er quals := d.KeyColumnQuals["parent_id"] for _, data := range quals { parentId := data.Value.GetStringValue() - if parentId != "" && data.Operator == "="{ + if parentId != "" && data.Operator == "=" { return parentId, nil } } From f0a48bc384c1242a2ffdfa4a7ec0785c93ed262b Mon Sep 17 00:00:00 2001 From: ParthaI Date: Fri, 22 Dec 2023 20:54:59 +0530 Subject: [PATCH 06/17] Updated the table aws_organizations_organizational_unit to list the organizational unit for a given account --- ...e_aws_organizations_organizational_unit.go | 65 ++++++++++++++++--- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index 63b97981e..012a29f7c 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -3,6 +3,7 @@ package aws import ( "context" "errors" + "regexp" "strings" "github.com/aws/aws-sdk-go-v2/aws" @@ -46,6 +47,7 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { { Name: "name", Description: "The friendly name of this OU.", + Hydrate: getOrganizationsOrganizationalUnit, Type: proto.ColumnType_STRING, }, { @@ -56,6 +58,7 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { { Name: "arn", Description: "The Amazon Resource Name (ARN) of this OU.", + Hydrate: getOrganizationsOrganizationalUnit, Type: proto.ColumnType_STRING, }, { @@ -70,12 +73,14 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { Name: "title", Description: resourceInterfaceDescription("title"), Type: proto.ColumnType_STRING, + Hydrate: getOrganizationsOrganizationalUnit, Transform: transform.FromField("Name"), }, { Name: "akas", Description: resourceInterfaceDescription("akas"), Type: proto.ColumnType_JSON, + Hydrate: getOrganizationsOrganizationalUnit, Transform: transform.FromField("Arn").Transform(transform.EnsureStringArray), }, }), @@ -87,15 +92,6 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { parentId := d.EqualsQualString("parent_id") - if parentId == "" && h.Item != nil { - parentId = *h.Item.(types.Root).Id - } - - // Empty Check - if parentId == "" { - return nil, nil - } - // Get Client svc, err := OrganizationClient(ctx, d) if err != nil { @@ -106,6 +102,57 @@ func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryDa // Limiting the result maxItems := int32(20) + // If parent_id is a account ID then we should get the organizational units for the given Account ID + pattern := `[0-9]{12}` + re := regexp.MustCompile(pattern) + if parentId != "" && re.MatchString(parentId) { + params := &organizations.ListParentsInput{ + ChildId: aws.String(parentId), + MaxResults: &maxItems, + } + + paginator := organizations.NewListParentsPaginator(svc, params, func(o *organizations.ListParentsPaginatorOptions) { + o.Limit = maxItems + o.StopOnDuplicateToken = true + }) + + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + if err != nil { + var ae smithy.APIError + if errors.As(err, &ae) { + if ae.ErrorCode() == "ParentNotFoundException" { + return nil, nil + } + } + plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits.ListParents", "api_error", err) + return nil, err + } + + for _, ou := range output.Parents { + d.StreamListItem(ctx, types.OrganizationalUnit{ + Id: ou.Id, + }) + + // Context may get cancelled due to manual cancellation or if the limit has been reached + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + } + + return nil, nil + } + + if parentId == "" && h.Item != nil { + parentId = *h.Item.(types.Root).Id + } + + // Empty Check + if parentId == "" { + return nil, nil + } + // Reduce the basic request limit down if the user has only requested a small number of rows if d.QueryContext.Limit != nil { limit := int32(*d.QueryContext.Limit) From 72023140ee2ebf5bac8e77bff6a6817af5f8f854 Mon Sep 17 00:00:00 2001 From: sourav chakraborty Date: Tue, 9 Jan 2024 21:18:35 +0530 Subject: [PATCH 07/17] update the list call --- ...e_aws_organizations_organizational_unit.go | 60 +++---------------- 1 file changed, 7 insertions(+), 53 deletions(-) diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index 012a29f7c..8cd844c26 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -3,7 +3,6 @@ package aws import ( "context" "errors" - "regexp" "strings" "github.com/aws/aws-sdk-go-v2/aws" @@ -90,7 +89,13 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { //// LIST FUNCTION func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - parentId := d.EqualsQualString("parent_id") + parentId := *h.Item.(types.Root).Id + + // Check if the parentId is provided + // The unique identifier (ID) of the root or OU whose child OUs you want to list. + if d.EqualsQualString("parent_id") != "" { + parentId = d.EqualsQualString("parent_id") + } // Get Client svc, err := OrganizationClient(ctx, d) @@ -102,57 +107,6 @@ func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryDa // Limiting the result maxItems := int32(20) - // If parent_id is a account ID then we should get the organizational units for the given Account ID - pattern := `[0-9]{12}` - re := regexp.MustCompile(pattern) - if parentId != "" && re.MatchString(parentId) { - params := &organizations.ListParentsInput{ - ChildId: aws.String(parentId), - MaxResults: &maxItems, - } - - paginator := organizations.NewListParentsPaginator(svc, params, func(o *organizations.ListParentsPaginatorOptions) { - o.Limit = maxItems - o.StopOnDuplicateToken = true - }) - - for paginator.HasMorePages() { - output, err := paginator.NextPage(ctx) - if err != nil { - var ae smithy.APIError - if errors.As(err, &ae) { - if ae.ErrorCode() == "ParentNotFoundException" { - return nil, nil - } - } - plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits.ListParents", "api_error", err) - return nil, err - } - - for _, ou := range output.Parents { - d.StreamListItem(ctx, types.OrganizationalUnit{ - Id: ou.Id, - }) - - // Context may get cancelled due to manual cancellation or if the limit has been reached - if d.RowsRemaining(ctx) == 0 { - return nil, nil - } - } - } - - return nil, nil - } - - if parentId == "" && h.Item != nil { - parentId = *h.Item.(types.Root).Id - } - - // Empty Check - if parentId == "" { - return nil, nil - } - // Reduce the basic request limit down if the user has only requested a small number of rows if d.QueryContext.Limit != nil { limit := int32(*d.QueryContext.Limit) From c2a3aace61118e3bb4313d88ef2c66fd9584ba59 Mon Sep 17 00:00:00 2001 From: sourav chakraborty Date: Wed, 10 Jan 2024 20:30:16 +0530 Subject: [PATCH 08/17] Update the table to list all nested OUs --- ...e_aws_organizations_organizational_unit.go | 76 +++++++++-------- .../aws_organizations_organizational_unit.md | 84 ++++++++++++++++++- 2 files changed, 122 insertions(+), 38 deletions(-) diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index 8cd844c26..8dd7a5f25 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -2,13 +2,11 @@ package aws import ( "context" - "errors" "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/organizations" "github.com/aws/aws-sdk-go-v2/service/organizations/types" - "github.com/aws/smithy-go" "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v5/plugin" @@ -64,7 +62,11 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { Name: "parent_id", Description: "The unique identifier (ID) of the root or OU whose child OUs you want to list.", Type: proto.ColumnType_STRING, - Transform: transform.From(getParentId), + }, + { + Name: "path", + Description: "The OU path is a string representation that uniquely identifies the hierarchical location of an Organizational Unit within the AWS Organizations structure.", + Type: proto.ColumnType_LTREE, }, // Steampipe standard columns @@ -86,6 +88,12 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { } } +type OrganizationalUnit struct { + types.OrganizationalUnit + Path string + ParentId string +} + //// LIST FUNCTION func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { @@ -97,6 +105,11 @@ func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryDa parentId = d.EqualsQualString("parent_id") } + // empty check + if parentId == "" { + return nil, nil + } + // Get Client svc, err := OrganizationClient(ctx, d) if err != nil { @@ -115,6 +128,18 @@ func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryDa } } + // Call the recursive function to list all nested OUs + rootPath := parentId + err = listAllNestedOUs(ctx, d, svc, parentId, maxItems, rootPath) + if err != nil { + plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits", "recursive_call_error", err) + return nil, err + } + + return nil, nil +} + +func listAllNestedOUs(ctx context.Context, d *plugin.QueryData, svc *organizations.Client, parentId string, maxItems int32, currentPath string) error { params := &organizations.ListOrganizationalUnitsForParentInput{ ParentId: aws.String(parentId), MaxResults: &maxItems, @@ -128,27 +153,26 @@ func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryDa for paginator.HasMorePages() { output, err := paginator.NextPage(ctx) if err != nil { - var ae smithy.APIError - if errors.As(err, &ae) { - if ae.ErrorCode() == "ParentNotFoundException" { - return nil, nil - } - } - plugin.Logger(ctx).Error("aws_organizations_organizational_unit.listOrganizationsOrganizationalUnits", "api_error", err) - return nil, err + return err } for _, unit := range output.OrganizationalUnits { - d.StreamListItem(ctx, unit) + ouPath := strings.Replace(currentPath, "-", "_", -1) + "." + strings.Replace(*unit.Id, "-", "_", -1) + d.StreamListItem(ctx, OrganizationalUnit{unit, ouPath, parentId}) + + // Recursively list units for this child + err := listAllNestedOUs(ctx, d, svc, *unit.Id, maxItems, ouPath) + if err != nil { + return err + } - // Context may get cancelled due to manual cancellation or if the limit has been reached if d.RowsRemaining(ctx) == 0 { - return nil, nil + return nil } } } - return nil, nil + return nil } //// HYDRATE FUNCTIONS @@ -157,7 +181,7 @@ func getOrganizationsOrganizationalUnit(ctx context.Context, d *plugin.QueryData var orgUnitId string if h.Item != nil { - orgUnitId = *h.Item.(types.OrganizationalUnit).Id + orgUnitId = *h.Item.(OrganizationalUnit).Id } else { orgUnitId = d.EqualsQuals["id"].GetStringValue() } @@ -181,23 +205,3 @@ func getOrganizationsOrganizationalUnit(ctx context.Context, d *plugin.QueryData return *op.OrganizationalUnit, nil } - -//// TRANSFORM FUNCTION - -// This function will be useful if user query the table with 'NOT' operator for the optionsl qual 'parent_id'. Like: "select * from aws_organizations_organizational_unit where parent_id <> 'ou-skjaa-siiewfhgw'" -func getParentId(_ context.Context, d *transform.TransformData) (interface{}, error) { - quals := d.KeyColumnQuals["parent_id"] - for _, data := range quals { - parentId := data.Value.GetStringValue() - if parentId != "" && data.Operator == "=" { - return parentId, nil - } - } - - if d.HydrateItem != nil { - data := d.HydrateItem.(types.OrganizationalUnit) - return strings.Split(*data.Arn, "/")[2], nil - } - - return nil, nil -} diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md index faab4fcc8..9d0f68776 100644 --- a/docs/tables/aws_organizations_organizational_unit.md +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -2,7 +2,9 @@ A container for accounts within a root. An OU also can contain other OUs, enabling you to create a hierarchy that resembles an upside-down tree, with a root at the top and branches of OUs that reach down, ending in accounts that are the leaves of the tree. When you attach a policy to one of the nodes in the hierarchy, it flows down and affects all the branches (OUs) and leaves (accounts) beneath it. An OU can have exactly one parent, and currently each account can be a member of exactly one OU. -You **_must_** specify a single `parent_id` or `id` in a where or join clause in order to use this table. +To represent the hierarchical structure, the table includes a `path` column. This column is crucial for understanding the relationship between different OUs in the hierarchy. Due to compatibility issues with the ltree type, which is typically used for representing tree-like structures in PostgreSQL, the standard hyphen (-) in the path values has been replaced with an underscore (\_). This modification ensures proper functionality of the ltree operations and queries. + +By default, querying the table without any specific filters will return all OUs from the root of the hierarchy. Users have the option to query the table using a specific `parent_id`. This allows for the retrieval of all direct child OUs under the specified parent. ## Examples @@ -18,4 +20,82 @@ select akas from aws_organizations_organizational_unit; -``` \ No newline at end of file +``` + +### Find a specific organizational unit and all its descendants + +```sql +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + path <@ 'r_wxnb.ou_wxnb_m8l8tpaq'; +``` + +### Select all organizational units at a certain level in the hierarchy + +```sql +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + nlevel(path) = 3; +``` + +### Get all ancestors of a given organizational unit + +```sql +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + 'r_wxnb.ou_wxnb_m8l8tpaq.ou_wxnb_5grilgkb' @> path; +``` + +### Retrieve all siblings of a specific organizational unit + +```sql +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + parent_id = + ( + select + parent_id + from + aws_organizations_organizational_unit + where + name = 'Punisher' + ); +``` + +### Select organizational units with a path that matches a specific pattern + +```sql +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + path ~ 'r_wxnb.*.ou_wxnb_m8l8tpaq.*'; +``` From e798034fdbfd6d842a106108fa8706273bfd7500 Mon Sep 17 00:00:00 2001 From: sourav chakraborty Date: Wed, 10 Jan 2024 20:42:06 +0530 Subject: [PATCH 09/17] remove getconfig --- ...table_aws_organizations_organizational_unit.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index 8dd7a5f25..e77639c4c 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -19,13 +19,6 @@ func tableAwsOrganizationsOrganizationalUnit(_ context.Context) *plugin.Table { return &plugin.Table{ Name: "aws_organizations_organizational_unit", Description: "AWS Organizations Organizational Unit", - Get: &plugin.GetConfig{ - KeyColumns: plugin.SingleColumn("id"), - Hydrate: getOrganizationsOrganizationalUnit, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"InvalidInputException"}), - }, - }, List: &plugin.ListConfig{ ParentHydrate: listOrganizationsRoots, Hydrate: listOrganizationsOrganizationalUnits, @@ -178,13 +171,7 @@ func listAllNestedOUs(ctx context.Context, d *plugin.QueryData, svc *organizatio //// HYDRATE FUNCTIONS func getOrganizationsOrganizationalUnit(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var orgUnitId string - - if h.Item != nil { - orgUnitId = *h.Item.(OrganizationalUnit).Id - } else { - orgUnitId = d.EqualsQuals["id"].GetStringValue() - } + orgUnitId := *h.Item.(OrganizationalUnit).Id // Get Client svc, err := OrganizationClient(ctx, d) From 65e3f8ff4c6fae5694638537f00e9e86fc8cef0a Mon Sep 17 00:00:00 2001 From: sourav chakraborty Date: Wed, 10 Jan 2024 20:43:47 +0530 Subject: [PATCH 10/17] update doc --- docs/tables/aws_organizations_organizational_unit.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md index 9d0f68776..dcc3e8710 100644 --- a/docs/tables/aws_organizations_organizational_unit.md +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -33,7 +33,7 @@ select from aws_organizations_organizational_unit where - path <@ 'r_wxnb.ou_wxnb_m8l8tpaq'; + path <@ 'r_wxnb.ou_wxnb_m8l8t123'; ``` ### Select all organizational units at a certain level in the hierarchy @@ -61,7 +61,7 @@ select from aws_organizations_organizational_unit where - 'r_wxnb.ou_wxnb_m8l8tpaq.ou_wxnb_5grilgkb' @> path; + 'r_wxnb.ou_wxnb_m8l123aq.ou_wxnb_5gri123b' @> path; ``` ### Retrieve all siblings of a specific organizational unit @@ -97,5 +97,5 @@ select from aws_organizations_organizational_unit where - path ~ 'r_wxnb.*.ou_wxnb_m8l8tpaq.*'; + path ~ 'r_wxnb.*.ou_wxnb_m81234aq.*'; ``` From f3b04dfa56c58f4a7a2ca48904377a76b1e69c38 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Wed, 10 Jan 2024 21:41:13 +0530 Subject: [PATCH 11/17] Added doc for the table aws_organizations_root --- docs/tables/aws_organizations_root.md | 60 +++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 docs/tables/aws_organizations_root.md diff --git a/docs/tables/aws_organizations_root.md b/docs/tables/aws_organizations_root.md new file mode 100644 index 000000000..dd546e332 --- /dev/null +++ b/docs/tables/aws_organizations_root.md @@ -0,0 +1,60 @@ +--- +title: "Steampipe Table: aws_organizations_root - Query AWS Organizations Root using SQL" +description: "Allows users to query AWS Organizations Root to retrieve detailed information on AWS Organizations Root account. This table can be utilized to gain insights on organizations root account." +--- + +# Table: aws_organizations_root - Query AWS Organizations Root using SQL + +AWS Organizations uses a hierarchical structure to manage accounts. At the top of this hierarchy is the "root." The root is the starting point for organizing your AWS accounts. The root acts as the parent container for all the accounts in your organization. It can also contain organizational units (OUs), which are sub-containers that can themselves contain accounts or further nested OUs. + +## Table Usage Guide + +The `aws_organizations_root` table in Steampipe provides you the information about AWS Organizations Root Account. + +## Examples + +### Basic info +It's particularly useful in contexts where managing or auditing AWS Organizations. + +```sql+postgres +select + name, + id, + arn +from + aws_organizations_root; +``` + +```sql+sqlite +select + name, + id, + arn +from + aws_organizations_root; +``` + +### Get the policy details attached to organization root account +The types of policies that are currently enabled for the root and therefore can be attached to the root or to its OUs or accounts. + +```sql+postgres +select + id, + name, + p ->> 'Status' as policy_status, + p ->> 'Type' as policy_type +from + aws_organizations_root, + jsonb_array_elements(policy_types) as p; +``` + +```sql+sqlite +select + id, + name, + json_extract(json_each.value, '$.Status') AS policy_status, + json_extract(json_each.value, '$.Type') AS policy_type +from + aws_organizations_root, + json_each(policy_types) as p; +``` \ No newline at end of file From 1767e849d8389a29fecccac8c2ea6672db51ec68 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Wed, 10 Jan 2024 22:07:23 +0530 Subject: [PATCH 12/17] Updated the doc for aws_organizations_organizational_unit --- .../aws_organizations_organizational_unit.md | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md index dcc3e8710..27934359c 100644 --- a/docs/tables/aws_organizations_organizational_unit.md +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -9,8 +9,21 @@ By default, querying the table without any specific filters will return all OUs ## Examples ### Basic info +This query helps AWS administrators and cloud architects to efficiently manage, audit, and report on the structure and composition of their AWS Organizations. -```sql +```sql+postgres +select + name, + id, + arn, + parent_id, + title, + akas +from + aws_organizations_organizational_unit; +``` + +```sql+sqlite select name, id, @@ -23,8 +36,9 @@ from ``` ### Find a specific organizational unit and all its descendants +By filtering OUs based on their path, the query efficiently retrieves information about a specific subset of your organization's structure, which is particularly useful for large organizations with complex hierarchies. -```sql +```sql+postgres select name, id, @@ -36,9 +50,22 @@ where path <@ 'r_wxnb.ou_wxnb_m8l8t123'; ``` +```sql+sqlite +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + path like 'r_wxnb.ou_wxnb_m8l8t123%' +``` + ### Select all organizational units at a certain level in the hierarchy +Retrieving a list of organizational units (OUs) from a structured hierarchy, specifically those that exist at a particular level. In the context of a database or a management system like AWS Organizations, this involves using a query to filter and display only the OUs that are positioned at the same depth or stage in the hierarchical structure. -```sql +```sql+postgres select name, id, @@ -50,9 +77,23 @@ where nlevel(path) = 3; ``` +```sql+sqlite +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + (length(path) - length(replace(path, '.', ''))) = 2; + +``` + ### Get all ancestors of a given organizational unit +Ancestors are the units in the hierarchy that precede the given OU. An ancestor can be a direct parent (the immediate higher-level unit), or it can be any higher-level unit up to the root of the hierarchy. -```sql +```sql+postgres select name, id, @@ -64,9 +105,42 @@ where 'r_wxnb.ou_wxnb_m8l123aq.ou_wxnb_5gri123b' @> path; ``` +```sql+sqlite +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + path like 'r_wxnb.ou_wxnb_m8l123aq.ou_wxnb_5gri123b%'; +``` + ### Retrieve all siblings of a specific organizational unit +The query is useful for retrieving information about sibling organizational units corresponding to a specified organizational unit. -```sql +```sql+postgres +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + parent_id = + ( + select + parent_id + from + aws_organizations_organizational_unit + where + name = 'Punisher' + ); +``` + +```sql+sqlite select name, id, From 81be37dacb1e48d5c06868b9cdab7b3fdae7e8ca Mon Sep 17 00:00:00 2001 From: ParthaI <47887552+ParthaI@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:42:17 +0530 Subject: [PATCH 13/17] Update aws/table_aws_organizations_organizational_unit.go Co-authored-by: Madhushree Ray --- aws/table_aws_organizations_organizational_unit.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index e77639c4c..f2f83c30f 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -144,6 +144,7 @@ func listAllNestedOUs(ctx context.Context, d *plugin.QueryData, svc *organizatio }) for paginator.HasMorePages() { + // apply rate limiting output, err := paginator.NextPage(ctx) if err != nil { return err From b6ee1302cebbdccae46a695a932940810def92b3 Mon Sep 17 00:00:00 2001 From: ParthaI <47887552+ParthaI@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:42:23 +0530 Subject: [PATCH 14/17] Update aws/table_aws_organizations_root.go Co-authored-by: Madhushree Ray --- aws/table_aws_organizations_root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/table_aws_organizations_root.go b/aws/table_aws_organizations_root.go index 2bbcf5528..f44e6cd47 100644 --- a/aws/table_aws_organizations_root.go +++ b/aws/table_aws_organizations_root.go @@ -94,6 +94,7 @@ func listOrganizationsRoots(ctx context.Context, d *plugin.QueryData, _ *plugin. }) for paginator.HasMorePages() { + // apply rate limiting output, err := paginator.NextPage(ctx) if err != nil { plugin.Logger(ctx).Error("aws_organizations_root.listOrganizationsRoots", "api_error", err) From 5c8bff5a3f9ba75771cdcd46df6bb07373725bdd Mon Sep 17 00:00:00 2001 From: ParthaI <47887552+ParthaI@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:42:30 +0530 Subject: [PATCH 15/17] Update docs/tables/aws_organizations_organizational_unit.md Co-authored-by: Madhushree Ray --- docs/tables/aws_organizations_organizational_unit.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md index 27934359c..dcc6a8d35 100644 --- a/docs/tables/aws_organizations_organizational_unit.md +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -87,7 +87,6 @@ from aws_organizations_organizational_unit where (length(path) - length(replace(path, '.', ''))) = 2; - ``` ### Get all ancestors of a given organizational unit From aa9ba270811499508a933cc04fd0fe8777426a2e Mon Sep 17 00:00:00 2001 From: Madhushree Ray Date: Mon, 15 Jan 2024 13:50:19 +0530 Subject: [PATCH 16/17] Update aws_organizations_organizational_unit.md --- .../aws_organizations_organizational_unit.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md index dcc6a8d35..30b6f6df8 100644 --- a/docs/tables/aws_organizations_organizational_unit.md +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -161,7 +161,7 @@ where ### Select organizational units with a path that matches a specific pattern -```sql +```sql+postgres select name, id, @@ -172,3 +172,15 @@ from where path ~ 'r_wxnb.*.ou_wxnb_m81234aq.*'; ``` + +```sql+sqlite +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +where + path like 'r_wxnb%ou_wxnb_m81234aq%'; +``` From 787f320b25a1cf69f2e4af4f471db52f285c41cd Mon Sep 17 00:00:00 2001 From: Madhushree Ray Date: Mon, 15 Jan 2024 14:03:54 +0530 Subject: [PATCH 17/17] Update aws_organizations_organizational_unit.md --- docs/tables/aws_organizations_organizational_unit.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md index 30b6f6df8..a287fa6e3 100644 --- a/docs/tables/aws_organizations_organizational_unit.md +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -160,6 +160,7 @@ where ``` ### Select organizational units with a path that matches a specific pattern +This query is designed to retrieve organizational units that have a specific hierarchical path pattern within an AWS (Amazon Web Services) organization's structure. ```sql+postgres select