Просмотр исходного кода

Merge branch 'master' of github.com:porter-dev/porter into upstash-frontend

Feroze Mohideen 2 лет назад
Родитель
Сommit
08220dcd7a

+ 42 - 0
api/server/handlers/billing/ingest.go

@@ -2,6 +2,9 @@
 package billing
 
 import (
+	"bytes"
+	"context"
+	"encoding/json"
 	"fmt"
 	"net/http"
 
@@ -80,5 +83,44 @@ func (c *IngestEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
+	// Call the ingest health endpoint
+	err = c.postIngestHealthEndpoint(ctx, proj.ID)
+	if err != nil {
+		err := telemetry.Error(ctx, span, err, "error calling ingest health endpoint")
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
 	c.WriteResult(w, r, "")
 }
+
+func (c *IngestEventsHandler) postIngestHealthEndpoint(ctx context.Context, projectID uint) (err error) {
+	ctx, span := telemetry.NewSpan(ctx, "post-ingest-health-endpoint")
+	defer span.End()
+
+	// Call the ingest check webhook
+	webhookUrl := c.Config().ServerConf.IngestStatusWebhookUrl
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "ingest-status-webhook-url", Value: webhookUrl})
+
+	if webhookUrl == "" {
+		return nil
+	}
+
+	req := struct {
+		ProjectID uint `json:"project_id"`
+	}{
+		ProjectID: projectID,
+	}
+
+	reqBody, err := json.Marshal(req)
+	if err != nil {
+		return telemetry.Error(ctx, span, err, "error marshalling ingest status webhook request")
+	}
+
+	client := &http.Client{}
+	resp, err := client.Post(webhookUrl, "application/json", bytes.NewBuffer(reqBody))
+	if err != nil || resp.StatusCode != http.StatusOK {
+		return telemetry.Error(ctx, span, err, "error sending ingest status webhook request")
+	}
+	return nil
+}

+ 21 - 0
api/server/handlers/oauth_callback/upstash.go

@@ -10,6 +10,7 @@ import (
 	"net/url"
 	"time"
 
+	"github.com/golang-jwt/jwt"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
@@ -100,6 +101,25 @@ func (p *OAuthCallbackUpstashHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 		return
 	}
 
+	t, _, err := new(jwt.Parser).ParseUnverified(token.AccessToken, jwt.MapClaims{}) // safe to use because we validated the token above
+	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error parsing token")
+		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
+	var email string
+	if claims, ok := t.Claims.(jwt.MapClaims); ok {
+		if emailVal, ok := claims["https://user.io/email"].(string); ok {
+			email = emailVal
+		}
+	}
+	if email == "" {
+		err = telemetry.Error(ctx, span, nil, "email not found in token")
+		p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+
 	// make an http call to https://api.upstash.com/apikey with authorization: bearer <access_token>
 	// to get the api key
 	apiKey, err := fetchUpstashApiKey(ctx, token.AccessToken)
@@ -118,6 +138,7 @@ func (p *OAuthCallbackUpstashHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 		},
 		ProjectID:       projID,
 		DeveloperApiKey: []byte(apiKey),
+		UpstashEmail:    email,
 	}
 
 	_, err = p.Repo().UpstashIntegration().Insert(ctx, oauthInt)

+ 3 - 0
api/server/shared/config/env/envconfs.go

@@ -75,6 +75,9 @@ type ServerConf struct {
 	PorterCloudPlanID    string `env:"PORTER_CLOUD_PLAN_ID"`
 	PorterStandardPlanID string `env:"PORTER_STANDARD_PLAN_ID"`
 
+	// The URL of the webhook to verify ingesting events works
+	IngestStatusWebhookUrl string `env:"INGEST_STATUS_WEBHOOK_URL"`
+
 	// This endpoint will be passed to the porter-agent so that
 	// the billing manager can query Prometheus.
 	PrometheusUrl string `env:"PROMETHEUS_URL"`

+ 1 - 0
api/types/project.go

@@ -41,6 +41,7 @@ type Project struct {
 	BillingEnabled                  bool    `json:"billing_enabled"`
 	MetronomeEnabled                bool    `json:"metronome_enabled"`
 	InfisicalEnabled                bool    `json:"infisical_enabled"`
+	FreezeEnabled                   bool    `json:"freeze_enabled"`
 	DBEnabled                       bool    `json:"db_enabled"`
 	EFSEnabled                      bool    `json:"efs_enabled"`
 	EnableReprovision               bool    `json:"enable_reprovision"`

+ 17 - 7
dashboard/src/main/home/Home.tsx

@@ -400,13 +400,14 @@ const Home: React.FC<Props> = (props) => {
       <DeploymentTargetProvider>
         <StyledHome
           padTop={
-            !currentProject?.sandbox_enabled &&
-            showCardBanner &&
-            currentProject?.billing_enabled &&
-            currentProject?.metronome_enabled &&
-            !trialExpired &&
-            plan &&
-            true
+            (!currentProject?.sandbox_enabled &&
+              showCardBanner &&
+              currentProject?.billing_enabled &&
+              currentProject?.metronome_enabled &&
+              !trialExpired &&
+              plan &&
+              true) ||
+            currentProject?.freeze_enabled
           }
         >
           {!currentProject?.sandbox_enabled &&
@@ -433,6 +434,14 @@ const Home: React.FC<Props> = (props) => {
                 )}
               </>
             )}
+          {currentProject?.freeze_enabled && (
+            <GlobalBanner>
+              <i className="material-icons-round">warning</i>
+              This project has been disabled due to recurring issues with the
+              connected payment method. Please contact support@porter.run to
+              reenable this project.
+            </GlobalBanner>
+          )}
           {showBillingModal && (
             <BillingModal
               back={() => {
@@ -704,6 +713,7 @@ export default withRouter(withAuth(Home));
 const GlobalBanner = styled.div`
   width: 100vw;
   z-index: 999;
+  padding: 20px;
   position: fixed;
   top: 0;
   color: #fefefe;

+ 7 - 2
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Resources.tsx

@@ -135,7 +135,7 @@ const Resources: React.FC<ResourcesProps> = ({
         <>
           <Spacer y={1} />
           <Text>
-            Sleep Service
+            Sleep service
             <a
               href="https://docs.porter.run/configure/basic-configuration#sleep-mode"
               target="_blank"
@@ -151,6 +151,7 @@ const Resources: React.FC<ResourcesProps> = ({
             render={({ field: { value, onChange } }) => (
               <Checkbox
                 checked={Boolean(value?.value)}
+                disabled={currentProject?.freeze_enabled}
                 toggleChecked={() => {
                   onChange({
                     ...value,
@@ -158,7 +159,11 @@ const Resources: React.FC<ResourcesProps> = ({
                   });
                 }}
               >
-                <Text color="helper">Pause all instances.</Text>
+                <Text color="helper">
+                  {currentProject?.freeze_enabled
+                    ? "Contact support@porter.run to re-enable your project and unsleep services."
+                    : "Pause all instances."}
+                </Text>
               </Checkbox>
             )}
           />

+ 7 - 6
dashboard/src/shared/types.tsx

@@ -289,15 +289,15 @@ export type FormElement = {
 export type RepoType = {
   FullName: string;
 } & (
-    | {
+  | {
       Kind: "github";
       GHRepoID: number;
     }
-    | {
+  | {
       Kind: "gitlab";
       GitIntegrationId: number;
     }
-  );
+);
 
 export type FileType = {
   path: string;
@@ -318,6 +318,7 @@ export type ProjectType = {
   billing_enabled: boolean;
   metronome_enabled: boolean;
   infisical_enabled: boolean;
+  freeze_enabled: boolean;
   capi_provisioner_enabled: boolean;
   db_enabled: boolean;
   efs_enabled: boolean;
@@ -380,15 +381,15 @@ export type ActionConfigType = {
   image_repo_uri: string;
   dockerfile_path?: string;
 } & (
-    | {
+  | {
       kind: "gitlab";
       gitlab_integration_id: number;
     }
-    | {
+  | {
       kind: "github";
       git_repo_id: number;
     }
-  );
+);
 
 export type GithubActionConfigType = ActionConfigType & {
   kind: "github";

+ 1 - 1
go.mod

@@ -81,6 +81,7 @@ require (
 	github.com/evanphx/json-patch/v5 v5.9.0
 	github.com/glebarez/sqlite v1.6.0
 	github.com/go-chi/chi/v5 v5.0.8
+	github.com/golang-jwt/jwt v3.2.1+incompatible
 	github.com/gosimple/slug v1.13.1
 	github.com/honeycombio/otel-config-go v1.11.0
 	github.com/launchdarkly/go-sdk-common/v3 v3.0.1
@@ -151,7 +152,6 @@ require (
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-ole/go-ole v1.2.6 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
-	github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
 	github.com/google/gnostic v0.6.9 // indirect
 	github.com/google/s2a-go v0.1.4 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect

+ 2 - 0
internal/models/integrations/upstash.go

@@ -11,4 +11,6 @@ type UpstashIntegration struct {
 	SharedOAuthModel
 
 	DeveloperApiKey []byte `json:"developer_api_key"`
+
+	UpstashEmail string `json:"upstash_email"`
 }

+ 5 - 0
internal/models/project.go

@@ -35,6 +35,9 @@ const (
 	// InfisicalEnabled enables the Infisical secrets operator integration
 	InfisicalEnabled FeatureFlagLabel = "infisical_enabled"
 
+	// FreezeEnabled freezes the project
+	FreezeEnabled FeatureFlagLabel = "freeze_enabled"
+
 	// DBEnabled enables the "Databases" tab
 	DBEnabled FeatureFlagLabel = "db_enabled"
 
@@ -106,6 +109,7 @@ var ProjectFeatureFlags = map[FeatureFlagLabel]bool{
 	BillingEnabled:                  false,
 	MetronomeEnabled:                false,
 	InfisicalEnabled:                false,
+	FreezeEnabled:                   false,
 	DBEnabled:                       false,
 	EFSEnabled:                      false,
 	EnableReprovision:               false,
@@ -319,6 +323,7 @@ func (p *Project) ToProjectType(launchDarklyClient *features.Client) types.Proje
 		BillingEnabled:                  p.GetFeatureFlag(BillingEnabled, launchDarklyClient),
 		MetronomeEnabled:                p.GetFeatureFlag(MetronomeEnabled, launchDarklyClient),
 		InfisicalEnabled:                p.GetFeatureFlag(InfisicalEnabled, launchDarklyClient),
+		FreezeEnabled:                   p.GetFeatureFlag(FreezeEnabled, launchDarklyClient),
 		DBEnabled:                       p.GetFeatureFlag(DBEnabled, launchDarklyClient),
 		EFSEnabled:                      p.GetFeatureFlag(EFSEnabled, launchDarklyClient),
 		EnableReprovision:               p.GetFeatureFlag(EnableReprovision, launchDarklyClient),