Merge branch 'main' into maa/vault-okta-jwt
						commit
						37bf439653
					
				@ -0,0 +1,6 @@
 | 
			
		||||
version: 2
 | 
			
		||||
updates:
 | 
			
		||||
  - package-ecosystem: "github-actions"
 | 
			
		||||
    directory: "/"
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: "weekly"
 | 
			
		||||
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								| 
		 After Width: | Height: | Size: 1.5 MiB  | 
@ -0,0 +1,35 @@
 | 
			
		||||
---
 | 
			
		||||
display_name: Cursor IDE
 | 
			
		||||
description: Add a one-click button to launch Cursor IDE
 | 
			
		||||
icon: ../.icons/cursor.svg
 | 
			
		||||
maintainer_github: coder
 | 
			
		||||
verified: true
 | 
			
		||||
tags: [ide, cursor, helper]
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Cursor IDE
 | 
			
		||||
 | 
			
		||||
Add a button to open any workspace with a single click in Cursor IDE.
 | 
			
		||||
 | 
			
		||||
Uses the [Coder Remote VS Code Extension](https://github.com/coder/cursor-coder).
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
module "cursor" {
 | 
			
		||||
  source   = "registry.coder.com/modules/cursor/coder"
 | 
			
		||||
  version  = "1.0.18"
 | 
			
		||||
  agent_id = coder_agent.example.id
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
### Open in a specific directory
 | 
			
		||||
 | 
			
		||||
```tf
 | 
			
		||||
module "cursor" {
 | 
			
		||||
  source   = "registry.coder.com/modules/cursor/coder"
 | 
			
		||||
  version  = "1.0.18"
 | 
			
		||||
  agent_id = coder_agent.example.id
 | 
			
		||||
  folder   = "/home/coder/project"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,89 @@
 | 
			
		||||
import { describe, expect, it } from "bun:test";
 | 
			
		||||
import {
 | 
			
		||||
  executeScriptInContainer,
 | 
			
		||||
  runTerraformApply,
 | 
			
		||||
  runTerraformInit,
 | 
			
		||||
  testRequiredVariables,
 | 
			
		||||
} from "../test";
 | 
			
		||||
 | 
			
		||||
describe("cursor", async () => {
 | 
			
		||||
  await runTerraformInit(import.meta.dir);
 | 
			
		||||
 | 
			
		||||
  testRequiredVariables(import.meta.dir, {
 | 
			
		||||
    agent_id: "foo",
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("default output", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const coder_app = state.resources.find(
 | 
			
		||||
      (res) => res.type === "coder_app" && res.name === "cursor",
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(coder_app).not.toBeNull();
 | 
			
		||||
    expect(coder_app?.instances.length).toBe(1);
 | 
			
		||||
    expect(coder_app?.instances[0].attributes.order).toBeNull();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("adds folder", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      folder: "/foo/bar",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("adds folder and open_recent", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      folder: "/foo/bar",
 | 
			
		||||
      open_recent: "true",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("adds folder but not open_recent", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      folder: "/foo/bar",
 | 
			
		||||
      openRecent: "false",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("adds open_recent", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      open_recent: "true",
 | 
			
		||||
    });
 | 
			
		||||
    expect(state.outputs.cursor_url.value).toBe(
 | 
			
		||||
      "cursor://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("expect order to be set", async () => {
 | 
			
		||||
    const state = await runTerraformApply(import.meta.dir, {
 | 
			
		||||
      agent_id: "foo",
 | 
			
		||||
      order: "22",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const coder_app = state.resources.find(
 | 
			
		||||
      (res) => res.type === "coder_app" && res.name === "cursor",
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(coder_app).not.toBeNull();
 | 
			
		||||
    expect(coder_app?.instances.length).toBe(1);
 | 
			
		||||
    expect(coder_app?.instances[0].attributes.order).toBe(22);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,62 @@
 | 
			
		||||
terraform {
 | 
			
		||||
  required_version = ">= 1.0"
 | 
			
		||||
 | 
			
		||||
  required_providers {
 | 
			
		||||
    coder = {
 | 
			
		||||
      source  = "coder/coder"
 | 
			
		||||
      version = ">= 0.23"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "agent_id" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The ID of a Coder agent."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "folder" {
 | 
			
		||||
  type        = string
 | 
			
		||||
  description = "The folder to open in Cursor IDE."
 | 
			
		||||
  default     = ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "open_recent" {
 | 
			
		||||
  type        = bool
 | 
			
		||||
  description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open."
 | 
			
		||||
  default     = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
variable "order" {
 | 
			
		||||
  type        = number
 | 
			
		||||
  description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
 | 
			
		||||
  default     = null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
data "coder_workspace" "me" {}
 | 
			
		||||
data "coder_workspace_owner" "me" {}
 | 
			
		||||
 | 
			
		||||
resource "coder_app" "cursor" {
 | 
			
		||||
  agent_id     = var.agent_id
 | 
			
		||||
  external     = true
 | 
			
		||||
  icon         = "/icon/cursor.svg"
 | 
			
		||||
  slug         = "cursor"
 | 
			
		||||
  display_name = "Cursor Desktop"
 | 
			
		||||
  order        = var.order
 | 
			
		||||
  url = join("", [
 | 
			
		||||
    "cursor://coder.coder-remote/open",
 | 
			
		||||
    "?owner=",
 | 
			
		||||
    data.coder_workspace_owner.me.name,
 | 
			
		||||
    "&workspace=",
 | 
			
		||||
    data.coder_workspace.me.name,
 | 
			
		||||
    var.folder != "" ? join("", ["&folder=", var.folder]) : "",
 | 
			
		||||
    var.open_recent ? "&openRecent" : "",
 | 
			
		||||
    "&url=",
 | 
			
		||||
    data.coder_workspace.me.access_url,
 | 
			
		||||
    "&token=$SESSION_TOKEN",
 | 
			
		||||
  ])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
output "cursor_url" {
 | 
			
		||||
  value       = coder_app.cursor.url
 | 
			
		||||
  description = "Cursor IDE Desktop URL."
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
email=${ARTIFACTORY_EMAIL}
 | 
			
		||||
%{ for REPO in REPOS ~}
 | 
			
		||||
${REPO.SCOPE}registry=${JFROG_URL}/artifactory/api/npm/${REPO.NAME}
 | 
			
		||||
//${JFROG_HOST}/artifactory/api/npm/${REPO.NAME}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}
 | 
			
		||||
%{ endfor ~}
 | 
			
		||||
@ -1,19 +1,129 @@
 | 
			
		||||
import { serve } from "bun";
 | 
			
		||||
import { describe } from "bun:test";
 | 
			
		||||
import { describe, expect, it } from "bun:test";
 | 
			
		||||
import {
 | 
			
		||||
  createJSONResponse,
 | 
			
		||||
  findResourceInstance,
 | 
			
		||||
  runTerraformInit,
 | 
			
		||||
  runTerraformApply,
 | 
			
		||||
  testRequiredVariables,
 | 
			
		||||
} from "../test";
 | 
			
		||||
 | 
			
		||||
describe("jfrog-oauth", async () => {
 | 
			
		||||
  type TestVariables = {
 | 
			
		||||
    agent_id: string;
 | 
			
		||||
    jfrog_url: string;
 | 
			
		||||
    package_managers: string;
 | 
			
		||||
 | 
			
		||||
    username_field?: string;
 | 
			
		||||
    jfrog_server_id?: string;
 | 
			
		||||
    external_auth_id?: string;
 | 
			
		||||
    configure_code_server?: boolean;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  await runTerraformInit(import.meta.dir);
 | 
			
		||||
 | 
			
		||||
  testRequiredVariables(import.meta.dir, {
 | 
			
		||||
    agent_id: "some-agent-id",
 | 
			
		||||
    jfrog_url: "http://localhost:8081",
 | 
			
		||||
    package_managers: "{}",
 | 
			
		||||
  const fakeFrogApi = "localhost:8081/artifactory/api";
 | 
			
		||||
  const fakeFrogUrl = "http://localhost:8081";
 | 
			
		||||
  const user = "default";
 | 
			
		||||
 | 
			
		||||
  it("can run apply with required variables", async () => {
 | 
			
		||||
    testRequiredVariables<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: "{}",
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//TODO add more tests
 | 
			
		||||
  it("generates an npmrc with scoped repos", async () => {
 | 
			
		||||
    const state = await runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: JSON.stringify({
 | 
			
		||||
        npm: ["global", "@foo:foo", "@bar:bar"],
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    const coderScript = findResourceInstance(state, "coder_script");
 | 
			
		||||
    const npmrcStanza = `cat << EOF > ~/.npmrc
 | 
			
		||||
email=${user}@example.com
 | 
			
		||||
registry=http://${fakeFrogApi}/npm/global
 | 
			
		||||
//${fakeFrogApi}/npm/global/:_authToken=
 | 
			
		||||
@foo:registry=http://${fakeFrogApi}/npm/foo
 | 
			
		||||
//${fakeFrogApi}/npm/foo/:_authToken=
 | 
			
		||||
@bar:registry=http://${fakeFrogApi}/npm/bar
 | 
			
		||||
//${fakeFrogApi}/npm/bar/:_authToken=
 | 
			
		||||
 | 
			
		||||
EOF`;
 | 
			
		||||
    expect(coderScript.script).toContain(npmrcStanza);
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'jf npmc --global --repo-resolve "global"',
 | 
			
		||||
    );
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'if [ -z "YES" ]; then\n  not_configured npm',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("generates a pip config with extra-indexes", async () => {
 | 
			
		||||
    const state = await runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: JSON.stringify({
 | 
			
		||||
        pypi: ["global", "foo", "bar"],
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    const coderScript = findResourceInstance(state, "coder_script");
 | 
			
		||||
    const pipStanza = `cat << EOF > ~/.pip/pip.conf
 | 
			
		||||
[global]
 | 
			
		||||
index-url = https://${user}:@${fakeFrogApi}/pypi/global/simple
 | 
			
		||||
extra-index-url =
 | 
			
		||||
    https://${user}:@${fakeFrogApi}/pypi/foo/simple
 | 
			
		||||
    https://${user}:@${fakeFrogApi}/pypi/bar/simple
 | 
			
		||||
 | 
			
		||||
EOF`;
 | 
			
		||||
    expect(coderScript.script).toContain(pipStanza);
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'jf pipc --global --repo-resolve "global"',
 | 
			
		||||
    );
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'if [ -z "YES" ]; then\n  not_configured pypi',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("registers multiple docker repos", async () => {
 | 
			
		||||
    const state = await runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: JSON.stringify({
 | 
			
		||||
        docker: ["foo.jfrog.io", "bar.jfrog.io", "baz.jfrog.io"],
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    const coderScript = findResourceInstance(state, "coder_script");
 | 
			
		||||
    const dockerStanza = ["foo", "bar", "baz"]
 | 
			
		||||
      .map((r) => `register_docker "${r}.jfrog.io"`)
 | 
			
		||||
      .join("\n");
 | 
			
		||||
    expect(coderScript.script).toContain(dockerStanza);
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'if [ -z "YES" ]; then\n  not_configured docker',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it("sets goproxy with multiple repos", async () => {
 | 
			
		||||
    const state = await runTerraformApply<TestVariables>(import.meta.dir, {
 | 
			
		||||
      agent_id: "some-agent-id",
 | 
			
		||||
      jfrog_url: fakeFrogUrl,
 | 
			
		||||
      package_managers: JSON.stringify({
 | 
			
		||||
        go: ["foo", "bar", "baz"],
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    const proxyEnv = findResourceInstance(state, "coder_env", "goproxy");
 | 
			
		||||
    const proxies = ["foo", "bar", "baz"]
 | 
			
		||||
      .map((r) => `https://${user}:@${fakeFrogApi}/go/${r}`)
 | 
			
		||||
      .join(",");
 | 
			
		||||
    expect(proxyEnv["value"]).toEqual(proxies);
 | 
			
		||||
 | 
			
		||||
    const coderScript = findResourceInstance(state, "coder_script");
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'jf goc --global --repo-resolve "foo"',
 | 
			
		||||
    );
 | 
			
		||||
    expect(coderScript.script).toContain(
 | 
			
		||||
      'if [ -z "YES" ]; then\n  not_configured go',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
[global]
 | 
			
		||||
index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${try(element(REPOS, 0), "")}/simple
 | 
			
		||||
extra-index-url =
 | 
			
		||||
%{ for REPO in try(slice(REPOS, 1, length(REPOS)), []) ~}
 | 
			
		||||
    https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPO}/simple
 | 
			
		||||
%{ endfor ~}
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
email=${ARTIFACTORY_EMAIL}
 | 
			
		||||
%{ for REPO in REPOS ~}
 | 
			
		||||
${REPO.SCOPE}registry=${JFROG_URL}/artifactory/api/npm/${REPO.NAME}
 | 
			
		||||
//${JFROG_HOST}/artifactory/api/npm/${REPO.NAME}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN}
 | 
			
		||||
%{ endfor ~}
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
[global]
 | 
			
		||||
index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${try(element(REPOS, 0), "")}/simple
 | 
			
		||||
extra-index-url =
 | 
			
		||||
%{ for REPO in try(slice(REPOS, 1, length(REPOS)), []) ~}
 | 
			
		||||
    https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPO}/simple
 | 
			
		||||
%{ endfor ~}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue