diff --git a/api/server/handlers/porter_app/create_secret_and_open_pr.go b/api/server/handlers/porter_app/create_secret_and_open_pr.go index 85eaeb2633..8954be8e7b 100644 --- a/api/server/handlers/porter_app/create_secret_and_open_pr.go +++ b/api/server/handlers/porter_app/create_secret_and_open_pr.go @@ -17,6 +17,7 @@ import ( "github.com/porter-dev/porter/internal/auth/token" "github.com/porter-dev/porter/internal/integrations/ci/actions" "github.com/porter-dev/porter/internal/models" + "github.com/porter-dev/porter/internal/telemetry" ) type OpenStackPRHandler struct { @@ -34,9 +35,12 @@ func NewOpenStackPRHandler( } func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - user, _ := r.Context().Value(types.UserScope).(*models.User) - project, _ := r.Context().Value(types.ProjectScope).(*models.Project) - cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster) + ctx, span := telemetry.NewSpan(r.Context(), "serve-open-stack-pr") + defer span.End() + + user, _ := ctx.Value(types.UserScope).(*models.User) + project, _ := ctx.Value(types.ProjectScope).(*models.Project) + cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster) appName, reqErr := requestutils.GetURLParamString(r, types.URLParamPorterAppName) if reqErr != nil { c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(reqErr, http.StatusBadRequest)) @@ -45,11 +49,14 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { request := &types.CreateSecretAndOpenGHPRRequest{} if ok := c.DecodeAndValidate(w, r, request); !ok { + err := telemetry.Error(ctx, span, nil, "error decoding request") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest)) return } client, err := getGithubClient(c.Config(), request.GithubAppInstallationID) if err != nil { + err := telemetry.Error(ctx, span, err, "error creating github client") c.HandleAPIError(w, r, apierrors.NewErrInternal(err)) return } @@ -59,12 +66,16 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // generate porter jwt token jwt, err := token.GetTokenForAPI(user.ID, project.ID) if err != nil { - c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error getting token for API: %w", err))) + err = fmt.Errorf("error getting token for API: %w", err) + err := telemetry.Error(ctx, span, err, err.Error()) + c.HandleAPIError(w, r, apierrors.NewErrInternal(err)) return } encoded, err := jwt.EncodeToken(c.Config().TokenConf) if err != nil { - c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error encoding API token: %w", err))) + err = fmt.Errorf("error encoding API token: %w", err) + err := telemetry.Error(ctx, span, err, err.Error()) + c.HandleAPIError(w, r, apierrors.NewErrInternal(err)) return } @@ -78,7 +89,9 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { request.GithubRepoName, ) if err != nil { - c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error generating secret: %w", err))) + err = fmt.Errorf("error generating secret: %w", err) + err := telemetry.Error(ctx, span, err, err.Error()) + c.HandleAPIError(w, r, apierrors.NewErrInternal(err)) return } } @@ -113,12 +126,16 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if unwrappedErr != nil { if errors.Is(unwrappedErr, actions.ErrProtectedBranch) { c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict)) + return } else if errors.Is(unwrappedErr, actions.ErrCreatePRForProtectedBranch) { c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusPreconditionFailed)) + return } } else { - c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error setting up application in the github "+ - "repo: %w", err))) + err = fmt.Errorf("error setting up application in the github "+ + "repo: %w", err) + err := telemetry.Error(ctx, span, err, err.Error()) + c.HandleAPIError(w, r, apierrors.NewErrInternal(err)) return } } @@ -133,7 +150,9 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // update DB with the PR url porterApp, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, appName) if err != nil { - c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("unable to get porter app db: %w", err))) + err = fmt.Errorf("unable to get porter app db: %w", err) + err := telemetry.Error(ctx, span, err, err.Error()) + c.HandleAPIError(w, r, apierrors.NewErrInternal(err)) return } @@ -141,7 +160,9 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { _, err = c.Repo().PorterApp().UpdatePorterApp(porterApp) if err != nil { - c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("unable to write pr url to porter app db: %w", err))) + err = fmt.Errorf("unable to write pr url to porter app db: %w", err) + err := telemetry.Error(ctx, span, err, err.Error()) + c.HandleAPIError(w, r, apierrors.NewErrInternal(err)) return } } diff --git a/dashboard/src/components/PreflightChecks.tsx b/dashboard/src/components/PreflightChecks.tsx index 668c8457ad..c1a6d40b91 100644 --- a/dashboard/src/components/PreflightChecks.tsx +++ b/dashboard/src/components/PreflightChecks.tsx @@ -13,6 +13,7 @@ import Loading from "./Loading"; type Props = RouteComponentProps & { preflightData: any provider: 'AWS' | 'GCP' | 'DEFAULT'; + error?: string; }; @@ -97,10 +98,16 @@ const PreflightChecks: React.FC = (props) => { Porter checks that the account has the right permissions and resources to provision a cluster. - {Object.keys(currentMessageConst).map((checkKey) => ( - - ))} - + { + props.error ? + + : + Object.keys(currentMessageConst).map((checkKey) => ( + + )) + + } + ); }; diff --git a/dashboard/src/components/ProvisionerSettings.tsx b/dashboard/src/components/ProvisionerSettings.tsx index d520e2c608..61bddbbbf6 100644 --- a/dashboard/src/components/ProvisionerSettings.tsx +++ b/dashboard/src/components/ProvisionerSettings.tsx @@ -133,6 +133,7 @@ const ProvisionerSettings: React.FC = (props) => { const [isLoading, setIsLoading] = useState(false); const [preflightData, setPreflightData] = useState(null) const [preflightFailed, setPreflightFailed] = useState(true) + const [preflightError, setPreflightError] = useState("") const markStepStarted = async (step: string, errMessage?: string) => { try { @@ -480,52 +481,60 @@ const ProvisionerSettings: React.FC = (props) => { useEffect(() => { if (!props.clusterId) { setStep(1) - setPreflightData(null) preflightChecks() } }, [props.selectedClusterVersion, awsRegion]); const preflightChecks = async () => { - setIsLoading(true); - setPreflightData(null); - var data = new PreflightCheckRequest({ - projectId: BigInt(currentProject.id), - cloudProvider: EnumCloudProvider.AWS, - cloudProviderCredentialsId: props.credentialId, - preflightValues: { - case: "eksPreflightValues", - value: new EKSPreflightValues({ - region: awsRegion, - }) + try { + setIsLoading(true); + setPreflightData(null); + setPreflightFailed(true) + setPreflightError(""); + + var data = new PreflightCheckRequest({ + projectId: BigInt(currentProject.id), + cloudProvider: EnumCloudProvider.AWS, + cloudProviderCredentialsId: props.credentialId, + preflightValues: { + case: "eksPreflightValues", + value: new EKSPreflightValues({ + region: awsRegion, + }) + } + }); + const preflightDataResp = await api.preflightCheck( + "", data, + { + id: currentProject.id, + } + ) + // Check if any of the preflight checks has a message + let hasMessage = false; + let errors = "Preflight Checks Failed : "; + for (let check in preflightDataResp?.data?.Msg.preflight_checks) { + if (preflightDataResp?.data?.Msg.preflight_checks[check]?.message) { + hasMessage = true; + errors = errors + check + ", " + } } - }); - const preflightDataResp = await api.preflightCheck( - "", data, - { - id: currentProject.id, + // If none of the checks have a message, set setPreflightFailed to false + if (hasMessage) { + markStepStarted("provisioning-failed", errors); } - ) - // Check if any of the preflight checks has a message - let hasMessage = false; - let errors = "Preflight Checks Failed : "; - for (let check in preflightDataResp?.data?.Msg.preflight_checks) { - if (preflightDataResp?.data?.Msg.preflight_checks[check]?.message) { - hasMessage = true; - errors = errors + check + ", " + if (!hasMessage) { + setPreflightFailed(false); + setStep(2); } + setPreflightData(preflightDataResp?.data?.Msg); + setIsLoading(false) + } catch (err) { + setPreflightError(err) + setIsLoading(false) + setPreflightFailed(true); } - // If none of the checks have a message, set setPreflightFailed to false - if (hasMessage) { - markStepStarted("provisioning-failed", errors); - } - if (!hasMessage) { - setPreflightFailed(false); - setStep(2); - } - setPreflightData(preflightDataResp?.data?.Msg); - setIsLoading(false) } const renderAdvancedSettings = () => { @@ -984,7 +993,7 @@ const ProvisionerSettings: React.FC = (props) => { = (props) => { , <> - + {(preflightFailed && preflightData) && <> diff --git a/dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettings.tsx b/dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettings.tsx index a8bba8bd8b..97a1a9988f 100644 --- a/dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettings.tsx +++ b/dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettings.tsx @@ -151,7 +151,7 @@ const ClusterSettings: React.FC = (props) => { ); - if (!currentCluster?.infra_id || !currentCluster?.service) { + if (!currentCluster?.infra_id && !currentProject?.capi_provisioner_enabled || !currentCluster?.service) { helperText = ( Remove this cluster from Porter. Since this cluster was not provisioned diff --git a/dashboard/src/main/home/modals/UpdateClusterModal.tsx b/dashboard/src/main/home/modals/UpdateClusterModal.tsx index dd97db5cf5..f527e2632d 100644 --- a/dashboard/src/main/home/modals/UpdateClusterModal.tsx +++ b/dashboard/src/main/home/modals/UpdateClusterModal.tsx @@ -52,7 +52,7 @@ class UpdateClusterModal extends Component { cluster_id: currentCluster.id, } ) - .then((_) => { + .then(async (_) => { if (!currentCluster?.infra_id) { // TODO: make this more declarative from the Home component this.props.setRefreshClusters(true); @@ -61,24 +61,34 @@ class UpdateClusterModal extends Component { pushFiltered(this.props, "/dashboard", ["project_id"], { tab: "overview", }); + + + // Handle destroying infra we've provisioned + api + .destroyInfra( + "", + {}, + { + project_id: currentProject.id, + infra_id: currentCluster.infra_id, + } + ) + .then(() => + console.log("destroyed provisioned infra:", currentCluster.infra_id) + ) + .catch(console.log); + + if (currentProject.simplified_view_enabled) { + await api.saveOnboardingState( + "", + { current_step: "connect_source" }, + { project_id: currentProject.id } + ); + window.location.reload(); + } return; } - // Handle destroying infra we've provisioned - api - .destroyInfra( - "", - {}, - { - project_id: currentProject.id, - infra_id: currentCluster.infra_id, - } - ) - .then(() => - console.log("destroyed provisioned infra:", currentCluster.infra_id) - ) - .catch(console.log); - this.props.setRefreshClusters(true); this.setState({ status: "successful", showDeleteOverlay: false }); this.context.setCurrentModal(null, null);