Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add table azure_api_management_backend. Closes #644 #689

Merged
merged 5 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions azure/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
"azure_ad_service_principal": tableAzureAdServicePrincipal(ctx),
"azure_ad_user": tableAzureAdUser(ctx),
"azure_api_management": tableAzureAPIManagement(ctx),
"azure_api_management_backend": tableAzureAPIManagementBackend(ctx),
"azure_app_configuration": tableAzureAppConfiguration(ctx),
"azure_app_service_environment": tableAzureAppServiceEnvironment(ctx),
"azure_app_service_function_app": tableAzureAppServiceFunctionApp(ctx),
Expand Down
225 changes: 225 additions & 0 deletions azure/table_azure_api_management_backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package azure

import (
"context"
"strings"

"github.com/Azure/azure-sdk-for-go/services/apimanagement/mgmt/2020-12-01/apimanagement"
"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"

"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
)

//// TABLE DEFINITION ////

func tableAzureAPIManagementBackend(_ context.Context) *plugin.Table {
return &plugin.Table{
Name: "azure_api_management_backend",
Description: "Azure API Management Backend",
Get: &plugin.GetConfig{
KeyColumns: plugin.AllColumns([]string{"backend_id", "resource_group", "service_name"}),
ParthaI marked this conversation as resolved.
Show resolved Hide resolved
Hydrate: getAPIManagementBackend,
IgnoreConfig: &plugin.IgnoreConfig{
ShouldIgnoreErrorFunc: isNotFoundError([]string{"ResourceNotFound", "ResourceGroupNotFound"}),
},
},
List: &plugin.ListConfig{
ParentHydrate: listAPIManagements,
ParthaI marked this conversation as resolved.
Show resolved Hide resolved
Hydrate: listAPIManagementBackends,
},
Columns: azureColumns([]*plugin.Column{
{
Name: "name",
Type: proto.ColumnType_STRING,
Description: "A friendly name that identifies an API management backend.",
},
{
Name: "id",
Description: "Contains ID to identify an API management backend uniquely.",
Type: proto.ColumnType_STRING,
Transform: transform.FromGo(),
},
{
Name: "url",
Description: "Runtime Url of the Backend.",
ParthaI marked this conversation as resolved.
Show resolved Hide resolved
Type: proto.ColumnType_STRING,
Transform: transform.FromField("BackendContractProperties.URL"),
},
{
Name: "type",
Description: "Resource type for API Management resource.",
Type: proto.ColumnType_STRING,
},
{
Name: "protocol",
Description: "Backend communication protocol. Possible values include: 'BackendProtocolHTTP', 'BackendProtocolSoap'.",
Type: proto.ColumnType_STRING,
Transform: transform.FromField("BackendContractProperties.Protocol"),
},
{
Name: "description",
Description: "Backend Description.",
Type: proto.ColumnType_STRING,
Transform: transform.FromField("BackendContractProperties.Description"),
},
{
Name: "resource_id",
Description: "Backend Description.",
ParthaI marked this conversation as resolved.
Show resolved Hide resolved
Type: proto.ColumnType_STRING,
Transform: transform.FromField("BackendContractProperties.ResourceID"),
},
{
Name: "properties",
Description: "Backend Properties contract.",
Type: proto.ColumnType_JSON,
Transform: transform.FromField("BackendContractProperties.Properties"),
},
{
Name: "credentials",
Description: "Backend Credentials Contract Properties.",
Type: proto.ColumnType_JSON,
Transform: transform.FromField("BackendContractProperties.Credentials"),
},
{
Name: "proxy",
Description: "Backend Proxy Contract Properties.",
Type: proto.ColumnType_JSON,
Transform: transform.FromField("BackendContractProperties.Proxy"),
},
{
Name: "tls",
Description: "Backend TLS Properties.",
Type: proto.ColumnType_JSON,
Transform: transform.FromField("BackendContractProperties.TLS"),
},
{
Name: "service_name",
Description: "Name of the API management service.",
Type: proto.ColumnType_STRING,
},
// We have added this as an extra column because the get call takes only the last path of the id as the backend_id which we do not get from the API
{
Name: "backend_id",
Description: "The API management backend ID.",
Type: proto.ColumnType_STRING,
Transform: transform.FromField("ID").Transform(lastPathElement),
},

// Steampipe standard columns
{
Name: "title",
Description: ColumnDescriptionTitle,
Type: proto.ColumnType_STRING,
Transform: transform.FromField("Name"),
},
{
Name: "akas",
Description: ColumnDescriptionAkas,
Type: proto.ColumnType_JSON,
Transform: transform.FromField("ID").Transform(idToAkas),
},

// Azure standard columns
{
Name: "resource_group",
Description: ColumnDescriptionResourceGroup,
Type: proto.ColumnType_STRING,
Transform: transform.FromField("ID").Transform(extractResourceGroupFromID),
},
}),
}
}

type BackendWithServiceName struct {
apimanagement.BackendContract
ServiceName string
}

//// LIST FUNCTION

func listAPIManagementBackends(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
serviceInfo := h.Item.(apimanagement.ServiceResource)
serviceName := *serviceInfo.Name
resourceGroup := strings.Split(*serviceInfo.ID, "/")[4]

session, err := GetNewSession(ctx, d, "MANAGEMENT")
if err != nil {
return nil, err
}
subscriptionID := session.SubscriptionID

apiManagementBackendClient := apimanagement.NewBackendClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID)
apiManagementBackendClient.Authorizer = session.Authorizer

result, err := apiManagementBackendClient.ListByService(ctx, resourceGroup, serviceName, "", nil, nil)
if err != nil {
plugin.Logger(ctx).Error("listAPIManagementBackends", "list", err)
return nil, err
}
for _, apiManagementBackend := range result.Values() {
backendWithService := &BackendWithServiceName{
apiManagementBackend,
serviceName,
}
d.StreamListItem(ctx, backendWithService)
// Check if context has been cancelled or if the limit has been hit (if specified)
// if there is a limit, it will return the number of rows required to reach this limit
if d.RowsRemaining(ctx) == 0 {
return nil, nil
}
}

for result.NotDone() {
err = result.NextWithContext(ctx)
if err != nil {
plugin.Logger(ctx).Error("listAPIManagementBackends", "list_paging", err)
return nil, err
}

for _, apiManagementBackend := range result.Values() {
d.StreamListItem(ctx, apiManagementBackend)
// Check if context has been cancelled or if the limit has been hit (if specified)
// if there is a limit, it will return the number of rows required to reach this limit
if d.RowsRemaining(ctx) == 0 {
return nil, nil
}
}
}

return nil, err
}

//// HYDRATE FUNCTIONS

func getAPIManagementBackend(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
plugin.Logger(ctx).Debug("getAPIManagementBackend")
ParthaI marked this conversation as resolved.
Show resolved Hide resolved

backendID := d.EqualsQualString("backend_id")
serviceName := d.EqualsQualString("service_name")
resourceGroup := d.EqualsQualString("resource_group")

// resourceGroupName can't be empty
// Error: pq: rpc error: code = Unknown desc = apimanagement.ServiceClient#Get: Invalid input: autorest/validation: validation failed: parameter=serviceName
// constraint=MinLength value="" details: value length must be greater than or equal to 1
if len(backendID) < 1 {
return nil, nil
}

session, err := GetNewSession(ctx, d, "MANAGEMENT")
if err != nil {
return nil, err
}
subscriptionID := session.SubscriptionID

apiManagementBackendClient := apimanagement.NewBackendClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID)
apiManagementBackendClient.Authorizer = session.Authorizer

op, err := apiManagementBackendClient.Get(ctx, resourceGroup, serviceName, backendID)
if err != nil {
plugin.Logger(ctx).Error("getAPIManagementBackend", "get", err)
ParthaI marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}

return BackendWithServiceName{op, serviceName}, nil
}
59 changes: 59 additions & 0 deletions docs/tables/azure_api_management_backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Table: azure_api_management_backend

The "Backend" in API Management represents the configuration of the web service/API on the backend that the gateway will forward the request to, after the request has been accepted and processed by APIM's policies. An API Management Backend provides details about the destination of the API requests. Backends in APIM can be, for instance, a web service running on Azure App Service, a virtual machine, or even an external API hosted outside of Azure. They play a critical role in the end-to-end API request and response lifecycle within APIM.

## Examples

### Basic Info

```sql
select
name,
id,
protocol,
service_name,
url,
type
from
azure_api_management_backend;
```

### List the backend credential contract properties

```sql
select
name,
id,
credentials -> 'authorization' ->> 'parameter' as parameter,
credentials -> 'authorization' ->> 'scheme' as scheme
from
azure_api_management_backend;
```

### Get the TLS configuration for a particular backend service

```sql
select
name,
id,
tls -> 'validateCertificateChain' as tls_validate_certificate_chain,
tls -> 'validateCertificateName' as tls_validate_certificate_name,
from
azure_api_management_backend;
```

### List backends that follow http protocol

```sql
select
name,
id,
protocol,
service_name,
url,
type
from
azure_api_management_backend
where
protocol = 'http';
```