Skip to content

Commit

Permalink
Merge branch 'master' into cc-project
Browse files Browse the repository at this point in the history
  • Loading branch information
sunguroku authored May 22, 2024
2 parents bfc4b09 + 82b62cd commit ec7d8ac
Show file tree
Hide file tree
Showing 1,424 changed files with 131,339 additions and 39,074 deletions.
2 changes: 1 addition & 1 deletion .github/actions/build-npm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ runs:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
- name: Install NPM Dependencies
shell: bash
run: |
Expand Down
42 changes: 23 additions & 19 deletions .github/workflows/install_script.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
"on":
name: Deploy to install-script
on:
push:
tags:
- production
name: Deploy Install Script to Production
- production
jobs:
porter-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Update Porter App
timeout-minutes: 20
uses: porter-dev/[email protected]
with:
app: install-script
cluster: "9"
host: https://dashboard.internal-tools.porter.run
namespace: default
project: "5"
tag: ${{ steps.vars.outputs.sha_short }}
token: ${{ secrets.PORTER_TOKEN_5 }}
- name: Checkout code
uses: actions/checkout@v3
- name: Set Github tag
id: vars
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Setup porter
uses: porter-dev/[email protected]
- name: Deploy stack
timeout-minutes: 30
run: exec porter apply
env:
PORTER_CLUSTER: "9"
PORTER_DEPLOYMENT_TARGET_ID: b0fec389-99d5-4ca5-9012-002b410248b3
PORTER_HOST: https://dashboard.internal-tools.porter.run
PORTER_PR_NUMBER: ${{ github.event.number }}
PORTER_PROJECT: "5"
PORTER_REPO_NAME: ${{ github.event.repository.name }}
PORTER_STACK_NAME: install-script
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
PORTER_TOKEN: ${{ secrets.PORTER_STACK_5_9 }}
2 changes: 1 addition & 1 deletion .github/workflows/pr_push_checks_node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
uses: actions/setup-node@v3
if: steps.changed-files.outputs.any_changed == 'true'
with:
node-version: 16
node-version: 20
- name: Setup NPM
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: dashboard
Expand Down
2 changes: 1 addition & 1 deletion api/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func NewClientWithConfig(ctx context.Context, input NewClientInput) (Client, err
client := Client{
BaseURL: input.BaseURL,
HTTPClient: &http.Client{
Timeout: time.Minute,
Timeout: 60 * time.Minute,
},
}
if cfToken := os.Getenv("PORTER_CF_ACCESS_TOKEN"); cfToken != "" {
Expand Down
14 changes: 14 additions & 0 deletions api/client/deployment_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"

"github.com/google/uuid"
"github.com/porter-dev/porter/api/types"
)

Expand Down Expand Up @@ -44,3 +45,16 @@ func (c *Client) ListDeploymentTargets(

return resp, err
}

// DeleteDeploymentTarget deletes a deployment target in a project
func (c *Client) DeleteDeploymentTarget(
ctx context.Context,
projectId uint,
deploymentTargetID uuid.UUID,
) error {
return c.deleteRequest(
fmt.Sprintf("/projects/%d/targets/%s", projectId, deploymentTargetID.String()),
nil,
nil,
)
}
2 changes: 1 addition & 1 deletion api/server/handlers/addons/tailscale_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (c *TailscaleServicesHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
return
}

var services []TailscaleService
services := make([]TailscaleService, 0)
for _, svc := range svcList.Items {
var port int
if len(svc.Spec.Ports) > 0 {
Expand Down
13 changes: 4 additions & 9 deletions api/server/handlers/billing/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/http"
"time"

"github.com/porter-dev/porter/api/server/handlers"
"github.com/porter-dev/porter/api/server/shared"
Expand Down Expand Up @@ -136,7 +135,7 @@ func (c *CreateBillingHandler) grantRewardIfReferral(ctx context.Context, referr
return telemetry.Error(ctx, span, err, "failed to get referral count by referrer id")
}

maxReferralRewards := c.Config().BillingManager.MetronomeClient.MaxReferralRewards
maxReferralRewards := c.Config().BillingManager.LagoClient.MaxReferralRewards
if referralCount >= maxReferralRewards {
return nil
}
Expand All @@ -147,13 +146,9 @@ func (c *CreateBillingHandler) grantRewardIfReferral(ctx context.Context, referr
}

if referral != nil && referral.Status != models.ReferralStatusCompleted {
// Metronome requires an expiration to be passed in, so we set it to 5 years which in
// practice will mean the credits will most likely run out before expiring
expiresAt := time.Now().AddDate(5, 0, 0).Format(time.RFC3339)
reason := "Referral reward"
rewardAmount := c.Config().BillingManager.MetronomeClient.DefaultRewardAmountCents
paidAmount := c.Config().BillingManager.MetronomeClient.DefaultPaidAmountCents
err := c.Config().BillingManager.MetronomeClient.CreateCreditsGrant(ctx, referrerProject.UsageID, reason, rewardAmount, paidAmount, expiresAt)
name := "Referral reward"
rewardAmount := c.Config().BillingManager.LagoClient.DefaultRewardAmountCents
err := c.Config().BillingManager.LagoClient.CreateCreditsGrant(ctx, referrerProject.ID, name, rewardAmount, referrerProject.EnableSandbox)
if err != nil {
return telemetry.Error(ctx, span, err, "failed to grand credits reward")
}
Expand Down
80 changes: 62 additions & 18 deletions api/server/handlers/billing/ingest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
package billing

import (
"fmt"
"bytes"
"context"
"encoding/json"
"net/http"

"github.com/porter-dev/porter/api/server/handlers"
Expand Down Expand Up @@ -36,22 +38,17 @@ func (c *IngestEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

proj, _ := ctx.Value(types.ProjectScope).(*models.Project)

if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) {
c.WriteResult(w, r, "")
telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "lago-config-exists", Value: c.Config().BillingManager.LagoConfigLoaded},
telemetry.AttributeKV{Key: "lago-enabled", Value: proj.GetFeatureFlag(models.LagoEnabled, c.Config().LaunchDarklyClient)},
telemetry.AttributeKV{Key: "porter-cloud-enabled", Value: proj.EnableSandbox},
)

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "metronome-config-exists", Value: c.Config().BillingManager.MetronomeConfigLoaded},
telemetry.AttributeKV{Key: "metronome-enabled", Value: proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient)},
telemetry.AttributeKV{Key: "porter-cloud-enabled", Value: proj.EnableSandbox},
)
if !c.Config().BillingManager.LagoConfigLoaded || !proj.GetFeatureFlag(models.LagoEnabled, c.Config().LaunchDarklyClient) {
c.WriteResult(w, r, "")
return
}

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "metronome-enabled", Value: true},
telemetry.AttributeKV{Key: "usage-id", Value: proj.UsageID},
)

ingestEventsRequest := struct {
Events []types.BillingEvent `json:"billing_events"`
}{}
Expand All @@ -66,19 +63,66 @@ func (c *IngestEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
telemetry.AttributeKV{Key: "usage-events-count", Value: len(ingestEventsRequest.Events)},
)

// For Porter Cloud events, we apend a prefix to avoid collisions before sending to Metronome
if proj.EnableSandbox {
for i := range ingestEventsRequest.Events {
ingestEventsRequest.Events[i].CustomerID = fmt.Sprintf("porter-cloud-%s", ingestEventsRequest.Events[i].CustomerID)
var subscriptionID string
if !proj.EnableSandbox {
plan, err := c.Config().BillingManager.LagoClient.GetCustomerActivePlan(ctx, proj.ID, proj.EnableSandbox)
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting active subscription")
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
return
}
subscriptionID = plan.ID
}

err := c.Config().BillingManager.MetronomeClient.IngestEvents(ctx, ingestEventsRequest.Events)
telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "subscription_id", Value: subscriptionID},
)

err := c.Config().BillingManager.LagoClient.IngestEvents(ctx, subscriptionID, ingestEventsRequest.Events, proj.EnableSandbox)
if err != nil {
err := telemetry.Error(ctx, span, err, "error ingesting events")
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
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
}
24 changes: 7 additions & 17 deletions api/server/handlers/billing/invoices.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package billing

import (
"fmt"
"net/http"

"github.com/porter-dev/porter/api/server/handlers"
Expand All @@ -25,7 +24,7 @@ func NewListCustomerInvoicesHandler(
writer shared.ResultWriter,
) *ListCustomerInvoicesHandler {
return &ListCustomerInvoicesHandler{
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
}
}

Expand All @@ -36,31 +35,22 @@ func (c *ListCustomerInvoicesHandler) ServeHTTP(w http.ResponseWriter, r *http.R
proj, _ := ctx.Value(types.ProjectScope).(*models.Project)

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "billing-config-exists", Value: c.Config().BillingManager.StripeConfigLoaded},
telemetry.AttributeKV{Key: "billing-enabled", Value: proj.GetFeatureFlag(models.BillingEnabled, c.Config().LaunchDarklyClient)},
telemetry.AttributeKV{Key: "lago-config-exists", Value: c.Config().BillingManager.LagoConfigLoaded},
telemetry.AttributeKV{Key: "lago-enabled", Value: proj.GetFeatureFlag(models.LagoEnabled, c.Config().LaunchDarklyClient)},
telemetry.AttributeKV{Key: "porter-cloud-enabled", Value: proj.EnableSandbox},
)

if !c.Config().BillingManager.StripeConfigLoaded || !proj.GetFeatureFlag(models.BillingEnabled, c.Config().LaunchDarklyClient) {
if !c.Config().BillingManager.LagoConfigLoaded || !proj.GetFeatureFlag(models.LagoEnabled, c.Config().LaunchDarklyClient) {
c.WriteResult(w, r, "")
return
}

req := &types.ListCustomerInvoicesRequest{}

if ok := c.DecodeAndValidate(w, r, req); !ok {
err := telemetry.Error(ctx, span, nil, "error decoding list customer usage request")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

invoices, err := c.Config().BillingManager.StripeClient.ListCustomerInvoices(ctx, proj.BillingID, req.Status)
invoices, err := c.Config().BillingManager.LagoClient.ListCustomerFinalizedInvoices(ctx, proj.ID, proj.EnableSandbox)
if err != nil {
err = telemetry.Error(ctx, span, err, fmt.Sprintf("error listing invoices for customer %s", proj.BillingID))
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
err = telemetry.Error(ctx, span, err, "error listing invoices")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

// Write the response to the frontend
c.WriteResult(w, r, invoices)
}
39 changes: 23 additions & 16 deletions api/server/handlers/billing/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"net/http"

"github.com/google/uuid"
"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"
Expand Down Expand Up @@ -100,10 +99,9 @@ func (c *CheckPaymentEnabledHandler) ensureBillingSetup(ctx context.Context, pro

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "billing-id", Value: proj.BillingID},
telemetry.AttributeKV{Key: "usage-id", Value: proj.UsageID},
)

if proj.BillingID == "" || proj.UsageID == uuid.Nil {
if proj.BillingID == "" {
adminUser, err := c.getAdminUser(ctx, proj.ID)
if err != nil {
return telemetry.Error(ctx, span, err, "error getting admin user")
Expand All @@ -119,11 +117,19 @@ func (c *CheckPaymentEnabledHandler) ensureBillingSetup(ctx context.Context, pro
if err != nil {
return telemetry.Error(ctx, span, err, "error ensuring Stripe customer exists")
}
}

lagoCustomerExists := false
if !lagoCustomerExists {
adminUser, err := c.getAdminUser(ctx, proj.ID)
if err != nil {
return telemetry.Error(ctx, span, err, "error getting admin user")
}

// Create usage customer for project and set the usage ID if it doesn't exist
err = c.ensureMetronomeCustomerExists(ctx, adminUser.Email, proj)
err = c.ensureLagoCustomerExists(ctx, adminUser.Email, proj)
if err != nil {
return telemetry.Error(ctx, span, err, "error ensuring Metronome customer exists")
return telemetry.Error(ctx, span, err, "error ensuring Lago customer exists")
}
}

Expand Down Expand Up @@ -189,30 +195,31 @@ func (c *CheckPaymentEnabledHandler) ensureStripeCustomerExists(ctx context.Cont
return nil
}

func (c *CheckPaymentEnabledHandler) ensureMetronomeCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (err error) {
ctx, span := telemetry.NewSpan(ctx, "ensure-metronome-customer-exists")
func (c *CheckPaymentEnabledHandler) ensureLagoCustomerExists(ctx context.Context, adminUserEmail string, proj *models.Project) (err error) {
ctx, span := telemetry.NewSpan(ctx, "ensure-lago-customer-exists")
defer span.End()

if !c.Config().BillingManager.MetronomeConfigLoaded || !proj.GetFeatureFlag(models.MetronomeEnabled, c.Config().LaunchDarklyClient) || proj.UsageID != uuid.Nil {
if !c.Config().BillingManager.LagoConfigLoaded || !proj.GetFeatureFlag(models.LagoEnabled, c.Config().LaunchDarklyClient) {
return nil
}

customerID, customerPlanID, err := c.Config().BillingManager.MetronomeClient.CreateCustomerWithPlan(ctx, adminUserEmail, proj.Name, proj.ID, proj.BillingID, proj.EnableSandbox)
// Check if the customer already exists
exists, err := c.Config().BillingManager.LagoClient.CheckIfCustomerExists(ctx, proj.ID, proj.EnableSandbox)
if err != nil {
return telemetry.Error(ctx, span, err, "error creating Metronome customer")
return telemetry.Error(ctx, span, err, "error while checking if customer exists")
}

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "usage-id", Value: proj.UsageID},
telemetry.AttributeKV{Key: "usage-plan-id", Value: proj.UsagePlanID},
telemetry.AttributeKV{Key: "customer-exists", Value: exists},
)

proj.UsageID = customerID
proj.UsagePlanID = customerPlanID
if exists {
return nil
}

_, err = c.Repo().Project().UpdateProject(proj)
err = c.Config().BillingManager.LagoClient.CreateCustomerWithPlan(ctx, adminUserEmail, proj.Name, proj.ID, proj.BillingID, proj.EnableSandbox)
if err != nil {
return telemetry.Error(ctx, span, err, "error updating project")
return telemetry.Error(ctx, span, err, "error creating Lago customer")
}

return nil
Expand Down
Loading

0 comments on commit ec7d8ac

Please sign in to comment.