Skip to content

Commit

Permalink
fix(communityEdition): fix unprocessable entity error (#5931)
Browse files Browse the repository at this point in the history
Fix an issue where garden failed with the error "unprocessable entity"
some times when user has more than 24 projects.

Previously core listed all projects and if it didn't find project with
given name in the first list of results, it would attempt creating the
project. Creating the project fails then with "unprocessable entity"
because a project with the given name already exists.
stefreak authored Apr 19, 2024

Verified

This commit was signed with the committer’s verified signature.
1 parent 4717da8 commit bfcc1fe
Showing 3 changed files with 26 additions and 29 deletions.
39 changes: 19 additions & 20 deletions core/src/cloud/api.ts
Original file line number Diff line number Diff line change
@@ -360,41 +360,40 @@ export class CloudApi {
return this.registeredSessions.has(id)
}

async getAllProjects(): Promise<CloudProject[]> {
async getProjectByName(projectName: string): Promise<CloudProject | undefined> {
let response: ListProjectsResponse

try {
response = await this.get<ListProjectsResponse>(`/projects`)
response = await this.get<ListProjectsResponse>(
`/projects?name=${encodeURIComponent(projectName)}&exactMatch=true`
)
} catch (err) {
this.log.debug(`Attempt to list all projects failed with ${err}`)
throw err
throw new CloudApiError({
message: `Failed to find Garden Cloud project by name: ${err}`,
})
}

const projectList: ListProjectsResponse["data"] = response.data

return projectList.map((p) => {
const project = toCloudProject(p)
// Cache the entry by ID
this.projects.set(project.id, project)
return project
})
}

async getProjectByName(projectName: string): Promise<CloudProject | undefined> {
const allProjects = await this.getAllProjects()

const projects = allProjects.filter((p) => p.name === projectName)
const projectList = response.data

// Expect a single project, otherwise we fail with an error
if (projects.length > 1) {
if (projectList.length > 1) {
throw new CloudApiDuplicateProjectsError({
message: deline`Found an unexpected state with multiple projects using the same name, ${projectName}.
Please make sure there is only one project with the given name.
Projects can be deleted through the Garden Cloud UI at ${this.domain}`,
})
}

return projects[0]
if (projectList.length === 0) {
return undefined
}

const project = toCloudProject(projectList[0])

// Cache the entry by ID
this.projects.set(project.id, project)

return project
}

async createProject(projectName: string): Promise<CloudProject> {
4 changes: 0 additions & 4 deletions core/test/helpers/api.ts
Original file line number Diff line number Diff line change
@@ -48,10 +48,6 @@ export class FakeCloudApi extends CloudApi {
}
}

override async getAllProjects(): Promise<CloudProject[]> {
return [(await this.getProjectById(apiProjectId))!]
}

override async createProject(name: string): Promise<CloudProject> {
return {
id: apiProjectId,
12 changes: 7 additions & 5 deletions core/test/unit/src/garden.ts
Original file line number Diff line number Diff line change
@@ -944,7 +944,7 @@ describe("Garden", () => {

it("should use the community dashboard domain", async () => {
scope.get("/api/token/verify").reply(200, {})
scope.get(`/api/projects`).reply(200, { data: [cloudProject] })
scope.get(`/api/projects?name=test&exactMatch=true`).reply(200, { data: [cloudProject] })

const cloudApi = await makeCloudApi(DEFAULT_GARDEN_CLOUD_DOMAIN)

@@ -993,7 +993,7 @@ describe("Garden", () => {
})
it("should throw if unable to fetch or create project", async () => {
scope.get("/api/token/verify").reply(200, {})
scope.get(`/api/projects`).reply(500, {})
scope.get(`/api/projects?name=test&exactMatch=true`).reply(500, {})
log.root["entries"] = []

const cloudApi = await makeCloudApi(DEFAULT_GARDEN_CLOUD_DOMAIN)
@@ -1018,15 +1018,17 @@ describe("Garden", () => {
expect(expectedLog[0].level).to.eql(0)
const cleanMsg = stripAnsi(resolveMsg(expectedLog[0]) || "").replace("\n", " ")
expect(cleanMsg).to.eql(
`Fetching or creating project ${projectName} from ${DEFAULT_GARDEN_CLOUD_DOMAIN} failed with error: HTTPError: Response code 500 (Internal Server Error)`
`Fetching or creating project ${projectName} from ${DEFAULT_GARDEN_CLOUD_DOMAIN} failed with error: Error: Failed to find Garden Cloud project by name: HTTPError: Response code 500 (Internal Server Error)`
)
expect(error).to.exist
expect(error!.message).to.eql("Response code 500 (Internal Server Error)")
expect(error!.message).to.eql(
"Failed to find Garden Cloud project by name: HTTPError: Response code 500 (Internal Server Error)"
)
expect(scope.isDone()).to.be.true
})
it("should not fetch secrets", async () => {
scope.get("/api/token/verify").reply(200, {})
scope.get(`/api/projects`).reply(200, { data: [cloudProject] })
scope.get(`/api/projects?name=test&exactMatch=true`).reply(200, { data: [cloudProject] })
scope
.get(`/api/secrets/projectUid/${projectId}/env/${envName}`)
.reply(200, { data: { SECRET_KEY: "secret-val" } })

0 comments on commit bfcc1fe

Please sign in to comment.