Skip to content

Does cf run-task need env/script uploaded befrore running? An elegant way?

I’m working with cloud foundry. My need is to run a database migration aside from my application so that I can trigger the migration in some way (cf command, api call, etc) when I need.

I was recommended to use cf run-task. After checking, my understanding is that, cf run-task is a “SSH client” interface to the cf space since we can even run "echo 1" as a cf task. I see examples on the official cf CLI docs or some online guides saying that cf run-task my-app "bin/rails db:migrate" --name my-task.

But I’m still confused. My questions are:

  1. Does the rail environment need to be setup manually and how? I know we can run something like sudo apt install xxx via SSH, but I think it’s kinda weird in production. Can we do it in a more elegant way like with cf push or another cf task maybe?

  2. So I need to use flyway db migration but how we can upload a script (as a file) or save .sql files to CF space? SSH?

  3. I tested on my cf space. I can only run java because it comes with a java build pack. so the problem becomes: how I can run a java script (same problem as Problem 2) or a java class inside my pushed jar to run the db migration?

I’m new to CF and this might be a really stupid but I’ve spent days on it and have not got a proper answer yet. My temporary solution is to expose a “/dbmigration” api in the controller. Then implement the db migration in the service. Then I can run cf run-task APP_NAME -c "curl https://xxxxx.xxx.xxx/dbmigration" to trigger a db migration when needed. But I still need to handle dbname, user, password, etc in the application service which it’s not preferred.
Thanks ahead.

Answer

I was recommended to use cf run-task. After checking, my understanding is that, cf run-task is a “SSH client” interface to the cf space since we can even run “echo 1” as a cf task.

That’s not quite right. A task is a resource that is attached to an app. It triggers the creation of a separate container based on the droplet used for your app wherein the command you set will be executed. A task is expected to have a finite lifetime (i.e. it’ll run, then exit), as opposed to an app that is expected to run and continue running forever.

The task will run in a container configured exactly like the app, this also includes having environment variables set and services bound just like your app.

  1. Does the rail environment need to be setup manually and how? I know we can run something like sudo apt install xxx via SSH, but I think it’s kinda weird in production. Can we do it in a more elegant way like with cf push or another cf task maybe?

The first step is that you need to cf push your Rails app. When you cf push the app, the Ruby buildpack will run and install everything required to run your app. The resulting droplet will then be available for use to run your app or other tasks.

It is a little trickier if you want to ensure that your task runs before your app starts, which is generally what you want for database migrations.

Here’s how you’d do that:

  1. Run cf push --no-start. That argument ensures your app won’t start. Unfortunately, it also means the app doesn’t stage so we can’t run a task yet.
  2. Run cf v3-packages my-cool-app (v6 cf cli) or cf packages my-cool-app (v7 cf cli). Copy the most recent package guid, if it’s your first push there will only be one.
  3. Run cf v3-stage my-cool-app --package-guid <package-guid> (v6 cf cli) or cf stage my-cool-app --package-guid <package-guid> (v7 cf cli). You’ll see output that’s similar to cf push, it’s the buildpack running and a droplet being created. The output will say Packaged staged and have a droplet guid: <guid> listing. Copy that guid.
  4. Then run cf v3-set-droplet my-cool-app -d <droplet-guid> (v6 cf cli) or cf set-droplet my-cool-app -d <droplet-guid> (v7 cf cli). This will set the droplet created as the currently active droplet.
  5. You can now cf run-task.
  1. So I need to use flyway db migration but how we can upload a script (as a file) or save .sql files to CF space? SSH?

You’d start by pushing your app, as mentioned previously. After the app is staged, you can then run a task which can be any command. In your case, you’d specify the command to run your migration.

The current working directory from which the command will run will be the root of your application (i.e. what you cf push‘d or the -p argument to cf push).

  1. I tested on my cf space. I can only run java because it comes with a java build pack. so the problem becomes: how I can run a java script (same problem as Problem 2) or

You can only run something that is present in the base image or that has been installed by a buildpack. If you’re running the Java buildpack you’ll have java (a JRE specifically) available. It is not on the $PATH by default, but you can run .java-buildpack/open_jdk_jre/bin/java.

For example: cf run-task my-cool-app '.java-buildpack/open_jdk_jre/bin/java -version'

If you need other tools, then you can have multiple buildpacks run. For example, if you need to execute Javascript code you could have Node.js buildpack run. There is also an apt-buildpack which can be used to install additional packages available through apt or .deb files.

Note that the Java buildpack must be the last buildpack listed. It only works as the final buildpack.

a java class inside my pushed jar to run the db migration?

As mentioned, the current working directory is the root of what you push (with Java, it’s the root of your JAR/WAR). So you can do java -cp . com.example.Main where -cp . sets the classpath to include the current directory, where your class files exist. If you look at the command generated by the Java buildpack to run your app, it’ll be similar. It has $PWD/., which is basically the same.