From 53aecf3845ef82a7ba2f3cecabc43a88130957f2 Mon Sep 17 00:00:00 2001 From: ParthaI <47887552+ParthaI@users.noreply.github.com> Date: Mon, 12 Feb 2024 23:23:45 +0530 Subject: [PATCH] Add table aws_organizations_organizational_unit back to the table list (#2063) Co-authored-by: Ved misra <47312748+misraved@users.noreply.github.com> --- aws/plugin.go | 2 +- ...e_aws_organizations_organizational_unit.go | 372 +++++++++--------- .../aws_organizations_organizational_unit.md | 194 +++++++++ 3 files changed, 385 insertions(+), 183 deletions(-) create mode 100644 docs/tables/aws_organizations_organizational_unit.md diff --git a/aws/plugin.go b/aws/plugin.go index 3abe5fd31..ed993efa9 100644 --- a/aws/plugin.go +++ b/aws/plugin.go @@ -338,7 +338,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_organizational_unit": tableAwsOrganizationsOrganizationalUnit(ctx), "aws_organizations_policy": tableAwsOrganizationsPolicy(ctx), "aws_organizations_policy_target": tableAwsOrganizationsPolicyTarget(ctx), "aws_organizations_root": tableAwsOrganizationsRoot(ctx), diff --git a/aws/table_aws_organizations_organizational_unit.go b/aws/table_aws_organizations_organizational_unit.go index 53b9dd976..95f9170a4 100644 --- a/aws/table_aws_organizations_organizational_unit.go +++ b/aws/table_aws_organizations_organizational_unit.go @@ -1,184 +1,192 @@ package aws -// Commenting out the table for the time being because, we need to hove the column type LTREE first then we can add it back. - -// 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{ -// Name: "aws_organizations_organizational_unit", -// Description: "AWS Organizations Organizational Unit", -// List: &plugin.ListConfig{ -// 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{ -// { -// Name: "name", -// Description: "The friendly name of this OU.", -// Hydrate: getOrganizationsOrganizationalUnit, -// 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.", -// Hydrate: getOrganizationsOrganizationalUnit, -// 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, -// }, -// { -// 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 -// { -// 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), -// }, -// }), -// } -// } - -// type OrganizationalUnit struct { -// types.OrganizationalUnit -// Path string -// ParentId string -// } - -// //// LIST FUNCTION - -// func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { -// 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") -// } - -// // 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) - -// // 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) -// } -// } - -// // 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, -// } - -// paginator := organizations.NewListOrganizationalUnitsForParentPaginator(svc, params, func(o *organizations.ListOrganizationalUnitsForParentPaginatorOptions) { -// o.Limit = maxItems -// o.StopOnDuplicateToken = true -// }) - -// for paginator.HasMorePages() { -// // apply rate limiting -// output, err := paginator.NextPage(ctx) -// if err != nil { -// return err -// } - -// for _, unit := range output.OrganizationalUnits { -// 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 -// } - -// if d.RowsRemaining(ctx) == 0 { -// return nil -// } -// } -// } - -// return nil -// } - -// //// HYDRATE FUNCTIONS - -// func getOrganizationsOrganizationalUnit(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { -// orgUnitId := *h.Item.(OrganizationalUnit).Id - -// // 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 -// } +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", + List: &plugin.ListConfig{ + 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{ + { + Name: "name", + Description: "The friendly name of this OU.", + Hydrate: getOrganizationsOrganizationalUnit, + 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.", + Hydrate: getOrganizationsOrganizationalUnit, + 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, + }, + { + 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 + { + 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), + }, + }), + } +} + +type OrganizationalUnit struct { + types.OrganizationalUnit + Path string + ParentId string +} + +//// LIST FUNCTION + +func listOrganizationsOrganizationalUnits(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + 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") + } + + // 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) + + // 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) + } + } + + // 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, + } + + paginator := organizations.NewListOrganizationalUnitsForParentPaginator(svc, params, func(o *organizations.ListOrganizationalUnitsForParentPaginatorOptions) { + o.Limit = maxItems + o.StopOnDuplicateToken = true + }) + + for paginator.HasMorePages() { + // apply rate limiting + output, err := paginator.NextPage(ctx) + if err != nil { + return err + } + + for _, unit := range output.OrganizationalUnits { + 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 + } + + if d.RowsRemaining(ctx) == 0 { + return nil + } + } + } + + return nil +} + +//// HYDRATE FUNCTIONS + +func getOrganizationsOrganizationalUnit(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + orgUnitId := *h.Item.(OrganizationalUnit).Id + + // 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 +} diff --git a/docs/tables/aws_organizations_organizational_unit.md b/docs/tables/aws_organizations_organizational_unit.md new file mode 100644 index 000000000..113ed712f --- /dev/null +++ b/docs/tables/aws_organizations_organizational_unit.md @@ -0,0 +1,194 @@ +--- +title: "Steampipe Table: aws_organizations_organizational_unit - Query AWS Organizations Organizational Units using SQL" +description: "Allows users to query AWS Organizations Organizational Units and provides information about each OU." +--- + +# 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. + +## Table Usage Guide + +The `aws_organizations_organizational_unit` table in Steampipe provides you with information about 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 + +### 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+postgres +select + name, + id, + arn, + parent_id, + title, + akas +from + aws_organizations_organizational_unit; +``` + +```sql+sqlite +select + name, + id, + arn, + parent_id, + title, + akas +from + aws_organizations_organizational_unit; +``` + +### 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+postgres +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +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+postgres +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +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+postgres +select + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +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+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, + 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 +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 + name, + id, + parent_id, + path +from + aws_organizations_organizational_unit +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%'; +```