· 5 min read Posted by Kevin Schildhorn
Publishing artifacts to Google Cloud Storage
How to create a Custom Artifact Manager for KMMBridge
In the last post we went over how to create a simple custom Artifact Manager. In this post we’ll make that Artifact Manager useful and have it upload to Google Cloud Storage.
Uploading to Google Cloud Storage
Auth
Before we get started diving into code, there’s one very important topic to discuss: authentication. While proper authentication is important, it makes development much trickier. Publishing artifacts with authentication is usually no issue, however package managers like Cocoapods and SPM want a static url they can point at without any hurdles.
Let’s quickly go over the way publishing artifacts works, in very simple terms. First the artifact is uploaded to a site, whether it’s GitHub or Google Cloud Storage or wherever. Then when using SPM or Cocoapods you are passing in the location of your artifact and the manager pulls it down. For example in the swift example they are referencing a dependency like so:
dependencies: [
.package(
url: "https://github.com/apple/example-package-playingcard.git",
from: "3.0.0"
),
],
SPM is referencing a GitHub url for a public repo, that means there’s no authentication needed. This works really well as long as you’re ok with that artifact being public, however if you need it to be private then that causes issues. SPM doesn’t prompt you for credentials or an API key, it needs to be able to access it immediately. This is why being able to use things ssh key pairs for authentication can be helpful.
Google Cloud Authentication
Google Cloud uses identity and Access Management(IAM) for authentication, which includes having identities take on roles for accessing data. This means that if you are using authentication on your bucket then you’ll need to manage IAM policies on your bucket, both for reading and writing.
There are different ways you can authenticate, but for this post we’ll be using a Google Cloud Service Account Key when it comes to writing. For reading we are making our bucket contents public for ease of use. Authentication is very tricky as mentioned above, so full authentication support is way out of scope for this post.
Setup
First you’ll need to setup a Google Cloud Storage project with a bucket. You can use Googles quickstart to setup a project with a bucket. For our example we are going to make the contents of our bucket public. Once you have all that setup, we can dive into some code.
The Upload Code
We can use the code from the Upload objects docs as a reference for our ArtifactManager
, and update some things to match what we want. This should go in the deployArtifact
function.
class GoogleCloudArtifactManager(
private val bucketName: String,
private val projectId: String,
) : ArtifactManager {
override fun deployArtifact(task: Task, zipFilePath: File, version: String): String {
// The name in your bucket
val artifactName = "YourArtifactName-$version"
val storage = StorageOptions.newBuilder()
.setProjectId(projectId)
.build()
.service
val blobId = BlobId.of(bucketName, artifactName)
val blobInfo = BlobInfo.newBuilder(blobId).build()
val blob = storage.createFrom(blobInfo, Paths.get(zipFilePath.path))
// Return the public URL generated for the object
return "https://storage.googleapis.com/${bucketName}/${blob.name}.zip"
}
}
The version for both deployArtifact
and configure
comes from the version defined in gradle
Here we are taking our zipFilePath
and uploading it to our bucket. Then we are returning the url to the public item (this is a standard url for public objects).
If we did not want a public URL then we can instead generate an unsigned url like so:
return blob.signUrl(7, TimeUnit.DAYS).path
This will generate a pre-signed url that is accessible for 7 days. Again more details are out of scope for this post, but look into Google Cloud CDN and other methods for authentication.
Configure
We can access build properties in the configure function to further customize our artifact. One common example is to pull the frameworks name from the configuration
lateinit var frameworkName: String
override fun configure(
project: Project,
version: String,
uploadTask: TaskProvider<Task>,
kmmPublishTask: TaskProvider<Task>
) {
this.frameworkName = project.kmmBridgeExtension.frameworkName.get()
}
override fun deployArtifact(task: Task, zipFilePath: File, version: String): String {
val artifactName = "$frameworkName-$version"
...
}
private val Project.kmmBridgeExtension get() = extensions.getByType<KmmBridgeExtension>()
Then in the build.gradle.kts
we can either set this in the kmmbridge block or from your target framework definition:
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "MyFramework"
}
}
}
// -- or --
kmmbridge {
frameworkName = "MyFramework"
}
GitHub Actions
Now that we’ve configured the plugin to upload to Google Cloud Storage, we can update our GitHub Action to call our code. We’ll start with adding actions/checkout@v4
and actions/setup-java@v3
to pull our codebase and configure java, then we need to authenticate with Google Cloud.
To do this we’ll use the google-github-actions/auth@v2 action which should handle authentication. We’ll create and pass in service account key json credentials as credentials_json
.
- uses: 'google-github-actions/auth@v2'
with:
credentials_json: ${{ secrets.GOOGLE_CREDENTIALS }
Then after authentication we will build our framework and call KMMBridge
- name: run build
run: ./gradlew build composeApp:assembleXCFramework
- name: Publishing
run: ./gradlew kmmBridgePublish -PENABLE_PUBLISHING=true --no-daemon --info --stacktrace
We can then call the GitHub action and we should see our artifact in our bucket!
You may run into Google Cloud issues that are outside of the code. For example I ran into an error about the billing account being disabled for my project.
Conclusion
This two part post went over how to create a custom Artifact Manager for Google Cloud. There was a lot covered, not only with KMMBridge but with defining plugins and the expansive Google Cloud, so take your time and re-read if needed. If you still need help you can reach out to us in our Kotlinlang Slack channel #touchlab-tools. Luckily KMMBridge already supports uploading to GitHub, Maven and Amazon S3 out of the box so you should only need this in rare situations. If you do need a fully custom solution though, I do hope this helped.