Go on z/OS

Go on z/OS

Go on z/OS

This group is a space for open discussions and sharing technical information, and also provides a connection to the wider Go open source community for anyone who is interested in Go on z/OS

 View Only

Go on z/OS 1.24 Container Updates

By Joon Lee posted Fri March 07, 2025 12:13 PM

  

With the new release of Go 1.24 for z/OS, we're excited to provide an overview of the some of the new features that you can leverage inside a container. In this blog we will briefly highlight the new interactions that Go and COBOL can achieve and how to orchestrate containerized Db2 applications.

Prerequisites

There are a few things you need to have before continuing:

  • A z/OS 3.1 system with z/OS UNIX System Services
    • A z/OS 2.5 system with the latest APARs will also work
  • The z/OS Containers Platform (zOSCP) suite of container tools
  • An SMP/E license for the IBM Open Enterprise SDK for Go and zOSCP that come with their respective entitlement keys
    • Entitlement keys should be found in the attached entitlement memos

You can get the image with the following command:

podman pull icr.io/zoscp/golang:latest --creds iamapikey:<go image entitlement key>

Go and COBOL

COBOL is still a widely used language on the mainframe and now with Go 1.24, developers can leverage the modern features that Go has to offer through the use of callbacks. This blog will not go over the specifics of how this interaction works. For now, all you need to know is that it can be done and that we can containerize the resulting application. We will walk through an example that will demonstrate how you can build a Go application that calls a COBOL program, which then calls back to Go. Go with COBOL-callbacks! 

In this example we will be building an application in which a Go program passes a Go pointer function to a COBOL program. The COBOL program will be built as a DLL which then invokes with arguments that are printed in the Go program. The resulting application will then be containerized. Note that we will be building the COBOL program on the host system, so a COBOL compiler will be required. The following files should be placed in the same directory.

The COBOL program, we will name this file mycobol.ccc.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SUBPROG.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
      * int TESTAPI(const char *astring,
      *     int *intp, int mycount) 
      
       01  ASTRING.
           05 FILLER PIC X(5) VALUE 'JOHN'.
           05 FILLER PIC X    VALUE X'00'.
       01  ULTIMATEANSWER  PIC S9(8) COMP VALUE 42.
       01  MYCOUNT  PIC S9(8) COMP VALUE 777.
       01  RESULT       PIC S9(8) COMP VALUE 0.

       LINKAGE SECTION.
       01 FP     USAGE IS FUNCTION-POINTER.

       PROCEDURE DIVISION  USING BY VALUE FP.
           DISPLAY "in PROCEDURE division".
           DISPLAY "about to call function pointer"
           CALL FP USING
           BY CONTENT   ASTRING,
           BY REFERENCE ULTIMATEANSWER ,
           BY VALUE     MYCOUNT
           RETURNING RESULT.
           DISPLAY "RESULT " RESULT.
              GOBACK.
       END PROGRAM 'SUBPROG'.

We can build the program into a DLL with the below commands. We have named it XDDLL.

cob2 -q64 -vV -comprc_ok=8 -q"list,LP(64)" -qexportall -c mycobol.cbl
cob2 -Wl,dll,lp64 -o XDDLL mycobol.o

The Go application - main.go. Notice that the mechanism for calling COBOL is using CGO.

package main

/*
#cgo LDFLAGS: XDDLL.x

typedef int (*callback_t)(const char *astring, int *intp, int mycount);

int SUBPROG(callback_t cb);

extern int TESTAPI(char *astring, int *intp, int mycount);

*/
import "C"

import (
	"fmt"
	"runtime"

	"github.com/ibmruntimes/go-recordio/v2/utils"
	"github.com/indece-official/go-ebcdic"
)

//export TESTAPI
func TESTAPI(astring *C.char, intp *C.int, mycount C.int) C.int {
	utils.ThreadAsciiMode()
	str, _ := ebcdic.Decode([]byte(C.GoString(astring)), ebcdic.EBCDIC037)
	fmt.Println("A STRING: ", str)
	fmt.Println("AN INT: ", *intp)
	fmt.Println("MYCOUNT: ", mycount)
	utils.ThreadEbcdicMode()
	return C.int(400)
}

func main() {
	runtime.LockOSThread()   // lock thread
	utils.ThreadEbcdicMode() // COBOL must run in ebcdic
	ret := C.SUBPROG((C.callback_t)(C.TESTAPI))
	utils.ThreadAsciiMode()  // restore
	runtime.UnlockOSThread() // unlock thread
	fmt.Printf("Call COBOL SUBPROG returns %d\n", ret)
}

The go.mod for this sample application.

module gocobol

go 1.24.0

require github.com/ibmruntimes/go-recordio/v2 v2.0.0-20241015235118-65d1e5a7469b

require github.com/indece-official/go-ebcdic v1.2.0

Now that we have all the code and the DLL of the COBOL program we need, lets take a look at the container file we will  be using:

FROM golang:latest

WORKDIR $HOME/demo

COPY ./XDDLL* ./
COPY ./*.go ./
COPY ./go.mod ./go.mod
COPY ./go.sum ./go.sum

ENV LIBPATH=$LIBPATH:/go/lib

RUN go build -o /gocobol

CMD ["/gocobol"]

To build the image and then run the container we can do the following:

podman build -t gocobol .
podman run --rm gocobol

That's how you can get started with containerizing a Go application that makes use of COBOL callbacks. There are some intricacies involved with COBOL callbacks in Go that are outside the scope of this blog, if you would like to know more, I highly encourage you to check out this blog that goes into more detail.

Go and DB2

In a previous blog, we covered how you can natively access Db2 on z/OS through Go. In this section we will go over how you can containerize your Go and Db2 application. To keep things simple we will follow and use the example from the blog mentioned earlier and demonstrate how to containerize it. Note that this under the assumption that you have created your own DSNAOINI file as it is necessary for the container to work. Make sure to copy it to the same directory where you are creating the files below.

First lets create our Go source code. It should be identical to the code from the Db2 blog. Remember to edit the line that contains con := "DSN=<DSN>" to whatever your data source name is. It could be the same as your SUBSYSTEM value.

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/ibmdb/go_ibm_db"
)

func Create_Con(con string) *sql.DB {
	db, err := sql.Open("go_ibm_db", con)
	if err != nil {
		fmt.Println(err)
		return nil
	}
	return db
}

// Creating a table.

func create(db *sql.DB) error {
	_, err := db.Exec("DROP table SAMPLE")
	if err != nil {
		_, err := db.Exec("create table SAMPLE(ID varchar(20),NAME varchar(20),LOCATION varchar(20),POSITION varchar(20))")
		if err != nil {
			return err
		}
	} else {
		_, err := db.Exec("create table SAMPLE(ID varchar(20),NAME varchar(20),LOCATION varchar(20),POSITION varchar(20))")
		if err != nil {
			return err
		}
	}
	fmt.Println("TABLE CREATED")
	return nil
}

// Inserting row.

func insert(db *sql.DB) error {
	st, err := db.Prepare("Insert into SAMPLE(ID,NAME,LOCATION,POSITION) values('3242','Mike','Hyderabad','Manager')")
	if err != nil {
		return err
	}
	st.Query()
	return nil
}

// This API selects the data from the table and prints it.

func display(db *sql.DB) error {
	st, err := db.Prepare("select * from SAMPLE")
	if err != nil {
		return err
	}
	err = execquery(st)
	if err != nil {
		return err
	}
	return nil
}

func execquery(st *sql.Stmt) error {
	rows, err := st.Query()
	if err != nil {
		return err
	}
	cols, _ := rows.Columns()
	fmt.Printf("%s    %s   %s    %s\n", cols[0], cols[1], cols[2], cols[3])
	fmt.Println("-------------------------------------")
	defer rows.Close()
	for rows.Next() {
		var t, x, m, n string
		err = rows.Scan(&t, &x, &m, &n)
		if err != nil {
			return err
		}
		fmt.Printf("%v  %v   %v   %v\n", t, x, m, n)
	}
	return nil
}

func main() {
	con := "DSN=<DSN>"
	type Db *sql.DB
	var re Db
	re = Create_Con(con)
	err := create(re)
	if err != nil {
		fmt.Println(err)
	}
	err = insert(re)
	if err != nil {
		fmt.Println(err)
	}
	err = display(re)
	if err != nil {
		fmt.Println(err)
	}
}

Now that we have our Go source code ready to go, we can go ahead create our container file. The values of IBM_DB_HOME and SUBSYSTEM will vary depending on how Db2 has been setup on your host system.

FROM golang:latest

# Setting up the container to enable Db2 usage with Go

ENV IBM_DB_HOME="<Db2_data server_product_installation_location>"
ENV STEPLIB="$IBM_DB_HOME.SDSNEXIT:$IBM_DB_HOME.SDSNLOAD:$IBM_DB_HOME.SDSNLOD2" \
  SUBSYSTEM="<subsystem_name>" 

COPY ./ODBCOINI_CAF $HOME/ODBCOINI_CAF

ENV DSNAOINI="$HOME/ODBCOINI_CAF"

RUN go install github.com/ibmdb/go_ibm_db/installer@latest

# Setting up and running our application

WORKDIR $HOME/demo/db2-demo

COPY ./*.go ./

RUN go mod init db2-demo &&\
  go mod tidy &&\
  cd .. &&\
  go work init ./* &&\
  cd db2-demo &&\
  go build -o /db2-demo

CMD ["/db2-demo"]

Finally, we can build our image and run our application in a container. Simply run the following 2 commands and you should be all set to go.

podman build -t go-db2 .
podman run --rm go-db2

As you can see, once you have your Go source code up and running you can easily get started containerizing it for rapid testing and deployment!

Multi-Stage Builds

With two new potential use cases for containerizing your Go applications, this is a great chance to remind you of a Podman feature you can take advantage of: multi-stage builds. Multi-stage builds are container files that have multiple FROM statements, where each FROM can use a different base if you choose to. This can allow you to copy artifacts from one stage to another. The container file below builds the third CGO callback example from this blog and copies the resulting binary to the z/OS base image.

FROM golang:latest AS extract

COPY ./cgodemo $HOME/cgodemo

RUN cd $HOME/cgodemo &&\
 GO111MODULE=off go build -o /cgo-callback-sort

FROM zos:latest
COPY --from=extract /cgo-callback-sort /cgo-callback-sort

CMD ["/cgo-callback-sort"]

Notice that in the first FROM statement, we have named this stage "extract" by including an AS <name>. We later reference this named stage in the COPY statement so that we can copy the Go program we just built. Since we do not require the Go compiler to run the program, we can use a different image as our base in the second FROM statement. In this case, we have chosen the z/OS base image for it's small size.

3 comments
36 views

Permalink

Comments

Mon March 10, 2025 05:00 PM

Thank you again  @Joon Lee

:-)

Mon March 10, 2025 10:56 AM

Hi Saul,

Go is not included with z/OS by default, but the PAX edition is free! You can find out more here. As for the features of Go, one of it's headline features is it's support for very robust concurrency. With the fact that Go is a compiled language, you can expect very fast performance from applications written in Go. 

Fri March 07, 2025 05:31 PM

Excellent contribution @Joon Lee

 I'm new to this topic:

  • Is Go included in z/OS (factory default)?
  • What advantages does Go have over Python?

Thank you :-)