DEV Community

loading...

Simple Util to Pull Code From IBM i

Barrett Otte
IBMi and web developer aspiring to be good at being mediocre at everything.
Updated on ・4 min read

Alt Text

In one of my really dumb side projects I'm making, I have some IBM i code that I'd like to keep in my git repository with some other stuff. I could use the Integrated File System (IFS), but truthfully I don't know enough about IBM i yet to use it correctly.

So, in the meantime I have a quick and dirty python script that lets me pull multiple source members based on a config file, repo.json. This isn't the cleanest, but it works pretty well.

My repo.json is very basic, but lays out a basic IBM i file structure to loop over below with FTP

{
  "library": "BOLIB",
  "spfs": [
    {
      "name": "QRPGLESRC",
      "extension": "RPGLE",
      "members": [
        {
          "name": "FIZZBUZZ"
        }
      ]
    }
  ],
  "output": "./"
}

Basic Idea

If you connect to IBM i over SSH you can navigate and find your source members. For example, I'm going to drill down into my QRPGLESRC file within my BOLIB library.

Alt Text

As you can see, there are my QRPGLESRC members hanging out. All my script does is automate grabbing the source members.

The Code

To start, I use a few basic modules found in the python standard library.

import ftplib  # easily setup FTP connection
import json    # read in repo.json config file
import getpass # used to discretely get password
import os      # used to safely make directories for output

I load my config file, instantiate my FTP client, and get the hostname, username, and password from the console

config = {}
with open("./repo.json", 'r') as f: 
  config = json.load(f)

ftp_client = ftplib.FTP()
host = input("Enter Host: ")
user   = input("Enter User: ")
password = getpass.getpass("Enter Password: ")
# FTP logic below...

Next, I setup my FTP connection shell with very basic error handling

try:
  ftp_client.connect(host, timeout=10000)
  ftp_client.login(user, password)
  # The meat of the code here ...
except ftplib.all_errors as e:
  print("Error occurred with FTP.\n" + str(e))
  exit(1)
except Exception as e:
  print("Some other error occurred\n" + str(e))
  exit(1)
finally:
  ftp_client.quit()

Now the fun stuff, sending FTP RETR command to get data from IBM i

lib = config['library'] # my example only has one library, so no looping

for spf in config['spfs']:
  print("Fetching member(s) from {}/{}".format(lib, spf['name']))

  if not os.path.exists('./'+spf['name']): 
    os.makedirs(spf['name']) # make a directory based on source physical file name

    for mbr in spf['members']:
      resp = []

      # The magic command to get data  ex: BOLIB/QRPGLESRC/FIZZBUZZ.mbr
      cmd = "RETR {}".format("/QSYS.lib/{}.lib/{}.file/{}.mbr").format(
        lib, spf['name'], mbr['name'])

      ftp_client.retrlines(cmd, resp.append) # run the command

      # Create file based on specified extension ex: RPGLE
      filepath = spf['name'] + '/' + mbr['name'] + '.' + spf['extension']

      # Finally, write data to file
      with open(filepath, 'w+') as f:
        for line in resp: f.write(str(line) + '\n')

      print("  Saved " + filepath)

Final Script

import ftplib  # easily setup FTP connection
import json    # read in repo.json config file
import getpass # used to discretely get password
import os      # used to safely make directories for output

config = {}
with open("./repo.json", 'r') as f: 
  config = json.load(f)

ftp_client = ftplib.FTP()
host = input("Enter Host: ")
user   = input("Enter User: ")
password = getpass.getpass("Enter Password: ")

try:
  ftp_client.connect(host, timeout=10000)
  ftp_client.login(user, password)

  lib = config['library'] # my example only has one library, so no looping

  for spf in config['spfs']:
    print("Fetching member(s) from {}/{}".format(lib, spf['name']))

    if not os.path.exists('./'+spf['name']): 
      os.makedirs(spf['name']) # make a directory based on source physical file name

    for mbr in spf['members']:
      resp = []

      # The magic command to get data  ex: BOLIB/QRPGLESRC/FIZZBUZZ.mbr
      cmd = "RETR {}".format("/QSYS.lib/{}.lib/{}.file/{}.mbr").format(
        lib, spf['name'], mbr['name'])

      ftp_client.retrlines(cmd, resp.append) # run the command

      # Create file based on specified extension ex: RPGLE
      filepath = spf['name'] + '/' + mbr['name'] + '.' + spf['extension']

      # Finally, write data to file
      with open(filepath, 'w+') as f:
        for line in resp: f.write(str(line) + '\n')

      print("  Saved " + filepath)

except ftplib.all_errors as e:
  print("Error occurred with FTP.\n" + str(e))
  exit(1)
except Exception as e:
  print("Some other error occurred\n" + str(e))
  exit(1)
finally:
  ftp_client.quit()

The File was Fetched!

This is stored in ./QRPGLESRC/FIZZBUZZ.RPGLE

       /free
       // The classic fizzbuzz program in RPGLE Free
       dcl-s num int(10);

       for num = 1 to 100;
           if %REM(num:3) = 0 and %REM(num:5) = 0;
               dsply ('num - ' + %char(num) + ' FIZZBUZZ');
           elseif %rem(num:3) = 0;
               dsply ('num - ' + %char(num) + ' FIZZ');
           elseif %rem(num:5) = 0;
               dsply ('num - ' + %char(num) + ' BUZZ');
           else;
               dsply ('num - ' + %char(num));
           endif;
       endfor;
       *INLR = *ON;

Simple Batch script

In my repository, I made a little batch script so I could pull IBM i code and commit it with the rest of my repository in one call.

@ECHO OFF
IF [%1] == [] GOTO NOMSG
python ibmi-pull.py && git add . && git commit -m "%~1" && git push origin master
GOTO END
:NOMSG
  ECHO "Enter the commit message!"
:END
PAUSE

Again, there's probably some better "tools" you could make involving the IFS, but I'm just not there yet knowledge-wise.

As an experiment, I expanded upon this simple script to grab an entire library and generate a basic git repository :
https://github.com/barrettotte/IBMi-Lib-Repo

Thanks for reading.

Discussion (2)

Collapse
gajendertyagi profile image
Gajender Tyagi

Nice! I absolutely loved what you did there.
I tried similar thing just with nodejs. What i was doing instead of a repo.json, I fetched all the source-pf and members name from the library (response was list in json format). Then i traversed through the json to fetch source, that way i dont have to write myself what to fetch.
One issue which I faced was with conversion of data from EBCDIC to ASCII, which i was not able to do in ssh fetch command.
What you have used is an IBMi FTP command RETR so i guess it doesn't need any conversion paramters.

Collapse
barrettotte profile image
Barrett Otte Author

Thanks! One day I hope to get around to finishing rewriting my new version of this util.

Unfortunately, this also has that darn EBCDIC to ASCII conversion problem. My new util uses an SFTP module, which allows you to specify encoding. In my case I started using IBM037

Thanks for commenting, good to see some other IBMi folks on here!