diff --git a/.github/workflows/jetbrains-integration-test.yml b/.github/workflows/jetbrains-integration-test.yml new file mode 100644 index 00000000000000..4c10bb3f841119 --- /dev/null +++ b/.github/workflows/jetbrains-integration-test.yml @@ -0,0 +1,64 @@ +name: JetBrains Test +on: + workflow_dispatch: + inputs: + secret_gateway_link: + type: string + description: Gateway Link + required: true + secret_access_token: + type: string + description: OAuth2 Access Token + required: true + secret_endpoint: + type: string + description: IDE Endpoint + required: true +jobs: + jetbrains-smoke-test-mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: '1.17.3' + - uses: actions/setup-java@v2 + with: + distribution: zulu + java-version: "11" + - name: Setup FFmpeg + uses: FedericoCarboni/setup-ffmpeg@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: Build Gateway Plugin + working-directory: components/ide/jetbrains/gateway-plugin + run: | + ./gradlew -PpluginVersion=test buildPlugin + - name: Smoke Test + env: + LEEWAY_REMOTE_CACHE_BUCKET: gitpod-core-leeway-cache-branch + working-directory: dev/jetbrains-test + run: | + mkdir ./build && cp -r ../../components/ide/jetbrains/gateway-plugin/build/distributions/gitpod-gateway-test.zip build/gateway-test.zip + + export GATEWAY_LINK=$(jq -r '.inputs.secret_gateway_link' $GITHUB_EVENT_PATH) + export GITPOD_TEST_ACCESSTOKEN=$(jq -r '.inputs.secret_access_token' $GITHUB_EVENT_PATH) + export WS_ENDPOINT=$(jq -r '.inputs.secret_endpoint' $GITHUB_EVENT_PATH) + export GATEWAY_PLUGIN_PATH=$(pwd)/build/gateway-test.zip + + mkdir -p ~/Library/Application\ Support/JetBrains/consentOptions + echo -n "rsch.send.usage.stat:1.1:0:1644945193441" > ~/Library/Application\ Support/JetBrains/consentOptions/accepted + mkdir -p ~/Library/Application\ Support/JetBrains/JetBrainsClient/options + touch ~/Library/Application\ Support/JetBrains/JetBrainsClient/options/ide.general.xml + gradle :test + - name: Move video + if: always() + run: | + cp -r dev/jetbrains-test/video dev/jetbrains-test/build/reports + - name: Save report + if: always() + uses: actions/upload-artifact@v2 + with: + name: video + path: | + dev/jetbrains-test/build/reports \ No newline at end of file diff --git a/.werft/run-integration-tests-ide.yaml b/.werft/run-integration-tests-ide.yaml index f91c3210367349..1c736ab1682608 100644 --- a/.werft/run-integration-tests-ide.yaml +++ b/.werft/run-integration-tests-ide.yaml @@ -67,6 +67,11 @@ pod: secretKeyRef: name: integration-test-user key: token + - name: ROBOQUAT_TOKEN + valueFrom: + secretKeyRef: + name: github-roboquat-automatic-changelog + key: token command: - /bin/bash - -c @@ -74,6 +79,8 @@ pod: sleep 1 set -Eeuo pipefail + printf '{{ toJson .Annotations }}' > context.json + echo "[prep] receiving config..." export GOOGLE_APPLICATION_CREDENTIALS="/config/gcloud/legacy_credentials/cd-gitpod-deployer@gitpod-core-dev.iam.gserviceaccount.com/adc.json" echo "[prep] received config." diff --git a/components/ide/jetbrains/gateway-plugin/src/main/kotlin/io/gitpod/jetbrains/auth/GitpodAuthService.kt b/components/ide/jetbrains/gateway-plugin/src/main/kotlin/io/gitpod/jetbrains/auth/GitpodAuthService.kt index 44bf016368e213..8c5f17493e1f93 100644 --- a/components/ide/jetbrains/gateway-plugin/src/main/kotlin/io/gitpod/jetbrains/auth/GitpodAuthService.kt +++ b/components/ide/jetbrains/gateway-plugin/src/main/kotlin/io/gitpod/jetbrains/auth/GitpodAuthService.kt @@ -171,7 +171,7 @@ internal class GitpodAuthService : OAuthServiceBase() { getAccessToken(gitpodHost) != null fun getAccessToken(gitpodHost: String) = - PasswordSafe.instance.getPassword(getAccessTokenCredentialAttributes(gitpodHost)) + System.getenv("GITPOD_TEST_ACCESSTOKEN") ?: PasswordSafe.instance.getPassword(getAccessTokenCredentialAttributes(gitpodHost)) fun setAccessToken(gitpodHost: String, accessToken: String?) { PasswordSafe.instance.setPassword(getAccessTokenCredentialAttributes(gitpodHost), accessToken) diff --git a/dev/jetbrains-test/.gitignore b/dev/jetbrains-test/.gitignore new file mode 100644 index 00000000000000..f8baeaf8617bd2 --- /dev/null +++ b/dev/jetbrains-test/.gitignore @@ -0,0 +1,4 @@ +.gradle +.idea +build +bin \ No newline at end of file diff --git a/dev/jetbrains-test/build.gradle b/dev/jetbrains-test/build.gradle new file mode 100644 index 00000000000000..a5c8ad893d020e --- /dev/null +++ b/dev/jetbrains-test/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.6.10' +} + +group 'io.gitpod' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() + maven { url = "https://packages.jetbrains.team/maven/p/ij/intellij-dependencies" } +} + +test { + systemProperty('video.save.mode', 'ALL') + systemProperty('gateway_link', System.getenv('GATEWAY_LINK')) + systemProperty('gateway_plugin_path', System.getenv('GATEWAY_PLUGIN_PATH')) + useJUnitPlatform() +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib") + + implementation("com.google.code.gson:gson:2.8.9") + implementation("com.squareup.okhttp3:okhttp:4.9.3") + implementation("com.intellij.remoterobot:remote-robot:0.11.12") + implementation("com.intellij.remoterobot:remote-fixtures:0.11.12") + + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") + + // Logging Network Calls + testImplementation('com.squareup.okhttp3:logging-interceptor:4.9.3') + + // Video Recording + implementation('com.automation-remarks:video-recorder-junit5:2.0') +} \ No newline at end of file diff --git a/dev/jetbrains-test/gradle.properties b/dev/jetbrains-test/gradle.properties new file mode 100644 index 00000000000000..29e08e8ca88fa5 --- /dev/null +++ b/dev/jetbrains-test/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/dev/jetbrains-test/gradle/wrapper/gradle-wrapper.jar b/dev/jetbrains-test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000000..7454180f2ae884 Binary files /dev/null and b/dev/jetbrains-test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/dev/jetbrains-test/gradle/wrapper/gradle-wrapper.properties b/dev/jetbrains-test/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000000..69a9715077f4fe --- /dev/null +++ b/dev/jetbrains-test/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/dev/jetbrains-test/gradlew.bat b/dev/jetbrains-test/gradlew.bat new file mode 100644 index 00000000000000..ac1b06f93825db --- /dev/null +++ b/dev/jetbrains-test/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/dev/jetbrains-test/settings.gradle b/dev/jetbrains-test/settings.gradle new file mode 100644 index 00000000000000..65f29208f6421c --- /dev/null +++ b/dev/jetbrains-test/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'jetbrains-test' + diff --git a/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/Ide.kt b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/Ide.kt new file mode 100644 index 00000000000000..b2b0ce731783b4 --- /dev/null +++ b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/Ide.kt @@ -0,0 +1,40 @@ +package io.gitpod.jetbrains.launcher + +enum class Ide(val code: String, val feedsCode: String) { + IDEA_COMMUNITY("IC", "IIC"), + IDEA_ULTIMATE("IU", "IIU"), + CLION("CL", "CL"), + WEBSTORM("WS", "WS"), + RUBY_MINE("RM", "RM"), + PYCHARM("PY", "PCP"), + GATEWAY("GW", "GW"); + + fun getIdePropertiesEnvVarName() = when (this) { + IDEA_COMMUNITY, IDEA_ULTIMATE -> "IDEA_PROPERTIES" + CLION -> "CLION_PROPERTIES" + WEBSTORM -> if (Os.hostOS() == Os.MAC) "WEBSTORM_PROPERTIES" else "WEBIDE_PROPERTIES" + RUBY_MINE -> "RUBYMINE_PROPERTIES" + PYCHARM -> "PYCHARM_PROPERTIES" + GATEWAY -> "GATEWAY_PROPERTIES" + } + + fun getVmOptionsEnvVarName() = when (this) { + IDEA_COMMUNITY, IDEA_ULTIMATE -> "IDEA_VM_OPTIONS" + CLION -> "CLION_VM_OPTIONS" + WEBSTORM -> if (Os.hostOS() == Os.MAC) "WEBSTORM_VM_OPTIONS" else "WEBIDE_VM_OPTIONS" + RUBY_MINE -> "RUBYMINE_VM_OPTIONS" + PYCHARM -> "PYCHARM_VM_OPTIONS" + GATEWAY -> "GATEWAY_VM_OPTIONS" + }.let { + if (Os.hostOS() == Os.WINDOWS) { + val productPrefix = it.substringBefore("_") + "${productPrefix}64${it.removePrefix(productPrefix)}" + } else { + it + } + } + + companion object { + fun withCode(code: String) = values().single { it.code == code } + } +} \ No newline at end of file diff --git a/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/IdeDownloader.kt b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/IdeDownloader.kt new file mode 100644 index 00000000000000..a0f8a42cb0e458 --- /dev/null +++ b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/IdeDownloader.kt @@ -0,0 +1,86 @@ +package io.gitpod.jetbrains.launcher + +import com.google.gson.JsonParser +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption + +class IdeDownloader @JvmOverloads constructor(private val httpClient: OkHttpClient = OkHttpClient()) { + + private companion object { + const val ROBOT_PLUGIN_VERSION_DEFAULT = "0.11.9" + + fun getRobotServerPluginDownloadUrl(version: String): String = + "https://packages.jetbrains.team/maven/p/ij/intellij-dependencies/com/intellij/remoterobot/robot-server-plugin/$version/robot-server-plugin-$version.zip" + + fun getFeedsOsPropertyName() = when (Os.hostOS()) { + Os.WINDOWS -> "windowsZip" + Os.LINUX -> "linux" + Os.MAC -> "mac" + } + } + + fun downloadAndExtractLatestEap(ide: Ide, toDir: Path): Path { + val idePackage = downloadIde(ide, toDir) + return extractIde(idePackage, toDir) + } + + @JvmOverloads + fun downloadRobotPlugin(toDir: Path, version: String = ROBOT_PLUGIN_VERSION_DEFAULT): Path { + return downloadFile(getRobotServerPluginDownloadUrl(version), toDir.resolve("robot-server-plugin-$version")) + } + + private fun extractIde(idePackage: Path, toDir: Path): Path = when (Os.hostOS()) { + Os.LINUX -> extractTar(idePackage, toDir).single() + Os.MAC -> extractDmgApp(idePackage, toDir) + Os.WINDOWS -> { + val appDir = Files.createDirectory(toDir.resolve(idePackage.fileName.toString().substringBefore(".win.zip"))) + extractZip(idePackage, appDir) + appDir + } + } + + private fun downloadIde(ide: Ide, toDir: Path): Path { + val ideDownloadLink = getIdeDownloadUrl(ide, httpClient) + val idePackageName = ideDownloadLink.substringAfterLast("/").removeSuffix("/") + val targetFile = toDir.resolve(idePackageName) + return downloadFile(ideDownloadLink, targetFile) + } + + private fun downloadFile(url: String, toFile: Path): Path { + return httpClient.newCall(Request.Builder().url(url).build()).execute().use { response -> + check(response.isSuccessful) { "failed to download file from $url" } + Files.newOutputStream(toFile, StandardOpenOption.CREATE_NEW).use { + response.body!!.byteStream().buffered().copyTo(it) + } + toFile + } + } + + private fun getIdeDownloadUrl(ide: Ide, httpClient: OkHttpClient): String { + return httpClient.newCall( + Request.Builder().url( + "https://data.services.jetbrains.com/products/releases".toHttpUrl() + .newBuilder() + .addQueryParameter("code", ide.feedsCode) + .addQueryParameter("platform", getFeedsOsPropertyName()) + .build() + ).build() + ).execute().use { response -> + check(response.isSuccessful) { "failed to get $ide feeds" } + JsonParser.parseReader(response.body!!.charStream()) + .asJsonObject[ide.feedsCode] + .asJsonArray + .firstOrNull { + it.asJsonObject["downloads"]?.asJsonObject?.keySet()?.isNotEmpty() ?: false + } + ?.asJsonObject?.get("downloads") + ?.asJsonObject?.get(getFeedsOsPropertyName()) + ?.asJsonObject?.get("link") + ?.asString ?: error("no suitable ide found") + } + } +} \ No newline at end of file diff --git a/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/IdeLauncher.kt b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/IdeLauncher.kt new file mode 100644 index 00000000000000..7ecc3c1b93d2f6 --- /dev/null +++ b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/IdeLauncher.kt @@ -0,0 +1,149 @@ +package io.gitpod.jetbrains.launcher + +import java.io.File +import java.io.FileNotFoundException +import java.nio.file.* +import java.util.* +import java.util.concurrent.TimeUnit +import java.util.stream.Collectors + +object IdeLauncher { + + fun launchIde( + pathToIde: Path, + additionalProperties: Map, + additionalVmOptions: List, + requiredPluginsArchives: List, + ideSandboxDir: Path, + additionalArgs: List + ): Process { + val configDir = Files.createTempDirectory(ideSandboxDir, "config") + val systemDir = Files.createTempDirectory(ideSandboxDir, "system") + val pluginsDir = Files.createTempDirectory(ideSandboxDir, "plugins") + for (pluginZip in requiredPluginsArchives) { + extractZip(pluginZip, pluginsDir) + } + val logDir = Files.createTempDirectory(ideSandboxDir, "log") + + val ideProperties = + buildIdeProperties(pathToIde, configDir, systemDir, pluginsDir, logDir) + additionalProperties + val vmOptions = readIdeDefaultVmOptions(pathToIde) + additionalVmOptions + + return runIde(pathToIde, ideSandboxDir, ideProperties.mapValues { it.value.toString() }, vmOptions, additionalArgs) + } + + private fun buildIdeProperties( + pathToIde: Path, + configDir: Path, + systemDir: Path, + pluginsDir: Path, + logDir: Path + ): Map { + val defaultIdeProperties = Properties().apply { + val defaultPropertiesFileRelativePath = when (Os.hostOS()) { + Os.MAC -> "Contents/bin/idea.properties" + else -> "bin${File.separator}idea.properties" + } + Files.newBufferedReader(pathToIde.resolve(defaultPropertiesFileRelativePath)).use { + load(it) + } + }.mapKeys { it.key.toString() } + + return mapOf( + "idea.config.path" to configDir.toAbsolutePath(), + "idea.system.path" to systemDir.toAbsolutePath(), + "idea.plugins.path" to pluginsDir.toAbsolutePath(), + "idea.log.path" to logDir.toAbsolutePath(), + "jb.privacy.policy.text" to "", + "jb.consents.confirmation.enabled" to "false", + "native.mac.file.chooser.enabled" to "false", + "ide.mac.file.chooser.native" to "false", + "idea.is.internal" to "true" + ) + defaultIdeProperties + } + + private fun readIdeDefaultVmOptions(pathToIde: Path): List { + val ideBinDir = pathToIde.resolve( + when (Os.hostOS()) { + Os.MAC -> "Contents/bin" + else -> "bin" + } + ) + val ideDefaultVmOptionsFile = Files.list(ideBinDir).filter { + it.fileName.toString().endsWith(".vmoptions") + }.collect(Collectors.toList()).let { vmOptionsFiles -> + if (vmOptionsFiles.size == 1) { + vmOptionsFiles[0] + } else { + vmOptionsFiles.singleOrNull { + it.fileName.toString().contains("64") + } ?: throw FileNotFoundException("failed to find default vmoptions file") + } + } + + return ideDefaultVmOptionsFile.toFile().readLines() + } + + private fun runIde( + pathToIde: Path, + ideSandboxDir: Path, + ideProperties: Map, + vmOptions: List, + additionalArgs: List + ): Process { + val idePropertiesFilePath = ideSandboxDir.resolve("idea.properties").also { file -> + Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING).use { + Properties().apply { + putAll(ideProperties) + store(it, null) + } + } + } + val vmOptionsFilePath = ideSandboxDir.resolve("idea.vmoptions").also { file -> + Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING).use { + it.write(vmOptions.joinToString(System.lineSeparator())) + } + } + + val buildTxtFilePath = + if (Os.MAC === Os.hostOS()) pathToIde.resolve("Contents/Resources/build.txt") else pathToIde.resolve("build.txt") + val ide = Ide.withCode(Files.readAllLines(buildTxtFilePath).single().substringBefore("-")) + + val processBuilder = ProcessBuilder().apply { + environment().putAll( + mapOf( + ide.getIdePropertiesEnvVarName() to idePropertiesFilePath.toAbsolutePath().toString(), + ide.getVmOptionsEnvVarName() to vmOptionsFilePath.toAbsolutePath().toString() + ) + ) + } + + val startupScriptName = when (ide) { + Ide.IDEA_COMMUNITY, Ide.IDEA_ULTIMATE -> "idea" + Ide.CLION -> "clion" + Ide.WEBSTORM -> "webstorm" + Ide.RUBY_MINE -> "rubymine" + Ide.PYCHARM -> "pycharm" + Ide.GATEWAY -> "gateway" + } + val startupScriptPath = when (Os.hostOS()) { + Os.LINUX -> "bin/${startupScriptName}.sh" + Os.WINDOWS -> "bin/${startupScriptName}64.exe" + Os.MAC -> "Contents/MacOS/${findIdeExecutableNameInInfoPlist(pathToIde)}" + } + return processBuilder.command(pathToIde.resolve(startupScriptPath).toAbsolutePath().toString(), *additionalArgs.toTypedArray()).inheritIO() + .start() + } + + private fun findIdeExecutableNameInInfoPlist(pathToIde: Path): String { + return ProcessBuilder().command( + "defaults", + "read", + "${pathToIde.toAbsolutePath()}/Contents/Info", + "CFBundleExecutable" + ).start().let { + it.waitFor(10, TimeUnit.SECONDS) + it.inputStream.bufferedReader().readText().trim() + } + } +} \ No newline at end of file diff --git a/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/Os.kt b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/Os.kt new file mode 100644 index 00000000000000..fe6a7b0a789f58 --- /dev/null +++ b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/Os.kt @@ -0,0 +1,17 @@ +package io.gitpod.jetbrains.launcher + +enum class Os { + WINDOWS, LINUX, MAC; + + companion object { + fun hostOS(): Os { + val osName = System.getProperty("os.name").lowercase() + return when { + osName.contains("win") -> WINDOWS + osName.contains("mac") -> MAC + osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> LINUX + else -> throw Exception("Unknown operation system with name: \"$osName\"") + } + } + } +} diff --git a/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/utils.kt b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/utils.kt new file mode 100644 index 00000000000000..62e14079c77e0f --- /dev/null +++ b/dev/jetbrains-test/src/main/kotlin/io/gitpod/jetbrains/launcher/utils.kt @@ -0,0 +1,114 @@ +package io.gitpod.jetbrains.launcher + +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes +import java.util.stream.Collectors + +fun extractTar(tarFile: Path, to: Path): List { + return getCreatedFilesAfter(to) { + check( + ProcessBuilder() + .command("which", "tar") + .start() + .waitFor() == 0 + ) { "tar binary is not found" } + check( + ProcessBuilder() + .command( + "tar", "xzf", + tarFile.toAbsolutePath().toString(), + "-C", to.toAbsolutePath().toString() + ) + .start() + .waitFor() == 0 + ) { "failed to extract $tarFile" } + } +} + +fun extractZip(zip: Path, to: Path): List { + return getCreatedFilesAfter(to) { + FileSystems.newFileSystem(zip, null).use { fs -> + val root = fs.getPath("/") + Files.walkFileTree(root, object : SimpleFileVisitor() { + override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes?): FileVisitResult { + if (root != dir) { + val targetDir = to.resolve(root.relativize(dir).toString()) + Files.copy(dir, targetDir) + } + return FileVisitResult.CONTINUE + } + + override fun visitFile(file: Path, attrs: BasicFileAttributes?): FileVisitResult { + val targetFile = to.resolve(root.relativize(file).toString()) + Files.copy(file, targetFile) + return FileVisitResult.CONTINUE + } + }) + } + } +} + +fun extractDmgApp(dmg: Path, to: Path): Path { + check(Files.exists(dmg)) { "dmg is not exists" } + check(Files.exists(to)) { "target directory is not exists" } + check(ProcessBuilder().command("which", "hdiutil").start().waitFor() == 0) + check( + ProcessBuilder() + .command("hdiutil", "verify", dmg.toAbsolutePath().toString()) + .start() + .waitFor() == 0 + ) { "${dmg.toAbsolutePath()} is not valid disk image" } + + val mountPoint: Path = Paths.get(to.toAbsolutePath().toString(), "mnt") + if (!Files.exists(mountPoint)) Files.createDirectory(mountPoint) + check( + ProcessBuilder() + .command( + "hdiutil", + "attach", dmg.toAbsolutePath().toString(), + "-mountpoint", mountPoint.toString(), + "-noautoopen", + "-nobrowse" + ) + .inheritIO() + .start() + .waitFor() == 0 + ) { "failed to mount $dmg to $mountPoint" } + + val mounted: Path = + Files.list(mountPoint).filter { it.fileName.toString().endsWith(".app") }.findFirst().orElseThrow { + IllegalStateException("ide app is not found") + } + val app: Path = Paths.get(to.toAbsolutePath().toString(), mounted.fileName.toString()) + + check( + ProcessBuilder().command( + "cp", + "-R", + mounted.toAbsolutePath().toString(), + app.toAbsolutePath().toString() + ).inheritIO().start().waitFor() == 0 + ) { "failed to copy ide app from mounted .dmg" } + + check( + ProcessBuilder() + .command( + "hdiutil", + "detach", mountPoint.toString(), + "-force" + ) + .inheritIO() + .start() + .waitFor() == 0 + ) { "failed to unmount $mountPoint" } + + Files.delete(mountPoint) + + return app +} + +private fun getCreatedFilesAfter(dir: Path, action: () -> Unit): List { + val dirContentBefore = Files.list(dir).collect(Collectors.toList()) + action() + return Files.list(dir).filter { !dirContentBefore.contains(it) }.collect(Collectors.toList()) +} diff --git a/dev/jetbrains-test/src/test/kotlin/io/gitpod/jetbrains/launcher/GatewayLauncherTest.kt b/dev/jetbrains-test/src/test/kotlin/io/gitpod/jetbrains/launcher/GatewayLauncherTest.kt new file mode 100644 index 00000000000000..16c182a6574fd0 --- /dev/null +++ b/dev/jetbrains-test/src/test/kotlin/io/gitpod/jetbrains/launcher/GatewayLauncherTest.kt @@ -0,0 +1,103 @@ +package io.gitpod.jetbrains.launcher + +import com.automation.remarks.junit5.Video +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.fixtures.CommonContainerFixture +import com.intellij.remoterobot.fixtures.ComponentFixture +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.stepsProcessing.StepLogger +import com.intellij.remoterobot.stepsProcessing.StepWorker +import com.intellij.remoterobot.utils.Locators +import com.intellij.remoterobot.utils.hasSingleComponent +import com.intellij.remoterobot.utils.waitFor +import com.intellij.remoterobot.stepsProcessing.log +import okhttp3.OkHttpClient +import org.junit.jupiter.api.* +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.TestWatcher +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.time.Duration +import java.util.concurrent.TimeUnit +import javax.imageio.ImageIO +import javax.swing.Box +import javax.swing.JDialog +import java.util.* +import java.util.prefs.Preferences + +@Timeout(value = 25, unit = TimeUnit.MINUTES) +class GatewayLauncherTest { + companion object { + private var gatewayProcess: Process? = null + private var tmpDir: Path = Files.createTempDirectory("launcher") + private lateinit var remoteRobot: RemoteRobot + + @AfterAll + @JvmStatic + fun cleanUp() { + gatewayProcess?.destroy() + tmpDir.toFile().deleteRecursively() + } + } + + private fun setPreferences(key: String, value: String) { + var prefs = Preferences.userRoot() + val dotIndex = key.lastIndexOf('.') + if (dotIndex > 0) { + val tokenizer = StringTokenizer(key.substring(0, dotIndex), ".", false) + while (tokenizer.hasMoreElements()) { + val str = tokenizer.nextToken() + prefs = prefs.node(str?.lowercase()) + } + } + val lastDotIndex = key.lastIndexOf('.') + val nodeKey = (if (lastDotIndex >= 0) key.substring(lastDotIndex + 1) else key).lowercase() + prefs.put(nodeKey, value) + } + + @Test + @Video + fun test() { + // bypass privacy_policy + setPreferences("jetbrains.privacy_policy.accepted_version", "999.999") + setPreferences("jetbrains.privacy_policy.cwmguesteua_accepted_version", "999.999") + setPreferences("jetbrains.privacy_policy.ij_euaeap_accepted_version", "999.999") + + val gatewayLink = System.getProperty("gateway_link") + val gatewayPluginPath = System.getProperty("gateway_plugin_path") + if (gatewayPluginPath == null || gatewayPluginPath == "") { + fail("please provider gateway plugin path") + } + if (gatewayLink == null || gatewayLink == "") { + fail("please provider gateway link") + } + StepWorker.registerProcessor(StepLogger()) + + val client = OkHttpClient() + remoteRobot = RemoteRobot("http://localhost:8082", client) + val ideDownloader = IdeDownloader(client) + gatewayProcess = IdeLauncher.launchIde( + ideDownloader.downloadAndExtractLatestEap(Ide.GATEWAY, tmpDir), + mapOf("robot-server.port" to 8082), + emptyList(), + listOf( + ideDownloader.downloadRobotPlugin(tmpDir), + Path.of(gatewayPluginPath) + ), + tmpDir, + listOf(gatewayLink) + ) + waitFor(Duration.ofSeconds(90), Duration.ofSeconds(5)) { + remoteRobot.isAvailable() + } + + log.atInfo().log("remoteRobot available") + Thread.sleep(1000 * 120) + } +} + +fun RemoteRobot.isAvailable(): Boolean = runCatching { + callJs("true") +}.getOrDefault(false) \ No newline at end of file diff --git a/dev/jetbrains-test/src/test/resources/log4j2.xml b/dev/jetbrains-test/src/test/resources/log4j2.xml new file mode 100644 index 00000000000000..aad4a7f55068fb --- /dev/null +++ b/dev/jetbrains-test/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/entrypoint.sh b/test/entrypoint.sh index 7fe6e53387f245..3a741452a9453e 100755 --- a/test/entrypoint.sh +++ b/test/entrypoint.sh @@ -5,7 +5,6 @@ # exclude 'e' (exit on any error) # there are many test binaries, each can have failures -set -x test_pattern="*.test" for i in "$@"; do @@ -25,7 +24,7 @@ FAILURE_COUNT=0 # shellcheck disable=SC2045 for i in $(find /tests/ -name "$test_pattern" | sort); do echo "running test: $i" - "$i" "$@"; + "$i" "$@" -test.v; TEST_STATUS=$? if [ "$TEST_STATUS" -ne "0" ]; then FAILURE_COUNT=$((FAILURE_COUNT+1)) diff --git a/test/go.mod b/test/go.mod index bc5c9850902eb8..153b8b0542652c 100644 --- a/test/go.mod +++ b/test/go.mod @@ -11,8 +11,10 @@ require ( github.com/gitpod-io/gitpod/supervisor/api v0.0.0-00010101000000-000000000000 github.com/gitpod-io/gitpod/ws-manager/api v0.0.0-00010101000000-000000000000 github.com/go-sql-driver/mysql v1.5.0 + github.com/google/go-github/v42 v42.0.0 github.com/google/uuid v1.2.0 github.com/prometheus/procfs v0.7.3 + golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 @@ -56,6 +58,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/go-cmp v0.5.6 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect @@ -110,11 +113,10 @@ require ( go.opencensus.io v0.23.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.uber.org/atomic v1.8.0 // indirect - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/mod v0.4.2 // indirect golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect - golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1 // indirect golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect golang.org/x/text v0.3.6 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect diff --git a/test/go.sum b/test/go.sum index 143f35db7811b2..309058e45880c5 100644 --- a/test/go.sum +++ b/test/go.sum @@ -57,6 +57,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I= +github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -86,6 +87,7 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -112,14 +114,17 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bradleyfalzon/ghinstallation/v2 v2.0.3/go.mod h1:tlgi+JWCXnKFx/Y4WtnDbZEINo31N5bcvnCoqieefmk= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -292,6 +297,7 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8 github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= @@ -331,12 +337,14 @@ github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= @@ -350,6 +358,8 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -406,6 +416,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v39 v39.0.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= +github.com/google/go-github/v42 v42.0.0 h1:YNT0FwjPrEysRkLIiKuEfSvBPCGKphW5aS5PxwaoLec= +github.com/google/go-github/v42 v42.0.0/go.mod h1:jgg/jvyI0YlDOM1/ps6XYh04HNQ3vKf0CVko62/EhRg= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -456,6 +471,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 h1:ajue7SzQMywqRjg2fK7dcpc0QhFGpTR2plWfV4EZWR4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0/go.mod h1:r1hZAcvfFXuYmcKyCJI9wlyOPIZUJl6FCB8Cpca/NLE= @@ -514,6 +530,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -714,6 +731,7 @@ github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiB github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -799,6 +817,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -866,9 +885,13 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -878,6 +901,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1071,6 +1095,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1089,13 +1114,16 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1158,6 +1186,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1305,6 +1337,7 @@ gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/segmentio/analytics-go.v3 v3.1.0/go.mod h1:4QqqlTlSSpVlWA9/9nDcPw+FkM2yv1NQoYjUbL9/JAw= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -1370,6 +1403,7 @@ k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= diff --git a/test/leeway-build.sh b/test/leeway-build.sh index 1dcfb2236d9b85..8ba7d364b5cc5c 100755 --- a/test/leeway-build.sh +++ b/test/leeway-build.sh @@ -22,5 +22,8 @@ done echo "building test tests/workspace" go test -trimpath -ldflags="-buildid= -w -s" -o bin/workspace.test -c ./tests/workspace -echo "building test tests/ide" -go test -trimpath -ldflags="-buildid= -w -s" -o bin/ide.test -c ./tests/ide +for COMPONENT in tests/ide/*; do + echo "building test $COMPONENT" + OUTPUT=$(basename "$COMPONENT") + go test -trimpath -ldflags="-buildid= -w -s" -c -o bin/"$OUTPUT".test ./"$COMPONENT" +done diff --git a/test/pkg/integration/apis.go b/test/pkg/integration/apis.go index 274035effcd1f9..c8b4354fdb8145 100644 --- a/test/pkg/integration/apis.go +++ b/test/pkg/integration/apis.go @@ -205,6 +205,14 @@ func WithGitpodUser(name string) GitpodServerOpt { } } +func (c *ComponentAPI) CreateOAuth2Token(user string, scopes []string) (string, error) { + tkn, err := c.createGitpodToken(user, scopes) + if err != nil { + return "", err + } + return tkn, nil +} + // GitpodServer provides access to the Gitpod server API func (c *ComponentAPI) GitpodServer(opts ...GitpodServerOpt) (gitpod.APIInterface, error) { var options gitpodServerOpts @@ -224,7 +232,10 @@ func (c *ComponentAPI) GitpodServer(opts ...GitpodServerOpt) (gitpod.APIInterfac tkn := c.serverStatus.Token[options.User] if tkn == "" { var err error - tkn, err = c.createGitpodToken(options.User) + tkn, err = c.createGitpodToken(options.User, []string{ + "resource:default", + "function:*", + }) if err != nil { return err } @@ -463,7 +474,7 @@ func (c *ComponentAPI) CreateUser(username string, token string) (string, error) return userId, nil } -func (c *ComponentAPI) createGitpodToken(user string) (tkn string, err error) { +func (c *ComponentAPI) createGitpodToken(user string, scopes []string) (tkn string, err error) { id, err := c.GetUserId(user) if err != nil { return "", err @@ -491,7 +502,7 @@ func (c *ComponentAPI) createGitpodToken(user string) (tkn string, err error) { fmt.Sprintf("integration-test-%d", time.Now().UnixNano()), tokenTypeMachineAuthToken, id, - "resource:default,function:*", + strings.Join(scopes, ","), time.Now().Format(time.RFC3339), ) if err != nil { diff --git a/test/tests/ide/jetbrains/gateway_test.go b/test/tests/ide/jetbrains/gateway_test.go new file mode 100644 index 00000000000000..da22f86a039f5b --- /dev/null +++ b/test/tests/ide/jetbrains/gateway_test.go @@ -0,0 +1,172 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package ide + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + "testing" + "time" + + "golang.org/x/oauth2" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + protocol "github.com/gitpod-io/gitpod/gitpod-protocol" + "github.com/gitpod-io/gitpod/test/pkg/integration" + "github.com/google/go-github/v42/github" +) + +type GatewayHostStatus struct { + AppPid int64 `json:"appPid"` + AppVersion string `json:"appVersion"` + IdePath string `json:"idePath"` + Projects []struct { + BackgroundTasksRunning bool `json:"backgroundTasksRunning"` + ControllerConnected bool `json:"controllerConnected"` + GatewayLink string `json:"gatewayLink"` + HTTPLink string `json:"httpLink"` + JoinLink string `json:"joinLink"` + ProjectName string `json:"projectName"` + ProjectPath string `json:"projectPath"` + SecondsSinceLastControllerActivity int64 `json:"secondsSinceLastControllerActivity"` + Users []string `json:"users"` + } `json:"projects"` + RuntimeVersion string `json:"runtimeVersion"` + UnattendedMode bool `json:"unattendedMode"` +} + +func GetHttpContent(url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + b, err := ioutil.ReadAll(resp.Body) + return b, err +} + +func TestJetBrainsGatewayWorkspace(t *testing.T) { + userToken, _ := os.LookupEnv("USER_TOKEN") + ideName, ok := annotations["jetbrains-ide"] + if !ok { + t.Skip("not provide a special IDE") + } + roboquatToken, ok := os.LookupEnv("ROBOQUAT_TOKEN") + if !ok { + t.Skip("this test need github action run permission") + } + integration.SkipWithoutUsername(t, username) + integration.SkipWithoutUserToken(t, userToken) + + f := features.New("Start a workspace and let JetBrains Gateway connect"). + WithLabel("component", "IDE"). + Assess("it can start a workspace and let JetBrains Gateway connect", func(_ context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) + defer cancel() + + api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client()) + t.Cleanup(func() { + api.Done(t) + }) + + config, err := integration.GetServerConfig(cfg.Namespace(), cfg.Client()) + if err != nil { + t.Fatal(err) + } + + t.Logf("connecting to server...") + server, err := api.GitpodServer(integration.WithGitpodUser(username)) + if err != nil { + t.Fatal(err) + } + t.Logf("connected to server") + + t.Logf("get or create user") + _, err = api.CreateUser(username, userToken) + if err != nil { + t.Fatal(err) + } + + t.Logf("starting workspace") + nfo, stopWs, err := integration.LaunchWorkspaceFromContextURL(ctx, "referrer:jetbrains-gateway:"+ideName+"/https://github.com/gitpod-io/spring-petclinic", username, api) + if err != nil { + t.Fatal(err) + } + defer stopWs(true) + + t.Logf("get oauth2 token") + oauthToken, err := api.CreateOAuth2Token(username, []string{ + "function:getGitpodTokenScopes", + "function:getIDEOptions", + "function:getOwnerToken", + "function:getWorkspace", + "function:getWorkspaces", + "function:listenForWorkspaceInstanceUpdates", + "resource:default", + }) + if err != nil { + t.Fatal(err) + } + + t.Logf("make port 63342 public") + _, err = server.OpenPort(ctx, nfo.Workspace.ID, &protocol.WorkspaceInstancePort{Port: 63342, Visibility: "public"}) + if err != nil { + t.Fatal(err) + } + + gatewayLink := fmt.Sprintf("jetbrains-gateway://connect#gitpodHost=%s&workspaceId=%s", strings.TrimPrefix(config.HostURL, "https://"), nfo.Workspace.ID) + + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: roboquatToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + githubClient := github.NewClient(tc) + + t.Logf("trigger github action") + _, err = githubClient.Actions.CreateWorkflowDispatchEventByFileName(ctx, "gitpod-io", "gitpod", "jetbrains-integration-test.yml", github.CreateWorkflowDispatchEventRequest{ + Ref: "main", + Inputs: map[string]interface{}{ + "secret_gateway_link": gatewayLink, + "secret_access_token": oauthToken, + "secret_endpoint": strings.TrimPrefix(nfo.LatestInstance.IdeURL, "https://"), + }, + }) + if err != nil { + t.Fatal(err) + } + + checkUrl := fmt.Sprintf("https://63342-%s/codeWithMe/unattendedHostStatus?token=gitpod", strings.TrimPrefix(nfo.LatestInstance.IdeURL, "https://")) + + t.Logf("waiting result") + testStatus := false + for ctx.Err() == nil { + time.Sleep(1 * time.Second) + body, _ := GetHttpContent(checkUrl) + var status GatewayHostStatus + err = json.Unmarshal(body, &status) + if err != nil { + continue + } + if len(status.Projects) == 1 && status.Projects[0].ControllerConnected { + testStatus = true + break + } + } + if !testStatus { + t.Fatal(ctx.Err()) + } + time.Sleep(time.Second * 10) + return ctx + }). + Feature() + testEnv.Test(t, f) +} diff --git a/test/tests/ide/jetbrains/main_test.go b/test/tests/ide/jetbrains/main_test.go new file mode 100644 index 00000000000000..bd6866fc0dc7e0 --- /dev/null +++ b/test/tests/ide/jetbrains/main_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package ide + +import ( + "context" + "encoding/json" + "io/ioutil" + "os" + "testing" + + "github.com/gitpod-io/gitpod/test/pkg/integration" + "sigs.k8s.io/e2e-framework/pkg/env" +) + +var ( + testEnv env.Environment + username string + namespace string + kubeconfig string + annotations map[string]string +) + +func TestMain(m *testing.M) { + b, _ := ioutil.ReadFile("context.json") + json.Unmarshal(b, &annotations) + username, namespace, testEnv, _, kubeconfig, _ = integration.Setup(context.Background()) + os.Exit(testEnv.Run(m)) +} diff --git a/test/tests/ide/main_test.go b/test/tests/ide/vscode/main_test.go similarity index 100% rename from test/tests/ide/main_test.go rename to test/tests/ide/vscode/main_test.go diff --git a/test/tests/ide/python_ws_test.go b/test/tests/ide/vscode/python_ws_test.go similarity index 99% rename from test/tests/ide/python_ws_test.go rename to test/tests/ide/vscode/python_ws_test.go index 6c18555b256c32..2d51cad633a6e3 100644 --- a/test/tests/ide/python_ws_test.go +++ b/test/tests/ide/vscode/python_ws_test.go @@ -127,6 +127,7 @@ func TestPythonExtWorkspace(t *testing.T) { Dir: "/workspace/python-test-workspace", Command: "yarn", Args: []string{ + "--silent", "gp-code-server-test", fmt.Sprintf("--endpoint=%s", nfo.LatestInstance.IdeURL), fmt.Sprintf("--authCookie=%s", base64.StdEncoding.EncodeToString([]byte(jsonCookie))),