JetBrains Space Help

From GitHub Actions

This section summarizes key differences between GitHub Actions and Space Automation and provides sample scripts to help you with the migration process.

Disclaimer: All the information about GitHub Actions is taken from the official GitHub website and is valid only at the moment of posting this document. As any product evolves over time, this information may be already outdated.

Main concepts

GitHub Actions

Space Automation

Workflow is a configurable automated process.

  • Consists of jobs that can depend on each other.

  • Stored in a .yml file.

  • The .github directory can contain as many workflows as you want.

(Not yet available) Pipeline is a configurable automated process.

  • Consists of stages that can depend on each other.

Job is a defined task made up of steps.

  • A job specifies execution environment using the runs-on keyword.

  • Jobs can run in parallel or be dependent on the status of a previous job and run sequentially.

Job is a defined task made up of steps.

  • Unlike GitHub Action's job, a job in Automation does not set execution environment.

  • All jobs in a script always run in parallel.

Step is a building block for a job.

  • All steps within a job run on the same virtual machine.

  • Steps can run commands or actions.

  • Steps run sequentially.

Step is a building block for a job.

  • A step specifies execution environment using the container("image_name") keyword. So, each step within a job runs in a separate container.

  • Steps can run commands, .sh files, or arbitrary Kotlin code.

  • By default, steps run in parallel, but you can run them sequentially as well.

Action is a portable building block that you can run as a step.

  • An action has input and output variables.

  • You can use an action by referencing it in your script.

  • An action can run a Docker container or JavaScript code.

  • Actions are distributed as npm packages.

No matching entity.

Hello World!

name: Hello World on: push jobs: hello-world: name: Hello world job runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Hello from vm run: echo Hello World!

Hello World!

job("Hello world") { container(displayName = "Say Hello", image = "ubuntu") { shellScript { content = "echo Hello World!" } } }

Configuration as code

Both products provide no UI to configure your builds. The configuration is performed using script files.

GitHub Actions

Space Automation

YAML-based DSL for declarative scripts. If you need scripts that change execution flow, you can create custom actions based on JavaScript.

Kotlin-based DSL. You can implement any complex execution logic in place using pure Kotlin.

Script location

GitHub Actions

Space Automation

Workflows are located in separate files inside the .github directory. Custom actions are located in separate directories.

/ project root ├─── .github │ ├─── action1 // custom action │ │ ├─── action.yml │ │ └─── ... │ ├─── action2 // custom action │ │ ├─── action.yml │ │ └─── ... │ ├─── myworkflow1.yml // workflow file │ ├─── myworkflow2.yml // workflow file ...

All configuration is done using a single .space.kts file.

/ project root ├─── .space.kts ...

Execution environment

GitHub Actions

Space Automation

  • Runners are hosted in cloud and are based on virtual machines with the following configuraion: 2 cores, 7 GB RAM, 14 GB SSD. You can also run Docker containers inside these virtual machines.

  • OS: a list of predefined OSs that includes Windows Server 2019, Ubuntu, and macOS Catalina.

  • Instead of using cloud runners, you can use self-hosted runners.

  • Runners are hosted in cloud and are based on Docker containers with customizable configuration: up to 4 vCPUs, 16 GB RAM, 30 GB SSD.

  • OS: any Linux-based container image.

  • Self-hosted runners are not yet supported but planned.

Execution limits

GitHub Actions

Space Automation

  • Job execution time up to 6 hours.

  • Workflow run time up to 72 hours.

  • Job queue time for self-hosted runners up to 24 hours.

  • API requests up to 1000 in an hour across all actions in a repo.

  • Concurrent jobs number depends on plan: 20 in Free and up to 180 in Enterprise (5 and 50 for macOS).

  • Job matrix up to 256 jobs per workflow run.

  • More details

  • Job execution time up to 2 hours.

  • Job queue time: none.

  • API requests: no limits.

  • Jobs number (concurrent or sequential): up to 100 in a script, up to 50 containers per each job.

Running commands

GitHub Actions

Space Automation

name: A workflow for showing project dir on: push jobs: show: name: Show dir content runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Show dir run: | echo Project dir: ls ./
job("Show dir content") { container(image = "ubuntu:latest") { shellScript { content = """ echo Project dir: ls ./ """ } } }

Running code

GitHub Actions

Space Automation

Running imperative code requires creating a custom action. For example, the following script gets a random joke from icanhazdadjoke.com.

Workflow in ./github/myworkflow.yml:

name: Get a joke on: push jobs: action: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: ha-ha uses: ./.github/actions/joke-action

Action in .github/actions/joke-action/action.yml:

name: "Get a joke action" description: "Use an external API to get a joke" outputs: joke-output: description: The resulting joke from the icanhazdadjokes API runs: using: "node12" main: "main.js"

.github/actions/joke-action/main.js:

const getJoke = require("./joke"); const core = require("@actions/core"); async function run() { const joke = await getJoke(); console.log(joke); core.setOutput("joke-output", joke); } run();

.github/actions/joke-action/joke.js:

const request = require("request-promise"); const options = { method: "GET", uri: "https://icanhazdadjoke.com/", headers: { Accept: "application/json" }, json: true }; async function getJoke() { const res = await request(options); return res.joke; } module.exports = getJoke;

Any step allows you to run arbitrary Kotlin code in place. For example, the following script gets a random joke from icanhazdadjoke.com using the OkHttp client.

./.space.kts:

@file:DependsOn("com.squareup.okhttp:okhttp:2.7.4", "org.json:json:20200518") import com.squareup.okhttp.* import org.json.JSONObject job("Get random joke") { container(image = "amazoncorretto:17-alpine") { kotlinScript { val client = OkHttpClient() val request = Request.Builder() .url("http://icanhazdadjoke.com") .addHeader("Accept", "application/json") .build() val response = client.newCall(request).execute() val jData = response.body().string() val jObject = JSONObject(jData) val joke = jObject.get("joke").toString() println(joke) } } }

Trigger events

GitHub Actions

Space Automation

To set run triggers for a job, you should use the on keyword. The following triggers are supported:

  • Git push.

  • Schedule (only in the main branch).

  • Webhook events: basically, any event in the system, like pull request, comment to an issue, and others. As webooks have payload, you can create various trigger filters.

  • Manually: the workflow_dispatch event lets you run a workflow manually using the REST API or from the UI.

All triggers excepting schedule let you specify a filter by branch using the branches keyword.

name: My workflow on: schedule: - cron: '0 8 * * *' push: branches: - main pull_request: branches: - mybranch

To set run triggers for a job, you should use the startOn keyword. The following triggers are supported:

  • Git push (enabled by default).

  • Schedule (only in the main branch).

  • Git branch is deleted.

  • Code review is opened or closed (only in the main branch).

You cannot explicitly specify a filter by branch: Automation will go through the list of triggers in .space.kts in every branch. So, if you want to disable a trigger in a particular branch, you should do it in it's .space.kts.

job("My job") { startOn { schedule { cron("0 8 * * *") } codeReviewOpened {} } }

Sharing files

GitHub Actions

Space Automation

You can share files between jobs using the upload-artifact and download-artifact actions. For example:

jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: ... - uses: actions/upload-artifact@main with: name: step-artifacts path: public/ test: needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/download-artifact@main with: name: step-artifacts path: public - name: ...

You can share files between jobs using the special API or directly through the containers' /mnt/space/share directory.

job("Example shell scripts") { container(image = "ubuntu") { shellScript { content = """ ./gradlew build cp -R ./output /mnt/space/share/output """ } } container(image = "ubuntu") { shellScript { content = """ echo Ouput content ls /mnt/space/share/output """ } } }

Sharing parameters

GitHub Actions

Space Automation

You can specify the output of one action to be the input of the following action. In the example below, the joke-action has the output joke-output.

name: JS Actions on: push jobs: action: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: ha-ha uses: ./.github/actions/joke-action id: jokes - name: create-issue uses: ./.github/actions/issue-maker with: repo-token: ${{secrets.GITHUB_TOKEN}} joke: ${{steps.jokes.outputs.joke-output}}

All steps in a job share the parameter storage. To access the storage, you should use the parameters API:

job("Reuse params") { container(displayName = "Set param", image = "amazoncorretto:17-alpine") { kotlinScript { api -> api.parameters["joke"] = "funny" } } container(displayName = "Use param", image = "amazoncorretto:17-alpine") { kotlinScript { api -> println("Here's the joke: ${api.parameters["joke"]}!") } } }

Publishing packages

GitHub Actions

Space Automation

GitHub provides its own package repository manager GitHub Packages. Workflows can use it to get and publish packages. To do this, you can use special actions (or write your own one that uses the corresponding external tool). To authenticate in GitHub Packages, you should use the github.actor parameter and the GITHUB_TOKEN secret. For example, this is how you publish a Docker image:

- name: Build container image uses: docker/build-push-action@v1 with: username: ${{github.actor}} password: ${{secrets.GITHUB_TOKEN}} registry: docker.pkg.github.com repository: UserName/my_project/my_package tag_with_sha: true

The package repository manager in Space is called Space Packages. Some automation scenarios do not require authentication. For example, for Docker, Automation provides a special API and authentication in Packages is not required.

job("Build and push Docker") { docker { build { args["HTTP_PROXY"] = "http://10.20.30.2:1234" labels["vendor"] = "mycompany" } push("mycompany.registry.jetbrains.space/p/prj/mydocker/myimage") { tag = "version1.0" } } }

Nevertheless, there are cases when providing authentication credentials is required. For example, in Gradle, credentials must be specified in build.gradle. You can do this using environment variables JB_SPACE_CLIENT_ID and JB_SPACE_CLIENT_SECRET. For example, build.gradle:

publishing { repositories { maven { credentials { username = "$System.env.JB_SPACE_CLIENT_ID" password = "$System.env.JB_SPACE_CLIENT_SECRET" } url = "https://maven.pkg.jetbrains.space/mycompany/p/projectkey/my-maven-repo" } } }

Using other product subsystems

GitHub and JetBrains Space contain multiple subsystems: project repositories, issue tracking, CI/CD, and others. Both GitHub Actions and Space Automation let you access these subsystems from your build scripts.

GitHub Actions

Space Automation

Working with other subsystems is possible in custom actions using JavaScript code. GitHub Actions provides subsystems API in separate npm modules that you should reference in your actions. For example, core functionality is available in the @actions/core package. So, after you create an action, you should run npm install --save @actions/core @actions/github to install dependencies. In the action's .js file, you reference the packages:

const core = require("@actions/core"); const github = require("@actions/github");

There's also an official client oktokit that lets you work with GitHub subsystems. For example, this is how you can use it to create a new issue with custom data:

const core = require("@actions/core"); const github = require("@actions/github"); async function run() { try { const issueTitle = core.getInput("issue-title"); const jokeBody = core.getInput("joke"); const token = core.getInput("repo-token"); const octokit = github.getOctokit(token); const newIssue = await octokit.issues.create({ repo: github.context.repo.repo, owner: github.context.repo.owner, title: issueTitle, body: jokeBody }); } catch (err) { core.setFailed(err.message); } } run()

In Space, each subsystem provides an API for accessing it. In .space.kts, you have access to the Kotlin HTTP API client and make calls to any subsystem. For example, to send a message to a chat:

job("build and publish") { container(image = "gradle") { kotlinScript { api -> try { api.gradle("build") } catch (ex: Exception) { val recipient = MessageRecipient.Channel(ChatChannel.FromName("CI-channel")) val content = ChatMessage.Text("Build failed") api.space().chats.messages.sendMessage(recipient, content) } } } }

Using services

GitHub Actions

Space Automation

For example, this is how you run a service container with PostgreSQL:

jobs: container-job: runs-on: ubuntu-latest container: node:10.18-jessie services: # Label used to access the service container postgres: image: postgres env: POSTGRES_PASSWORD: 1234

The service runs in a separate container inside the container that runs the job. Network resources are shared between containers. The service container hostname is defined by the label (postgres in our example).

For example, this is how you run a service container with PostgreSQL:

job("service") { container(image = "node:10.18-jessie") { service("postgres") { env["POSTGRES_PASSWORD"] = "1234" } } }

The service runs in a separate container inside the container that runs the step. Network resources are shared between containers. The service container hostname is defined by the image name (postgres in our example).

Secrets and parameters

GitHub Actions

Space Automation

You can create secrets on the project or organization level. To make a secret available to an action, you must set the secret as an input or environment variable in the workflow file.

name: Secrets on: push jobs: use_secrets: runs-on: ubuntu-latest steps: - shell: bash env: MY_SECRET: ${{ secrets.MySecret }} run: | echo My secret is "$MY_SECRET"

You can create secrets and parameters only on the project level. To access secrets and parameters you should use environmental variables and a special API.

job("Use secrets") { container(image = "ubuntu:latest") { env["MY_SECRET"] = Secrets("my-secret") shellScript { content = "echo My secret is ${'$'}MY_SECRET" } } }

Custom workflows

GitHub Actions

Space Automation

Apart from CI/CD, you can create custom workflows: custom scenarios that are triggered by various events in the system and can change the system state. How does it look: Place a .yml file with the custom workflow into the project’s .github/workflows directory. In the file specify how the workflow must be triggered (it can be any event in the system, see Trigger events). For example, you can add a label to a pull request after it gets a preset number of approvals.

Not yet available.

What is planned: You can create a custom Automation workflow that can be triggered by Space webhooks. As in .space.kts, you have access to Space HTTP API client, you can use it to perform any actions in Space: send messages to Chats, create Documents or Blog posts, create and close code reviews, and so on.

Running build matrix

GitHub Actions

Space Automation

Using strategy.matrix, you can run various job configurations. For example, running build for different framework versions.

strategy: matrix: node: [6, 8, 10] steps: - uses: actions/setup-node@v1 with: # The Node.js version to configure node-version: ${{ matrix.node }}

Not yet available. As a workaround, you can create a number of jobs – one for each version.

User interface

GitHub Actions

Space Automation

The UI is represented with the Actions tab of a particular project. Here you can view workflow logs, run and cancel workflow execution, view artifacts. Test results are available only in the form of logs.

The UI is represented with the Jobs page of a particular project. Here you can view, run, cancel jobs, view artifacts, and more. Test results are shown with a table that lets you instantly see failed tests.

Sample scripts. Hello World

GitHub Actions

Space Automation

Using a 'hello-world' container:

./.github/workflows/main.yml

name: Hello World on: push jobs: build: name: Hello world action runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: ./action-a

./action-a/action.yml

name: "Hello Actions" description: "Say hello" author: "me@example.com" runs: using: "docker" image: "hello-world"

Using a 'hello-world' container

job("Hello world") { container(image = "hello-world") }

Using the echo command:

name: Hello World on: push jobs: build: name: Hello world action runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Hello from vm run: echo Hello world!

Using the echo command:

job("Hello world") { container(image = "ubuntu") { shellScript { content = "echo Hello world!" } } }

Sample scripts. Build and run tests using Gradle

GitHub Actions

Space Automation

First, you should configure Java in a virtual machine using the setup-java action and then run the project's Gradle wrapper.

name: Java CI on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - name: Build with Gradle run: ./gradlew build

Use the special gradlew syntax sugar to pass the build command to the Gradle wrapper in a container with Java on board.

job("Build and run tests") { gradlew("amazoncorretto:17-alpine", "build") }

Sample scripts. Build and publish a Docker image

GitHub Actions

Space Automation

Build-and-Push-Docker-Image: runs-on: ubuntu-latest needs: test name: Docker Build, Tag, Push steps: - name: Checkout uses: actions/checkout@v1 - name: Build container image uses: docker/build-push-action@v1 with: username: ${{github.actor}} password: ${{secrets.GITHUB_TOKEN}} registry: docker.pkg.github.com repository: UserName/myproject/mypackage tag_with_sha: true

Use the special docker syntax sugar.

job("Build and push Docker") { docker { build { context = "docker" file = "./docker/Dockerfile" args["HTTP_PROXY"] = "http://10.20.30.2:1234" labels["vendor"] = "mycompany" } push("mycompany.registry.jetbrains.space/p/myproject/myrepo/myimage") { tag = "version1.0" } } }
Last modified: 15 December 2023