Go 1.25 is out now for z/OS and we thought it was a great time to go over how porting your Go applications to z/OS is now easier than ever! With the release of GitLab Ultimate for IBM Z earlier this year, let's take a closer look at how we went about porting Gitlab Runner to z/OS.
How Was Runner Ported?
To provide some context let's briefly go over Wharf. Wharf is IBM's tool for automatically porting Go programs to z/OS. It does this by attempting to fix build errors that are caused by dependencies. These build errors are usually simple fixes like adding a build tag or updating to a newer version. However, there are cases Wharf is not able to handle, especially for applications that are more complex like Gitlab Runner. That isn't to say that Wharf can't help, as it certainly did for Runner.
When we initially began porting Runner, we started off by trying to see how far we could get with using Wharf. We found that out of the hundreds of dependencies, only a handful of them needed manual work to be compatible. Let's take a look at one of the dependencies, creack/pty
(this was for version 1.1.21). This is an example of a package that required some manual source code changes. In this case, we needed to add a z/OS build tag and make a z/OS-specific implementation of a I/O utility function. Below you can see the diff we used to apply a patch to the package:
ioctl_inner.go | 4 ++--
ioctl_zos.go | 20 ++++++++++++++++++++
2 files changed, 22 insertions(+), 2 deletions(-)
create mode 100644 ioctl_zos.go
diff --git a/ioctl_inner.go b/ioctl_inner.go
index 272b50b..51960ba 100644
--- a/ioctl_inner.go
+++ b/ioctl_inner.go
@@ -1,5 +1,5 @@
-//go:build !windows && !solaris && !aix
-// +build !windows,!solaris,!aix
+//go:build !windows && !solaris && !aix && !zos
+// +build !windows,!solaris,!aix,!zos
package pty
diff --git a/ioctl_zos.go b/ioctl_zos.go
new file mode 100644
index 0000000..2635063
--- /dev/null
+++ b/ioctl_zos.go
@@ -0,0 +1,20 @@
+//go:build zos
+// +build zos
+
+package pty
+
+import "syscall"
+
+// Local syscall const values.
+const (
+ TIOCGWINSZ = syscall.TIOCGWINSZ
+ TIOCSWINSZ = syscall.TIOCSWINSZ
+)
+
+func ioctlInner(fd, cmd, ptr uintptr) error {
+ err := syscall.Ioctl(int(fd), int(cmd), ptr)
+ if err != nil {
+ return err
+ }
+ return nil
+}
--
2.42.1
At the time of writing, this dependency has had it's compatibility changes upstreamed - that is, the changes necessary for it to work on z/OS are now officially part of the open-source version.
After making our changes to the dependencies, we moved on to Runner itself. The process was very similar - we attempted a build of the application in which we ran into some build errors, this time requiring changes to the source code of Runner. If you look at the zOpen Community repo for Gitlab Runner, you can see our various patch files. You'll notice that many of them are very simple fixes - most of them are just applying build tags for z/OS. Here's a change that's a bit more involved but still simple. We needed to change the implementation of a utility function, lchtimes
so that on z/OS, the file has it's permissions set before the file times are modified:
---
commands/helpers/archive/tarzstd/ops_unix.go | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/commands/helpers/archive/tarzstd/ops_unix.go b/commands/helpers/archive/tarzstd/ops_unix.go
index 8020a8801..4bc51d24e 100644
--- a/commands/helpers/archive/tarzstd/ops_unix.go
+++ b/commands/helpers/archive/tarzstd/ops_unix.go
@@ -29,7 +29,12 @@ func lchmod(name string, mode os.FileMode) error {
}
func lchtimes(name string, mode os.FileMode, atime, mtime time.Time) error {
- at := unix.NsecToTimeval(atime.UnixNano())
+ if runtime.GOOS == "zos" {
+ if err := lchmod(name, mode); err != nil {
+ return err
+ }
+ }
+ at := unix.NsecToTimeval(atime.UnixNano())
mt := unix.NsecToTimeval(mtime.UnixNano())
tv := [2]unix.Timeval{at, mt}
--
2.42.1
Once all of our fixes we're applied and the Runner was able to build, we moved on to tests. For Runner, we knew that certain features would not work from the start. Docker is not available on z/OS (but Podman is!) so any Runner functionality related to it would not work, thus any failing tests for Docker were not cause for concern.
How You Can Port Your Go Applications
So what are the main takeaways from porting GitLab Runner? The two that matter the most are:
- Using Wharf makes porting Go applications to z/OS much easier
- Porting is a repeatable 3-step process: Build, Patch, Repeat!
Wharf has made porting Go programs to z/OS easier than ever. A testament to this is the wide number of available tools and utilities for zOpen Community that are written in Go! To get a more detailed breakdown of how to port a Go package check out this guide on zOpen Community. Happy porting!