Skip to content

Commit

Permalink
POR-1902 validate name against existing porter apps (#3774)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Oct 9, 2023
1 parent 27e49bb commit 32cd139
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 9 deletions.
16 changes: 15 additions & 1 deletion api/server/handlers/porter_app/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,28 @@ func (c *ValidatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
}
}

existingApps, err := c.Repo().PorterApp().ListPorterAppsByProjectID(project.ID)
if err != nil {
err := telemetry.Error(ctx, span, err, "error listing porter apps by project id")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

if appProto.Name == "" {
err := telemetry.Error(ctx, span, nil, "app proto name is empty")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-name", Value: appProto.Name})

for _, existingApp := range existingApps {
if existingApp.Name == appProto.Name {
err := telemetry.Error(ctx, span, nil, "app with the provided name already exists in the project")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}
}
telemetry.WithAttributes(span,
telemetry.AttributeKV{Key: "app-name", Value: appProto.Name},
telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetId},
telemetry.AttributeKV{Key: "commit-sha", Value: request.CommitSHA},
)
Expand Down
8 changes: 7 additions & 1 deletion dashboard/src/lib/porter-apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ export const deletionValidator = z.object({
export const clientAppValidator = z.object({
name: z.object({
readOnly: z.boolean(),
value: z.string(),
value: z
.string()
.min(1, { message: "Name must be at least 1 character" })
.max(30, { message: "Name must be 30 characters or less" })
.regex(/^[a-z0-9-]{1,61}$/, {
message: 'Lowercase letters, numbers, and "-" only.',
}),
}),
envGroups: z
.object({ name: z.string(), version: z.bigint() })
Expand Down
28 changes: 21 additions & 7 deletions dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
const { maxCPU, maxRAM } = useClusterResourceLimits({
projectId: currentProject?.id,
clusterId: currentCluster?.id,
})
});

const { data: porterApps = [] } = useQuery<string[]>(
["getPorterApps", currentProject?.id, currentCluster?.id],
Expand Down Expand Up @@ -513,7 +513,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
placeholder="ex: academic-sophon"
type="text"
width="300px"
error={errors.app?.name?.message}
error={errors.app?.name?.value?.message}
disabled={name.readOnly}
disabledTooltip={
"You may only edit this field in your porter.yaml."
Expand Down Expand Up @@ -585,10 +585,23 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
<ImageSettings
projectId={currentProject.id}
imageUri={image?.repository ?? ""}
setImageUri={(uri: string) => setValue("source.image", { ...image, repository: uri })}
setImageUri={(uri: string) =>
setValue("source.image", {
...image,
repository: uri,
})
}
imageTag={image?.tag ?? ""}
setImageTag={(tag: string) => setValue("source.image", { ...image, tag })}
resetImageInfo={() => setValue("source.image", { ...image, repository: "", tag: "" })}
setImageTag={(tag: string) =>
setValue("source.image", { ...image, tag })
}
resetImageInfo={() =>
setValue("source.image", {
...image,
repository: "",
tag: "",
})
}
/>
)
) : null}
Expand All @@ -614,8 +627,9 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
}
>
{detectedServices.count > 0
? `Detected ${detectedServices.count} service${detectedServices.count > 1 ? "s" : ""
} from porter.yaml.`
? `Detected ${detectedServices.count} service${
detectedServices.count > 1 ? "s" : ""
} from porter.yaml.`
: `Could not detect any services from porter.yaml. Make sure it exists in the root of your repo.`}
</Text>
</AppearingDiv>
Expand Down
11 changes: 11 additions & 0 deletions internal/repository/gorm/porter_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ func (repo *PorterAppRepository) ListPorterAppByClusterID(clusterID uint) ([]*mo
return apps, nil
}

// ListPorterAppsByProjectID returns a list of PorterApps by project ID.
func (repo *PorterAppRepository) ListPorterAppsByProjectID(projectID uint) ([]*models.PorterApp, error) {
apps := []*models.PorterApp{}

if err := repo.db.Where("project_id = ?", projectID).Find(&apps).Error; err != nil {
return nil, err
}

return apps, nil
}

func (repo *PorterAppRepository) ReadPorterAppByName(clusterID uint, name string) (*models.PorterApp, error) {
app := &models.PorterApp{}

Expand Down
1 change: 1 addition & 0 deletions internal/repository/porter_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type PorterAppRepository interface {
ReadPorterAppsByProjectIDAndName(projectID uint, name string) ([]*models.PorterApp, error)
CreatePorterApp(app *models.PorterApp) (*models.PorterApp, error)
ListPorterAppByClusterID(clusterID uint) ([]*models.PorterApp, error)
ListPorterAppsByProjectID(projectID uint) ([]*models.PorterApp, error)
UpdatePorterApp(app *models.PorterApp) (*models.PorterApp, error)
DeletePorterApp(app *models.PorterApp) (*models.PorterApp, error)
}
6 changes: 6 additions & 0 deletions internal/repository/test/porter_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,16 @@ func (repo *PorterAppRepository) UpdatePorterApp(app *models.PorterApp) (*models
return nil, errors.New("cannot write database")
}

// ListPorterAppByClusterID is a test method that is not implemented
func (repo *PorterAppRepository) ListPorterAppByClusterID(clusterID uint) ([]*models.PorterApp, error) {
return nil, errors.New("cannot write database")
}

// ListPorterAppsByProjectID is a test method that is not implemented
func (repo *PorterAppRepository) ListPorterAppsByProjectID(projectID uint) ([]*models.PorterApp, error) {
return nil, errors.New("cannot write database")
}

func (repo *PorterAppRepository) DeletePorterApp(app *models.PorterApp) (*models.PorterApp, error) {
return nil, errors.New("cannot write database")
}

0 comments on commit 32cd139

Please sign in to comment.