Skip to content

Commit

Permalink
switch Azure provisioning to new preflight endpoint (#4326)
Browse files Browse the repository at this point in the history
  • Loading branch information
d-g-town authored Feb 26, 2024
1 parent e248ee1 commit dab8157
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 113 deletions.
2 changes: 1 addition & 1 deletion .github/golangci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@ output:
path-prefix: ""

# sorts results by: filepath, line and column
sort-results: false
sort-results: false
45 changes: 44 additions & 1 deletion api/server/handlers/project_integration/preflight_check.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package project_integration

import (
"fmt"
"net/http"

"connectrpc.com/connect"
Expand Down Expand Up @@ -57,7 +58,7 @@ var recognizedPreflightCheckKeys = []string{
"apiEnabled",
"cidrAvailability",
"iamPermissions",
"resourceProviders",
"authz",
}

func (p *CreatePreflightCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -91,6 +92,48 @@ func (p *CreatePreflightCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.R
}
}

if cloudValues.Contract != nil && cloudValues.Contract.Cluster != nil && cloudValues.Contract.Cluster.CloudProvider == porterv1.EnumCloudProvider_ENUM_CLOUD_PROVIDER_AZURE {
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "new-endpoint", Value: true})
checkResp, err := p.Config().ClusterControlPlaneClient.CloudContractPreflightCheck(ctx,
connect.NewRequest(
&porterv1.CloudContractPreflightCheckRequest{
Contract: cloudValues.Contract,
},
),
)
if err != nil {
err = telemetry.Error(ctx, span, err, "error calling preflight checks")
p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

if checkResp.Msg == nil {
err = telemetry.Error(ctx, span, nil, "no message received from preflight checks")
p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

errors := []PreflightCheckError{}
for _, val := range checkResp.Msg.FailingPreflightChecks {
if val.Message == "" || !contains(recognizedPreflightCheckKeys, val.Type) {
continue
}

fmt.Printf("val: %+v\n", val.Metadata)

errors = append(errors, PreflightCheckError{
Name: val.Type,
Error: PorterError{
Message: val.Message,
Metadata: val.Metadata,
},
})
}
resp.Errors = errors
p.WriteResult(w, r, resp)
return
}

checkResp, err := p.Config().ClusterControlPlaneClient.PreflightCheck(ctx, connect.NewRequest(&input))
if err != nil {
err = telemetry.Error(ctx, span, err, "error calling preflight checks")
Expand Down
33 changes: 28 additions & 5 deletions dashboard/src/components/porter/Error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import styled from "styled-components";

import Modal from "./Modal";
import Spacer from "./Spacer";
import Text from "./Text";

type Props = {
message: string;
metadata?: Record<string, string>;
ctaText?: string;
ctaOnClick?: () => void;
errorModalContents?: React.ReactNode;
Expand All @@ -14,6 +16,7 @@ type Props = {

export const Error: React.FC<Props> = ({
message,
metadata,
ctaText,
ctaOnClick,
errorModalContents,
Expand All @@ -26,10 +29,18 @@ export const Error: React.FC<Props> = ({
<StyledError maxWidth={maxWidth}>
<i className="material-icons">error_outline</i>
<Block>
<Text>Error: {message}</Text>
<Text color={"#ff385d"}>Error: {message}</Text>
{ctaText && (errorModalContents != null || ctaOnClick != null) && (
<>
<Spacer y={0.5} />
{metadata &&
Object.entries(metadata).map(([key, value]) => (
<div key={key}>
<ErrorMessageLabel>{key}:</ErrorMessageLabel>
<ErrorMessageContent>{value}</ErrorMessageContent>
</div>
))}
<Spacer y={0.5} />
<Cta
onClick={() => {
errorModalContents ? setErrorModalOpen(true) : ctaOnClick?.();
Expand Down Expand Up @@ -57,10 +68,6 @@ export const Error: React.FC<Props> = ({

export default Error;

const Text = styled.span`
display: inline;
`;

const Block = styled.div`
display: block;
`;
Expand Down Expand Up @@ -101,3 +108,19 @@ const StyledError = styled.div<{ maxWidth?: string }>`
}
max-width: ${(props) => props.maxWidth || "100%"};
`;

const ErrorMessageLabel = styled.span`
font-weight: bold;
margin-left: 10px;
color: #9999aa;
user-select: text;
`;
const ErrorMessageContent = styled.div`
font-family: "Courier New", Courier, monospace;
padding: 5px 10px;
border-radius: 4px;
margin-left: 10px;
user-select: text;
cursor: text;
color: #9999aa;
`;
4 changes: 2 additions & 2 deletions dashboard/src/components/porter/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ const Underline = styled.div<{ color: string }>`
background: ${(props) => props.color};
`;

const StyledLink = styled(DynamicLink) <{ hasunderline?: boolean, color: string }>`
const StyledLink = styled(DynamicLink) <{ hasunderline?: boolean, color: string, removeInline?: boolean }>`
color: ${(props) => props.color};
display: inline-flex;
${(props) => !props.removeInline && "display: inline-flex;"};
font-size: 13px;
cursor: pointer;
align-items: center;
Expand Down
9 changes: 3 additions & 6 deletions dashboard/src/components/porter/Step.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import Container from "./Container";

type Props = {
number: number;
Expand All @@ -13,19 +14,15 @@ const Step: React.FC<Props> = ({
return (
<StyledStep>
<StepNumber>{number}</StepNumber>
<Block>
<Container>
{children}
</Block>
</Container>
</StyledStep>
);
};

export default Step;

const Block = styled.div`
display: block;
`;

const StepNumber = styled.div`
height: 20px;
min-width: 20px;
Expand Down
97 changes: 92 additions & 5 deletions dashboard/src/lib/clusters/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ const AWS_EIP_QUOTA_RESOLUTION: PreflightCheckResolution = {
"You will need to either request more EIP addresses or delete existing ones in order to provision in the region specified. You can request more addresses by following these steps:",
steps: [
{
text: "Log into your AWS Account",
text: "Log in to your AWS Account",
externalLink:
"https://console.aws.amazon.com/billing/home?region=us-east-1#/account",
},
Expand All @@ -1053,7 +1053,7 @@ const AWS_NAT_GATEWAY_QUOTA_RESOLUTION: PreflightCheckResolution = {
"You will need to either request more NAT Gateways or delete existing ones in order to provision in the region specified. You can request more NAT Gateways by following these steps:",
steps: [
{
text: "Log into your AWS Account",
text: "Log in to your AWS Account",
externalLink:
"https://console.aws.amazon.com/billing/home?region=us-east-1#/account",
},
Expand All @@ -1079,7 +1079,7 @@ const AWS_VPC_QUOTA_RESOLUTION: PreflightCheckResolution = {
"You will need to either request more VPCs or delete existing ones in order to provision in the region specified. You can request more VPCs by following these steps:",
steps: [
{
text: "Log into your AWS Account",
text: "Log in to your AWS Account",
externalLink:
"https://console.aws.amazon.com/billing/home?region=us-east-1#/account",
},
Expand All @@ -1105,7 +1105,7 @@ const AWS_VCPUS_QUOTA_RESOLUTION: PreflightCheckResolution = {
"You will need to either request more vCPUs or delete existing instances in order to provision in the region specified. You can request more vCPUs by following these steps:",
steps: [
{
text: "Log into your AWS Account",
text: "Log in to your AWS Account",
externalLink:
"https://console.aws.amazon.com/billing/home?region=us-east-1#/account",
},
Expand All @@ -1125,6 +1125,75 @@ const AWS_VCPUS_QUOTA_RESOLUTION: PreflightCheckResolution = {
},
],
};
const AZURE_AUTHZ_RESOLUTION: PreflightCheckResolution = {
title: "Granting your service principal authorization to your subscription",
subtitle:
"You will need to authorize your service principal to read and write to your subscription. To properly configure the service principal, following the creation steps in our docs:",
steps: [
{
text: "Log in to your Azure Portal:",
externalLink:
"https://portal.azure.com",
},
{
text: "Click on the Azure Cloud Shell icon to the right of the global search bar, and select Bash as your shell"
},
{
text: "Follow the directions in our docs to create the Azure role required for provisioning with Porter and attach it to a service principal:",
externalLink:
"https://docs.porter.run/provision/provisioning-on-azure#creating-the-service-principal",
},
{
text: "Note the outputted credentials, return to the Azure credential input screen in Porter, and re-enter the credentials to provision",
},
],
};
const AZURE_RESOURCE_PROVIDER_RESOLUTION: PreflightCheckResolution = {
title: "Enable required resource providers in your subscription",
subtitle:
"You will need to enable certain resource providers in your Azure subscription in order for Porter to provision your infrastructure:",
steps: [
{
text: "Take note of any particular resource providers flagged as missing in the provisioning error message."
},
{
text: "Log in to your Azure Portal:",
externalLink:
"https://portal.azure.com",
},
{
text: "Follow the directions in our docs to enable all required resource providers in your subscription:",
externalLink:
"https://docs.porter.run/provision/provisioning-on-azure#prerequisites",
},
{
text: "Changes may take a few minutes to take effect. Once you have enabled the resource providers, return to Porter and retry the provision.",
},
],
};
const AZURE_VCPUS_QUOTA_RESOLUTION: PreflightCheckResolution = {
title: "Requesting more vCPUs",
subtitle:
"You will need to either request more vCPUs or delete existing instances in order to provision in the location specified. You can request more vCPUs by following these steps:",
steps: [
{
text: "Note which resource families were flagged in the provisioning error message. These may include your requested machine types, as well as those required by Porter."
},
{
text: "Log in to your Azure Portal:",
externalLink:
"https://portal.azure.com",
},
{
text: "Follow the directions in our docs to request quota increases:",
externalLink:
"https://docs.porter.run/provision/provisioning-on-azure#compute-quotas",
},
{
text: "Requests may take a few hours to be fulfilled. Once you have confirmed that the quota increases have been granted, return to Porter and retry the provision.",
},
],
};

const SUPPORTED_AWS_PREFLIGHT_CHECKS: PreflightCheck[] = [
{
Expand All @@ -1149,6 +1218,24 @@ const SUPPORTED_AWS_PREFLIGHT_CHECKS: PreflightCheck[] = [
},
];

const SUPPORTED_AZURE_PREFLIGHT_CHECKS: PreflightCheck[] = [
{
name: "authz",
displayName: "Subscription authorization",
resolution: AZURE_AUTHZ_RESOLUTION,
},
{
name: "apiEnabled",
displayName: "Enable resource providers",
resolution: AZURE_RESOURCE_PROVIDER_RESOLUTION,
},
{
name: "vcpu",
displayName: "vCPU availability",
resolution: AZURE_VCPUS_QUOTA_RESOLUTION,
},
];

const SUPPORTED_GCP_PREFLIGHT_CHECKS: PreflightCheck[] = [
{
name: "apiEnabled",
Expand Down Expand Up @@ -1254,7 +1341,7 @@ export const CloudProviderAzure: ClientCloudProvider & {
machineTypes: SUPPORTED_AZURE_MACHINE_TYPES,
baseCost: 164.69,
newClusterDefaultContract: DEFAULT_AKS_CONTRACT,
preflightChecks: [],
preflightChecks: SUPPORTED_AZURE_PREFLIGHT_CHECKS,
config: {
kind: "Azure",
skuTiers: SUPPORTED_AZURE_SKU_TIERS,
Expand Down
4 changes: 3 additions & 1 deletion dashboard/src/lib/clusters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export type ClientMachineType = {
type PreflightCheckResolutionStep = {
text: string;
externalLink?: string;
code?: string;
};
export type PreflightCheckResolution = {
title: string;
Expand Down Expand Up @@ -508,6 +509,7 @@ const preflightCheckKeyValidator = z.enum([
"apiEnabled",
"cidrAvailability",
"iamPermissions",
"authz",
]);
type PreflightCheckKey = z.infer<typeof preflightCheckKeyValidator>;
export const preflightCheckValidator = z.object({
Expand All @@ -519,7 +521,7 @@ export const preflightCheckValidator = z.object({
metadata: z.record(z.string()).optional(),
}),
})
.array(),
.array()
});
export const createContractResponseValidator = z.object({
contract_revision: z.object({
Expand Down
7 changes: 4 additions & 3 deletions dashboard/src/lib/hooks/useCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
updateExistingClusterContract,
} from "lib/clusters";
import {
CloudProviderAWS,
CloudProviderGCP,
SUPPORTED_CLOUD_PROVIDERS,
CloudProviderAWS, CloudProviderAzure,
CloudProviderGCP,
SUPPORTED_CLOUD_PROVIDERS,
} from "lib/clusters/constants";
import {
clusterStateValidator,
Expand Down Expand Up @@ -382,6 +382,7 @@ export const useUpdateCluster = ({
)
.with("AWS", () => CloudProviderAWS.preflightChecks)
.with("GCP", () => CloudProviderGCP.preflightChecks)
.with("Azure", () => CloudProviderAzure.preflightChecks)
.otherwise(() => []);

const clientPreflightChecks: ClientPreflightCheck[] = parsed.errors
Expand Down
Loading

0 comments on commit dab8157

Please sign in to comment.