Skip to content

Commit

Permalink
Merge branch 'master' into inference-fe
Browse files Browse the repository at this point in the history
  • Loading branch information
Feroze Mohideen authored May 20, 2024
2 parents 1674b53 + 19cb8c9 commit 4500261
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 44 deletions.
132 changes: 132 additions & 0 deletions api/server/handlers/user/create_ory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package user

import (
"errors"
"net/http"

"github.com/porter-dev/porter/internal/analytics"

"github.com/porter-dev/porter/internal/telemetry"

"gorm.io/gorm"

"github.com/porter-dev/porter/api/server/shared/apierrors"
"github.com/porter-dev/porter/internal/models"

"github.com/porter-dev/porter/api/server/handlers"
"github.com/porter-dev/porter/api/server/shared"
"github.com/porter-dev/porter/api/server/shared/config"
)

// OryUserCreateHandler is the handler for user creation triggered by an ory action
type OryUserCreateHandler struct {
handlers.PorterHandlerReadWriter
}

// NewOryUserCreateHandler generates a new OryUserCreateHandler
func NewOryUserCreateHandler(
config *config.Config,
decoderValidator shared.RequestDecoderValidator,
writer shared.ResultWriter,
) *OryUserCreateHandler {
return &OryUserCreateHandler{
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
}
}

// CreateOryUserRequest is the expected request body for user creation triggered by an ory action
type CreateOryUserRequest struct {
OryId string `json:"ory_id"`
Email string `json:"email"`
Referral string `json:"referral"`
}

// ServeHTTP handles the user creation triggered by an ory action
func (u *OryUserCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-create-ory-user")
defer span.End()

// this endpoint is not authenticated through middleware; instead, we check
// for the presence of an ory action cookie that matches env
oryActionCookie, err := r.Cookie("ory_action")
if err != nil {
err = telemetry.Error(ctx, span, err, "invalid ory action cookie")
u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
return
}

if oryActionCookie.Value != u.Config().OryActionKey {
err = telemetry.Error(ctx, span, nil, "cookie does not match")
u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusForbidden))
return
}

request := &CreateOryUserRequest{}
ok := u.DecodeAndValidate(w, r, request)
if !ok {
err = telemetry.Error(ctx, span, nil, "invalid request")
u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "email", Value: request.Email},
telemetry.AttributeKV{Key: "ory-id", Value: request.OryId},
telemetry.AttributeKV{Key: "referral", Value: request.Referral},
)

if request.Email == "" {
err = telemetry.Error(ctx, span, nil, "email is required")
u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}
if request.OryId == "" {
err = telemetry.Error(ctx, span, nil, "ory_id is required")
u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

user := &models.User{
Model: gorm.Model{},
Email: request.Email,
EmailVerified: false,
AuthProvider: models.AuthProvider_Ory,
ExternalId: request.OryId,
}

existingUser, err := u.Repo().User().ReadUserByEmail(user.Email)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
err = telemetry.Error(ctx, span, err, "error reading user by email")
u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
return
}

if existingUser == nil || existingUser.ID == 0 {
user, err = u.Repo().User().CreateUser(user)
if err != nil {
err = telemetry.Error(ctx, span, err, "error creating user")
u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
return
}

_ = u.Config().AnalyticsClient.Identify(analytics.CreateSegmentIdentifyUser(user))

_ = u.Config().AnalyticsClient.Track(analytics.UserCreateTrack(&analytics.UserCreateTrackOpts{
UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
Email: user.Email,
FirstName: user.FirstName,
LastName: user.LastName,
CompanyName: user.CompanyName,
ReferralMethod: request.Referral,
}))
} else {
existingUser.AuthProvider = models.AuthProvider_Ory
existingUser.ExternalId = request.OryId
_, err = u.Repo().User().UpdateUser(existingUser)
if err != nil {
err = telemetry.Error(ctx, span, err, "error updating user")
u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
return
}
}
}
24 changes: 24 additions & 0 deletions api/server/router/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,30 @@ func GetBaseRoutes(
Router: r,
})

// POST /api/users/ory -> user.NewOryUserCreateHandler
createOryUserEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Verb: types.APIVerbUpdate,
Method: types.HTTPVerbPost,
Path: &types.Path{
Parent: basePath,
RelativePath: "/users/ory",
},
},
)

createOryUserHandler := user.NewOryUserCreateHandler(
config,
factory.GetDecoderValidator(),
factory.GetResultWriter(),
)

routes = append(routes, &router.Route{
Endpoint: createOryUserEndpoint,
Handler: createOryUserHandler,
Router: r,
})

// POST /api/login -> user.NewUserLoginHandler
loginUserEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Expand Down
1 change: 1 addition & 0 deletions api/server/shared/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ type Config struct {

Ory ory.APIClient
OryApiKeyContextWrapper func(ctx context.Context) context.Context
OryActionKey string
}

type ConfigLoader interface {
Expand Down
2 changes: 2 additions & 0 deletions api/server/shared/config/env/envconfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ type ServerConf struct {
OryEnabled bool `env:"ORY_ENABLED,default=false"`
OryUrl string `env:"ORY_URL,default=http://localhost:4000"`
OryApiKey string `env:"ORY_API_KEY"`
// OryActionKey is the key used to authenticate api requests from Ory Actions to the Porter API
OryActionKey string `env:"ORY_ACTION_KEY"`
}

// DBConf is the database configuration: if generated from environment variables,
Expand Down
1 change: 1 addition & 0 deletions api/server/shared/config/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
res.OryApiKeyContextWrapper = func(ctx context.Context) context.Context {
return context.WithValue(ctx, ory.ContextAccessToken, InstanceEnvConf.ServerConf.OryApiKey)
}
res.OryActionKey = InstanceEnvConf.ServerConf.OryActionKey
res.Logger.Info().Msg("Created Ory client")
}

Expand Down
9 changes: 9 additions & 0 deletions api/types/billing_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ type ListCustomerUsageRequest struct {
CurrentPeriod bool `json:"current_period,omitempty"`
}

// Subscription is the subscription for a customer
type Subscription struct {
ExternalID string `json:"external_id"`
ExternalCustomerID string `json:"external_customer_id"`
Status string `json:"status"`
SubscriptionAt string `json:"subscription_at"`
EndingAt string `json:"ending_at"`
}

// Usage is the aggregated usage for a customer
type Usage struct {
FromDatetime string `json:"from_datetime"`
Expand Down
7 changes: 7 additions & 0 deletions dashboard/src/components/ProvisionerSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ const machineTypeOptions = [
{ value: "c7g.8xlarge", label: "c7g.8xlarge" },
{ value: "c7g.12xlarge", label: "c7g.12xlarge" },
{ value: "c7g.16xlarge", label: "c7g.16xlarge" },
{ value: "c7gn.large", label: "c7gn.large" },
{ value: "c7gn.xlarge", label: "c7gn.xlarge" },
{ value: "c7gn.2xlarge", label: "c7gn.2xlarge" },
{ value: "c7gn.4xlarge", label: "c7gn.4xlarge" },
{ value: "c7gn.8xlarge", label: "c7gn.8xlarge" },
{ value: "c7gn.12xlarge", label: "c7gn.12xlarge" },
{ value: "c7gn.16xlarge", label: "c7gn.16xlarge" },
];

const defaultCidrVpc = "10.78.0.0/16";
Expand Down
64 changes: 64 additions & 0 deletions dashboard/src/lib/clusters/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,70 @@ const SUPPORTED_AWS_MACHINE_TYPES: ClientMachineType[] = [
cpuCores: 64,
ramMegabytes: 131072,
},
{
name: "c7gn.medium",
displayName: "c7gn.medium",
supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
isGPU: false,
cpuCores: 1,
ramMegabytes: 2048,
},
{
name: "c7gn.large",
displayName: "c7gn.large",
supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
isGPU: false,
cpuCores: 2,
ramMegabytes: 4096,
},
{
name: "c7gn.xlarge",
displayName: "c7gn.xlarge",
supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
isGPU: false,
cpuCores: 4,
ramMegabytes: 8192,
},
{
name: "c7gn.2xlarge",
displayName: "c7gn.2xlarge",
supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
isGPU: false,
cpuCores: 8,
ramMegabytes: 16384,
},
{
name: "c7gn.4xlarge",
displayName: "c7gn.4xlarge",
supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
isGPU: false,
cpuCores: 16,
ramMegabytes: 32768,
},
{
name: "c7gn.8xlarge",
displayName: "c7gn.8xlarge",
supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
isGPU: false,
cpuCores: 32,
ramMegabytes: 65536,
},
{
name: "c7gn.12xlarge",
displayName: "c7gn.12xlarge",
supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
isGPU: false,
cpuCores: 48,
ramMegabytes: 98304,
},
{
name: "c7gn.16xlarge",
displayName: "c7gn.16xlarge",
supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
isGPU: false,
cpuCores: 64,
ramMegabytes: 131072,
},
{
name: "g4dn.xlarge",
displayName: "g4dn.xlarge",
Expand Down
8 changes: 8 additions & 0 deletions dashboard/src/lib/clusters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,14 @@ const awsMachineTypeValidator = z.enum([
"c7g.8xlarge",
"c7g.12xlarge",
"c7g.16xlarge",
"c7gn.medium",
"c7gn.large",
"c7gn.xlarge",
"c7gn.2xlarge",
"c7gn.4xlarge",
"c7gn.8xlarge",
"c7gn.12xlarge",
"c7gn.16xlarge",
// gpu types
"g4dn.xlarge",
"g4dn.2xlarge",
Expand Down
6 changes: 5 additions & 1 deletion dashboard/src/lib/hooks/useAddon.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from "react";
import { Addon, AddonWithEnvVars } from "@porter-dev/api-contracts";
import { useQuery } from "@tanstack/react-query";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import Anser, { type AnserJsonEntry } from "anser";
import { match } from "ts-pattern";
import { z } from "zod";
Expand Down Expand Up @@ -200,6 +200,8 @@ export const useAddon = (): {
isError: boolean;
};
} => {
const queryClient = useQueryClient();

const updateAddon = async ({
projectId,
deploymentTargetId,
Expand Down Expand Up @@ -241,6 +243,8 @@ export const useAddon = (): {
addonName: addon.name.value,
}
);

await queryClient.invalidateQueries(["listAddons"]);
};

const getAddon = ({
Expand Down
22 changes: 2 additions & 20 deletions dashboard/src/main/home/add-on-dashboard/AddonContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { createContext, useCallback, useContext } from "react";
import { useQueryClient } from "@tanstack/react-query";
import React, { createContext, useContext } from "react";
import styled from "styled-components";

import Loading from "components/Loading";
Expand All @@ -26,7 +25,6 @@ type AddonContextType = {
projectId: number;
deploymentTarget: DeploymentTarget;
status: ClientAddonStatus;
deleteAddon: () => Promise<void>;
};

const AddonContext = createContext<AddonContextType | null>(null);
Expand All @@ -53,7 +51,7 @@ export const AddonContextProvider: React.FC<AddonContextProviderProps> = ({
const { currentProject } = useContext(Context);
const { defaultDeploymentTarget, isDefaultDeploymentTargetLoading } =
useDefaultDeploymentTarget();
const { getAddon, deleteAddon } = useAddon();
const { getAddon } = useAddon();
const {
addon,
isLoading: isAddonLoading,
Expand All @@ -64,7 +62,6 @@ export const AddonContextProvider: React.FC<AddonContextProviderProps> = ({
addonName,
refreshIntervalSeconds: 5,
});
const queryClient = useQueryClient();

const status = useAddonStatus({
projectId: currentProject?.id,
Expand All @@ -78,20 +75,6 @@ export const AddonContextProvider: React.FC<AddonContextProviderProps> = ({
!!currentProject &&
currentProject.id !== -1;

const deleteContextAddon = useCallback(async () => {
if (!paramsExist || !addon) {
return;
}

await deleteAddon({
projectId: currentProject.id,
deploymentTargetId: defaultDeploymentTarget.id,
addon,
});

await queryClient.invalidateQueries(["listAddons"]);
}, [paramsExist]);

if (isDefaultDeploymentTargetLoading || isAddonLoading || !paramsExist) {
return <Loading />;
}
Expand All @@ -118,7 +101,6 @@ export const AddonContextProvider: React.FC<AddonContextProviderProps> = ({
projectId: currentProject.id,
deploymentTarget: defaultDeploymentTarget,
status,
deleteAddon: deleteContextAddon,
}}
>
{children}
Expand Down
Loading

0 comments on commit 4500261

Please sign in to comment.