· 5 min read Posted by Kevin Schildhorn

Publishing artifacts to Google Cloud Storage

How to create a Custom Artifact Manager for KMMBridge

How do you take a custom Artifact Manager and point it to Google Cloud Storage?This post will guide you through the process on to publish artifacts to Google Cloud using your Artifact Manager.
Tatiana P https://unsplash.com/photos/a-large-bridge-spanning-over-a-large-body-of-water-UfFSO6JOlKE
Credit: Tatiana P https://unsplash.com/photos/a-large-bridge-spanning-over-a-large-body-of-water-UfFSO6JOlKE
How to create a Custom Artifact Manager for KMMBridge (2 Post Series)

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.