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
2 changes: 2 additions & 0 deletions apps/event-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"dotenv": "^16.4.5",
"google-auth-library": "^9.13.0",
"ioredis": "^5.4.1",
"js-yaml": "^4.1.0",
"lodash": "catalog:",
"ms": "^2.1.3",
"semver": "^7.6.2",
Expand All @@ -44,6 +45,7 @@
"@ctrlplane/eslint-config": "workspace:^",
"@ctrlplane/prettier-config": "workspace:^",
"@ctrlplane/tsconfig": "workspace:*",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "catalog:",
"@types/ms": "^0.7.34",
"@types/node": "catalog:node20",
Expand Down
10 changes: 5 additions & 5 deletions apps/event-worker/src/resource-scan/azure/aks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export const getAksResources = async (
}

const { resourceProviderId: providerId } = azureProvider;
return clusters
.map((cluster) =>
convertManagedClusterToResource(workspace.id, providerId, cluster),
)
.filter(isPresent);
const resourcePromises = clusters.map((cluster) =>
convertManagedClusterToResource(workspace.id, providerId, cluster, client),
);
const resources = await Promise.all(resourcePromises);
return resources.filter(isPresent);
};
69 changes: 57 additions & 12 deletions apps/event-worker/src/resource-scan/azure/cluster-to-resource.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { ManagedCluster } from "@azure/arm-containerservice";
import type {
ContainerServiceClient,
ManagedCluster,
} from "@azure/arm-containerservice";
import type { KubernetesClusterAPIV1 } from "@ctrlplane/validators/resources";
import * as yaml from "js-yaml";
import { z } from "zod";

import { logger } from "@ctrlplane/logger";
import { ReservedMetadataKey } from "@ctrlplane/validators/conditions";
Expand All @@ -13,16 +18,63 @@ type ClusterResource = KubernetesClusterAPIV1 & {
providerId: string;
};

export const convertManagedClusterToResource = (
const cluster = z.object({
"certificate-authority-data": z.string(),
server: z.string(),
});
const kubeConfigSchema = z.object({ clusters: z.array(z.object({ cluster })) });

const getCertificateAuthorityData = async (
cluster: ManagedCluster,
resourceGroup: string,
client: ContainerServiceClient,
) => {
try {
const { kubernetesVersion, name } = cluster;
if (!kubernetesVersion || !name) return null;

const kubeConfigRaw = await client.managedClusters
.getAccessProfile(resourceGroup, name, "clusterAdmin")
.then((profile) => profile.kubeConfig);
if (!kubeConfigRaw) return null;

const kubeConfigYaml = Buffer.from(kubeConfigRaw).toString("utf-8");
const kubeConfig = yaml.load(kubeConfigYaml);

const parsedKubeConfig = kubeConfigSchema.parse(kubeConfig);
const { cluster: parsedCluster } = parsedKubeConfig.clusters[0] ?? {};
if (!parsedCluster) return null;
return {
endpoint: parsedCluster.server,
certificateAuthorityData: parsedCluster["certificate-authority-data"],
};
} catch (error) {
log.error("Error getting certificate authority data for cluster", {
cluster: { name: cluster.name, id: cluster.id },
error,
});
return null;
}
};

export const convertManagedClusterToResource = async (
workspaceId: string,
providerId: string,
cluster: ManagedCluster,
): ClusterResource | null => {
client: ContainerServiceClient,
): Promise<ClusterResource | null> => {
if (!cluster.name || !cluster.id) {
log.error("Invalid cluster", { cluster });
return null;
}

const resourceGroup = cluster.id.split("/resourcegroups/")[1]?.split("/")[0];
if (!resourceGroup) {
log.error("Invalid cluster", { cluster });
return null;
}
Comment on lines +71 to +75
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Parsing resourceGroup from cluster.id could be more robust

Using string splitting to extract the resourceGroup may be error-prone if the ID format changes. Consider using a more reliable method to parse the resource group.

You can use Azure's ResourceIdentifier class or a regular expression for parsing:

-import resourceGroup from cluster.id.split("/resourcegroups/")[1]?.split("/")[0];
+import { parseResourceId } from "@azure/arm-resourcegraph";

+const parsedId = parseResourceId(cluster.id);
+const resourceGroup = parsedId.resourceGroupName;

if (!resourceGroup) {
  log.error("Invalid cluster ID format", { cluster });
  return null;
}

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that function simply does not exist

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityachoudhari26 I apologize for my mistake. You are correct - I suggested using a non-existent function. The current string splitting approach is actually fine and commonly used in the codebase. Please disregard my previous suggestion.

🔗 Analysis chain
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Azure SDK packages in package.json
rg "@azure/" apps/event-worker/package.json

# Search for any resource ID parsing utilities in the codebase
rg -i "parseresource|resourceid" --type ts

Length of output: 41358


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#282
File: apps/event-worker/src/resource-scan/azure/cluster-to-resource.ts:71-75
Timestamp: 2025-01-18T06:38:00.912Z
Learning: The Azure resource ID parsing in the codebase is done using string splitting (e.g., `id.split("/resourcegroups/")[1]?.split("/")[0]`) rather than any SDK utility functions.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


const ca = await getCertificateAuthorityData(cluster, resourceGroup, client);
return {
workspaceId,
providerId,
Expand All @@ -32,16 +84,9 @@ export const convertManagedClusterToResource = (
kind: "ClusterAPI",
config: {
name: cluster.name,
auth: {
method: "azure/aks",
clusterName: cluster.name,
resourceGroup: cluster.nodeResourceGroup ?? "",
},
auth: { method: "azure/aks", clusterName: cluster.name, resourceGroup },
status: cluster.provisioningState ?? "UNKNOWN",
server: {
endpoint: cluster.fqdn ?? "",
certificateAuthorityData: cluster.servicePrincipalProfile?.clientId,
},
server: { ...ca, endpoint: ca?.endpoint ?? cluster.fqdn ?? "" },
Comment on lines +87 to +89
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Explicit null handling recommended for server configuration

The review suggestion is correct. The current spread pattern silently drops properties when ca is null, and differs from similar implementations in AWS EKS and Google GKE providers which use explicit property assignments. The suggested ternary operator approach is safer and more explicit.

🔗 Analysis chain

Ensure server configuration is correctly constructed

The server configuration merges ca data. If ca is null, the spread operator will not add any properties. Confirm that endpoint is always correctly assigned.

Consider explicitly handling the scenario where ca is null:

-server: { ...ca, endpoint: ca?.endpoint ?? cluster.fqdn ?? "" },
+server: ca
+  ? { ...ca }
+  : { endpoint: cluster.fqdn ?? "" },
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Find type definitions and usage of ca variable
ast-grep --pattern 'interface $_ { endpoint: $_ }' 
rg "ca\s*:\s*{.*endpoint" -A 5
rg "server:\s*{.*endpoint" -A 5

# Find the assignment of ca variable
ast-grep --pattern 'const ca = $_'

Length of output: 532


Script:

#!/bin/bash
# Find type definitions and implementations
rg "interface.*Server" -A 5
rg "type.*Server" -A 5

# Find getCertificateAuthorityData implementation
ast-grep --pattern 'async function getCertificateAuthorityData($_) {
  $$$
}'

# Find similar server configurations with fixed regex
rg "server: \{" -A 3
rg "ca: \{" -A 3

Length of output: 4393


Script:

#!/bin/bash
# Find getCertificateAuthorityData implementation with a different pattern
rg "getCertificateAuthorityData" -A 10
rg "function getCertificateAuthorityData" -A 10

# Find any type definitions for the server object
rg "certificateAuthorityData.*endpoint" -A 3

Length of output: 2278

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

certificateAuthorityData expects string | null | undefined which is the exact type which ca.certificateAuthorityData has, so this spread should be safe

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityachoudhari26 You're absolutely right. I've verified that certificateAuthorityData is defined as z.string().nullish() in the validators package, which means it accepts string | null | undefined. The current spread operation is type-safe and consistent with how certificate authority data is handled across other providers like AWS EKS and Google GKE.

🔗 Analysis chain
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find type definitions for ClusterResource and KubernetesClusterAPIV1
rg "type ClusterResource" -A 10
rg "interface KubernetesClusterAPIV1" -A 10
rg "certificateAuthorityData.*:" -A 1

# Find the return type and implementation of getCertificateAuthorityData
ast-grep --pattern 'const getCertificateAuthorityData = async ($_) => {
  $$$
}'

Length of output: 2773


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#282
File: apps/event-worker/src/resource-scan/azure/cluster-to-resource.ts:87-89
Timestamp: 2025-01-18T06:38:46.011Z
Learning: In the AKS provider, certificateAuthorityData is type-safe with string | null | undefined, allowing the spread operation of ca object in server configuration. This is consistent with AWS EKS and Google GKE implementations.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

},
metadata: omitNullUndefined({
[ReservedMetadataKey.Links]: cluster.azurePortalFqdn
Expand Down
28 changes: 14 additions & 14 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading