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.