Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
75 changes: 74 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Please contact us if you need any further information.
|--------------------------------------------------------------------------|-------------------------------------------------------------|
| Instance | [Application Gateways](#application-gateways) |
| Container | [Container Instances](#container-instances) |
| Container | [Container Registries](#container-registries) |
| Instance | [CosmosDB](#cosmos-db) |
| Disk | [Disks](#disks) |
| Instance | [KeyVaults](#key-vaults) |
Expand All @@ -49,6 +50,7 @@ Please contact us if you need any further information.
| ScaleSet | [VM ScaleSets](#virtual-machine-scale-sets) |
| Service | [Web PubSub Service](#web-pubsub-service) |
| Score<br>OperationalExcellence<br>Performance<br>Reliability<br>Security | [Advisor](#advisor) |
| Instance | [Functions](#functions) |


---
Expand Down Expand Up @@ -111,6 +113,8 @@ The following is a list of services being collected and service code information
| 20 | Container Instances | Microsoft.ContainerInstance/containerGroups |
| 21 | Web PubSub Service | Microsoft.SignalRService/WebPubSub |
| 22 | Advisor | Microsoft.Advisor/advisorScore<br>Microsoft.ResourceHealth/events |
| 23 | Container Registries | Microsoft.ContainerRegistry/registries |
| 24 | Functions | Microsoft.Web/sites |

---

Expand Down Expand Up @@ -836,6 +840,43 @@ Deprecated)
```
"Microsoft.ContainerInstance/containerGroups/read"
```
#### [Container Registries](https://learn.microsoft.com/ko-kr/python/api/azure-mgmt-containerregistry/azure.mgmt.containerregistry.containerregistrymanagementclient?view=azure-python)
- Container Registries
- Scope
- https://learn.microsoft.com/ko-kr/python/api/azure-mgmt-containerregistry/azure.mgmt.containerregistry.containerregistrymanagementclient?view=azure-python
- registries
- list()
- get()
- list_usages()
- webhooks
- list()
- replications
- list()
- tasks
- list()
- connected_registries
- list()
- cache_rules
- list()
- tokens
- list()
- scope_maps
- list()
- Permissions
```
"Microsoft.ContainerRegistry/registries/read",
"Microsoft.ContainerRegistry/registries/listUsages/read",
"Microsoft.ContainerRegistry/registries/webhooks/read",
"Microsoft.ContainerRegistry/registries/replications/read",
"Microsoft.ContainerRegistry/registries/tasks/read",
"Microsoft.ContainerRegistry/registries/connectedRegistries/read",
"Microsoft.ContainerRegistry/registries/cacheRules/read",
"Microsoft.ContainerRegistry/registries/tokens/read",
"Microsoft.ContainerRegistry/registries/scopeMaps/read",
"Microsoft.Resources/subscriptions/resourceGroups/read"
```
#### [Web PubSub Service](https://learn.microsoft.com/en-us/python/api/overview/azure/web-pubsub?view=azure-python)
Expand Down Expand Up @@ -863,6 +904,35 @@ Deprecated)
- list()
- Permissions
#### [Functions](https://learn.microsoft.com/ko-kr/python/api/azure-mgmt-web/azure.mgmt.web.websitemanagementclient?view=azure-python)
- Functions
- Scope
- https://learn.microsoft.com/ko-kr/python/api/azure-mgmt-web/azure.mgmt.web.websitemanagementclient?view=azure-python
- web_apps
- list()
- get()
- list_private_endpoint_connections()
- list_hybrid_connections()
- app_service_plans
- get()
- https://learn.microsoft.com/ko-kr/python/api/azure-mgmt-network/azure.mgmt.network.networkmanagementclient?view=azure-python
- virtual_networks
- get()
- subnets
- get()
- Permissions
```
"Microsoft.Web/*/read",
"Microsoft.Network/virtualNetworks/read",
"Microsoft.Network/virtualNetworks/subnets/read",
"Microsoft.Network/privateEndpoints/read",
"Microsoft.Network/publicIPAddresses/read",
"Microsoft.Network/natGateways/read",
"Microsoft.Network/networkSecurityGroups/read",
"Microsoft.Network/routeTables/read",
"Microsoft.Resources/*/read"
```
---
## Options
Expand Down Expand Up @@ -896,7 +966,9 @@ The cloud_service_types items that can be specified are as follows.
'VirtualNetworks',
'VMScaleSets',
'ContainerInstances',
'WebPubSubService'
'WebPubSubService',
"ContainerRegistries",
"Functions"
]
}
</code>
Expand Down Expand Up @@ -962,6 +1034,7 @@ The default ASSET_URL in cloud_service_conf is
| Version | Description | Affected Service | Release Date |
|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|--------------|
| 2.0.9 | - Add Azure Functions, Container Registries | Functions, Container Registries | 2025.12.03 |
| 2.0.8 | - Add Azure Advisor service | | |
| 2.0.5 | - Add Azure Cognitive service | | |
| 2.0.0 | - [Migration to spaceone framework 2.0](https://github.com/cloudforet-io/plugin-azure-inven-collector/issues/91) | All Services | 2024.08.22 |
Expand Down
1 change: 0 additions & 1 deletion src/plugin/connector/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient
from azure.mgmt.compute import ComputeManagementClient
from azure.mgmt.containerinstance import ContainerInstanceManagementClient
# Container Registry 클라이언트 import 추가
from azure.mgmt.containerregistry import ContainerRegistryManagementClient
from azure.mgmt.cosmosdb import CosmosDBManagementClient
from azure.mgmt.keyvault import KeyVaultManagementClient
Expand Down
123 changes: 39 additions & 84 deletions src/plugin/manager/container_registries/registry_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,20 @@ def create_cloud_service_type(self):
)

def create_cloud_service(self, options, secret_data, schema):
# Collect container registry resources
cloud_services = []
error_responses = []

container_registries_conn = ContainerRegistriesConnector(
secret_data=secret_data
)
container_registries_conn = ContainerRegistriesConnector(secret_data=secret_data)
subscription_conn = SubscriptionsConnector(secret_data=secret_data)

subscription_raw = subscription_conn.get_subscription(
secret_data["subscription_id"]
)
# Load subscription info
subscription_raw = subscription_conn.get_subscription(secret_data["subscription_id"])
subscription_info = self.convert_nested_dictionary(subscription_raw)

# Validate subscription info
if not isinstance(subscription_info, dict):
_LOGGER.error(
"[ContainerRegistries] invalid subscription_info. raw=%r",
subscription_raw,
)
_LOGGER.error("[ContainerRegistries] Invalid subscription info")
error_responses.append(
make_error_response(
error=Exception("Invalid subscription info"),
Expand All @@ -60,33 +56,25 @@ def create_cloud_service(self, options, secret_data, schema):
)
return cloud_services, error_responses

# Iterate registry list
for registry in container_registries_conn.list_registries():
try:
registry_dict = self.convert_nested_dictionary(registry)
if not isinstance(registry_dict, dict):
_LOGGER.error(
"[ContainerRegistries] registry list item is not dict. raw=%r",
registry,
)
continue

registry_id = registry_dict["id"]
resource_group_name = self.get_resource_group_from_id(registry_id)
registry_name = registry_dict["name"]

# Fetch full registry detail
registry_dict = self.convert_nested_dictionary(
container_registries_conn.get_registry(
resource_group_name, registry_name
)
container_registries_conn.get_registry(resource_group_name, registry_name)
)

if not isinstance(registry_dict, dict):
_LOGGER.error(
"[ContainerRegistries] registry dict is not dict. "
f"rg={resource_group_name}, name={registry_name}, raw={registry_dict}"
)
continue

# Base fields
registry_dict.update(
{
"resource_group": resource_group_name,
Expand All @@ -96,27 +84,18 @@ def create_cloud_service(self, options, secret_data, schema):
}
)

# Populate registry data
self._set_settings_info(registry_dict)

self._set_services_info(
container_registries_conn,
registry_dict,
resource_group_name,
registry_name,
)

self._set_services_info(container_registries_conn, registry_dict, resource_group_name, registry_name)
self._set_repository_permissions_info(
container_registries_conn,
registry_dict,
resource_group_name,
registry_name,
container_registries_conn, registry_dict, resource_group_name, registry_name
)

registry_dict = self.update_tenant_id_from_secret_data(
registry_dict, secret_data
)
# Common metadata
registry_dict = self.update_tenant_id_from_secret_data(registry_dict, secret_data)
self.set_region_code(registry_dict["location"])

# Build CloudService
cloud_services.append(
make_cloud_service(
name=registry_dict["name"],
Expand All @@ -131,11 +110,9 @@ def create_cloud_service(self, options, secret_data, schema):
data_format="dict",
)
)

except Exception as e:
_LOGGER.error(
f"[create_cloud_service] Error {self.service_code} {e}",
exc_info=True,
)
_LOGGER.error(f"[create_cloud_service] Error {self.service_code}: {e}", exc_info=True)
error_responses.append(
make_error_response(
error=e,
Expand All @@ -148,20 +125,17 @@ def create_cloud_service(self, options, secret_data, schema):
return cloud_services, error_responses

def _set_settings_info(self, registry_dict):
# Populate Settings category
if not isinstance(registry_dict, dict):
_LOGGER.error(
"[ContainerRegistries] _set_settings_info called with non-dict: %r",
registry_dict,
)
return

registry_dict["admin_user_enabled"] = registry_dict.get(
"admin_user_enabled", False
)
registry_dict["admin_user_enabled"] = registry_dict.get("admin_user_enabled", False)

# Identity mapping
identities = []
if identity_info := registry_dict.get("identity"):
identity_type = identity_info.get("type", "None")

if "SystemAssigned" in identity_type:
identities.append(
{
Expand All @@ -173,8 +147,8 @@ def _set_settings_info(self, registry_dict):
)

for ua_id in identity_info.get("user_assigned_identities", {}):
try:
parts = ua_id.split("/")
parts = ua_id.split("/")
if len(parts) >= 5:
identities.append(
{
"name": parts[-1],
Expand All @@ -183,42 +157,33 @@ def _set_settings_info(self, registry_dict):
"subscription_id": parts[2],
}
)
except Exception:
else:
identities.append({"name": ua_id, "type": "UserAssigned"})
registry_dict["identities"] = identities

registry_dict["private_endpoints"] = [] # Placeholder

# Policies
policies = registry_dict.get("policies", {})
registry_dict["domain_name_label_scope"] = policies.get(
"quarantine_policy", {}
).get("status", "Unsecured")
registry_dict["domain_name_label_scope"] = policies.get("quarantine_policy", {}).get("status", "Unsecured")
registry_dict["soft_delete_enabled"] = (
policies.get("retention_policy", {}).get("status", "disabled") == "enabled"
)
registry_dict.setdefault("role_assignment_mode", "RBAC_REGISTRY")

registry_dict["locks"] = [] # Placeholder
registry_dict["private_endpoints"] = []
registry_dict["locks"] = []

def _set_services_info(self, conn, registry_dict, rg, name):
# Populate Services category
if not isinstance(registry_dict, dict):
_LOGGER.error(
"[ContainerRegistries] _set_services_info called with non-dict: %r",
registry_dict,
)
return

registry_dict["repositories"] = []

webhooks = conn.list_webhooks(rg, name)
registry_dict["webhooks"] = [
self.convert_nested_dictionary(w) for w in webhooks
]
registry_dict["webhooks"] = [self.convert_nested_dictionary(w) for w in webhooks]

replications = conn.list_replications(rg, name)
registry_dict["replications"] = [
self.convert_nested_dictionary(r) for r in replications
]
registry_dict["replications"] = [self.convert_nested_dictionary(r) for r in replications]

tasks = conn.list_tasks(rg, name)
registry_dict["tasks"] = [self.convert_nested_dictionary(t) for t in tasks]
Expand All @@ -229,38 +194,28 @@ def _set_services_info(self, conn, registry_dict, rg, name):
]

cache_rules = conn.list_cache_rules(rg, name)
registry_dict["cache_rules"] = [
self.convert_nested_dictionary(rule) for rule in cache_rules
]
registry_dict["cache_rules"] = [self.convert_nested_dictionary(rule) for rule in cache_rules]
registry_dict["cache_credentials"] = []

def _set_repository_permissions_info(self, conn, registry_dict, rg, name):
# Populate Repository Permissions category
if not isinstance(registry_dict, dict):
_LOGGER.error(
"[ContainerRegistries] _set_repository_permissions_info called with non-dict: %r",
registry_dict,
)
return

tokens = conn.list_tokens(rg, name)
token_list = []
for t in tokens:
td = self.convert_nested_dictionary(t)

if not isinstance(td, dict):
_LOGGER.error(
"[ContainerRegistries] token item is not dict. raw=%r",
t,
)
continue

if creds := td.get("credentials"):
creds = td.get("credentials")
if creds:
td["password1_expiry"] = creds.get("password1", {}).get("expiry")
td["password2_expiry"] = creds.get("password2", {}).get("expiry")

token_list.append(td)
registry_dict["tokens"] = token_list

registry_dict["tokens"] = token_list
scope_maps = conn.list_scope_maps(rg, name)
registry_dict["scope_maps"] = [
self.convert_nested_dictionary(s) for s in scope_maps
]
registry_dict["scope_maps"] = [self.convert_nested_dictionary(s) for s in scope_maps]
Loading
Loading