Connecting Slack to RSS Feeds

I wanted to be able to curate some of my favorite RSS feeds into a single location. There are an endless number of RSS readers and ways to do it but I wanted to use Slack since it’s on every device I use. Using Slack would also allow me to get notifications wherever I was. I could also easily share these articles with anyone that I thought would want to read them.

I thought this was going to be a very tedious setup but I found a really simple application already in the Slack Application directory called RSS. Slack does a great job of providing a bunch of documentation for this app here.

Configuring adminer for Oracle Databases

If you are not familiar with adminer, you can read more about it here. In short, it is a PHP script that allows you to manage various databases via a single interface. We have been successfully using the adminer official docker image from docker hub for a number of different databases. We now have a need to add Oracle as one of those databases to use with adminer. This initially seemed like a simple task. The docker hub page states


“To add support for the other drivers you will need to install the following PHP extensions on top of this image:

oci8 (Oracle)”


I’ve configured Oracle Clients on a number of different machines. I’ve also had my fair share of adding PHP extensions to PHP installations in a past life. I then realized that the adminer image is based off of Alpine. This added some complexity. I was lucky enough that my coworker had started down the rabbit hole so I only needed to do some cleanup to get it over the line.

The Initial Dockerfile

I was able to get a trimmed down Dockerfile going that allowed me to get everything installed. I was also able to get oci8 to compile successfully. The below Dockerfile is where I got stuck:

FROM adminer
USER root

RUN apk --no-cache add libaio libnsl libc6-compat curl && \
  cd /tmp

ENV ORACLE_BASE /usr/local/oracle
ENV LD_LIBRARY_PATH /usr/local/oracle/instantclient_21_7:/lib64
ENV TNS_ADMIN /usr/local/oracle/instantclient_21_7/network/admin
ENV ORACLE_HOME /usr/local/oracle

# # Install Oracle Client and build OCI8 (Oracle Command Interface 8 - PHP extension)
RUN \
## Download and unarchive Instant Client v21
  mkdir /usr/local/oracle && \
  curl -o /tmp/sdk.zip https://download.oracle.com/otn_software/linux/instantclient/217000/instantclient-sdk-linux.x64-21.7.0.0.0dbru.zip && \
  curl -o /tmp/basic_lite.zip https://download.oracle.com/otn_software/linux/instantclient/217000/instantclient-basiclite-linux.x64-21.7.0.0.0dbru.zip && \
  unzip -d /usr/local/oracle /tmp/sdk.zip && \
  unzip -d /usr/local/oracle /tmp/basic_lite.zip

## Build OCI8 with PECL
RUN \
  C_INCLUDE_PATH=/usr/local/oracle/instantclient_21_7/sdk/include/ docker-php-ext-configure oci8 --with-oci8=instantclient,/usr/local/oracle/instantclient_21_7 && docker-php-ext-install oci8

#  Clean up
RUN \
  rm -rf /tmp/*.zip /var/cache/apk/* /tmp/pear/
USER adminer

My coworker was attempting to build this using Oracle 11 so I wanted to use the 21.x libraries to have the latest greatest. In order to cover this a little, you need to have the both the Basic Light Package and the SDK Package for oci8 to compile as per their requirements page. I downloaded the Linux x64 zip files for this build from Oracle’s download page. In addition to these, you’ll need the libraries referenced in the apk add command. In order for a successful compile, we need to make sure we have the SDK’s include directory passed to the docker-php-ext-configure command using the C_INCLUDE_PATH variable. The LD_LIBRARY_PATH statement also covers the Oracle library files as well as the additional libraries that will be installed in the /lib64 directory by apk add.

Dealing With The Last Error

I felt it was important to point out the exact error that I was getting up to this point so that everyone could see it and how to resolve it. With the above Dockerfile, I was able to successfully build my image.

% docker build -t adminer:local_test .
[+] Building 34.7s (10/10) FINISHED                                                                                                                                                   
 => [internal] load build definition from Dockerfile                                                                                                                             0.0s
 => => transferring dockerfile: 1.25kB                                                                                                                                           0.0s
 => [internal] load .dockerignore                                                                                                                                                0.0s
 => => transferring context: 2B                                                                                                                                                  0.0s
 => [internal] load metadata for docker.io/library/adminer:latest                                                                                                                1.1s
 => [auth] library/adminer:pull token for registry-1.docker.io                                                                                                                   0.0s
 => [1/5] FROM docker.io/library/adminer@sha256:ffef52f1cdc49e9877f8e73b62afe358396241fd3d018dd6e55bc865c2d58c87                                                                 5.3s
 => => resolve docker.io/library/adminer@sha256:ffef52f1cdc49e9877f8e73b62afe358396241fd3d018dd6e55bc865c2d58c87                                                                 0.0s
 => => sha256:0cdd6cb15c0d57bb696f4a1a47b41374e78a6a5f1cdd24c45d8b9655b52caa40 1.26kB / 1.26kB                                                                                   0.4s
 => => sha256:ffef52f1cdc49e9877f8e73b62afe358396241fd3d018dd6e55bc865c2d58c87 1.65kB / 1.65kB                                                                                   0.0s
 => => sha256:a600fdbc30cc6501babd12a4b22d75316d5ae384a68b9b4c588bfba33109bbc0 1.72MB / 1.72MB                                                                                   0.6s
 => => sha256:8967c2c42a6bd768b87f60aa77c0eb983d5c4607bdb668c13abf3f4ddb2bab47 3.45kB / 3.45kB                                                                                   0.0s
 => => sha256:75cd6c93316cb33fc0e5264a601cd3a30e3c869aeff375eab6b38679285f40cd 13.88kB / 13.88kB                                                                                 0.0s
 => => sha256:213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49 2.81MB / 2.81MB                                                                                   0.5s
 => => sha256:8a4c40d8aee7b41f415744811ec585376e2f7798b79ac68e04b7127fb847d566 268B / 268B                                                                                       0.6s
 => => extracting sha256:213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49                                                                                        0.2s
 => => sha256:77e67522f4fd69104b389667cdaa7cbac2c77f9de63edb5cfb9ab7e672232186 10.44MB / 10.44MB                                                                                 2.9s
 => => sha256:0d5c09a73378079aa8f6f1444eaadb178bc2440c63cd31a3fc8f0dec79d26919 15.07MB / 15.07MB                                                                                 3.9s
 => => sha256:d181492ef8e98df214123c11ce13997a4838a452a4e9f972b1f4398696d2b4d3 497B / 497B                                                                                       0.6s
 => => sha256:8bb8e21282b9e4f7015c3ab8bc3b58fa5dbc5920796f2ba25ec626afaf801753 2.44kB / 2.44kB                                                                                   0.9s
 => => extracting sha256:a600fdbc30cc6501babd12a4b22d75316d5ae384a68b9b4c588bfba33109bbc0                                                                                        0.2s
 => => extracting sha256:0cdd6cb15c0d57bb696f4a1a47b41374e78a6a5f1cdd24c45d8b9655b52caa40                                                                                        0.0s
 => => extracting sha256:8a4c40d8aee7b41f415744811ec585376e2f7798b79ac68e04b7127fb847d566                                                                                        0.0s
 => => sha256:fdd7ab990e10e983d38a54116fd52979cbba315f3a7776f55ed1d781179e3716 18.62kB / 18.62kB                                                                                 1.1s
 => => sha256:d59940a6c65c6196df1deb4e5985c2af04867b15aaee854127434268ed6c2acc 303B / 303B                                                                                       1.5s
 => => sha256:8af31b44c60c1e4d3276cfca694f285205514c7d5b1728493fc4e514ebad44ab 1.39kB / 1.39kB                                                                                   1.8s
 => => sha256:8f57d1664c2a86dcfe3bffa01915454678248fa87917c62ffaed9feb0e0ba12f 3.87MB / 3.87MB                                                                                   3.2s
 => => extracting sha256:77e67522f4fd69104b389667cdaa7cbac2c77f9de63edb5cfb9ab7e672232186                                                                                        0.1s
 => => sha256:eafa31c9e9994a03878bcd0284eefa41d846e7ceb0b9c9d8d613585897b8d7dc 1.49kB / 1.49kB                                                                                   3.1s
 => => extracting sha256:d181492ef8e98df214123c11ce13997a4838a452a4e9f972b1f4398696d2b4d3                                                                                        0.0s
 => => sha256:d99ebf4176fd240d317e44f69f61480c5a5d9988b1192edb2b2c05c2c86de1de 873.02kB / 873.02kB                                                                               3.3s
 => => sha256:dd175257ae8d3b87ebd540ce478cde35f0051be838c0decfcfdd8f6219375d89 494B / 494B                                                                                       3.4s
 => => extracting sha256:0d5c09a73378079aa8f6f1444eaadb178bc2440c63cd31a3fc8f0dec79d26919                                                                                        0.7s
 => => extracting sha256:8bb8e21282b9e4f7015c3ab8bc3b58fa5dbc5920796f2ba25ec626afaf801753                                                                                        0.0s
 => => extracting sha256:fdd7ab990e10e983d38a54116fd52979cbba315f3a7776f55ed1d781179e3716                                                                                        0.0s
 => => extracting sha256:d59940a6c65c6196df1deb4e5985c2af04867b15aaee854127434268ed6c2acc                                                                                        0.0s
 => => extracting sha256:8af31b44c60c1e4d3276cfca694f285205514c7d5b1728493fc4e514ebad44ab                                                                                        0.0s
 => => extracting sha256:8f57d1664c2a86dcfe3bffa01915454678248fa87917c62ffaed9feb0e0ba12f                                                                                        0.1s
 => => extracting sha256:eafa31c9e9994a03878bcd0284eefa41d846e7ceb0b9c9d8d613585897b8d7dc                                                                                        0.0s
 => => extracting sha256:d99ebf4176fd240d317e44f69f61480c5a5d9988b1192edb2b2c05c2c86de1de                                                                                        0.1s
 => => extracting sha256:dd175257ae8d3b87ebd540ce478cde35f0051be838c0decfcfdd8f6219375d89                                                                                        0.0s
 => [2/5] RUN apk --no-cache add libaio libnsl libc6-compat curl &&   ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 &&   cd /tmp                                                  1.2s
 => [3/5] RUN   mkdir /usr/local/oracle &&   curl -o /tmp/sdk.zip https://download.oracle.com/otn_software/linux/instantclient/217000/instantclient-sdk-linux.x64-21.7.0.0.0dbr  6.7s 
 => [4/5] RUN   C_INCLUDE_PATH=/usr/local/oracle/instantclient_21_7/sdk/include/ docker-php-ext-configure oci8 --with-oci8=instantclient,/usr/local/oracle/instantclient_21_7   19.3s 
 => [5/5] RUN   rm -rf /tmp/*.zip /var/cache/apk/* /tmp/pear/                                                                                                                    0.2s 
 => exporting to image                                                                                                                                                           0.8s 
 => => exporting layers                                                                                                                                                          0.8s 
 => => writing image sha256:31d6c9350ab7046253da7d3227b718c1715ca20a1b9974aff7f4898b8300f20f                                                                                     0.0s 
 => => naming to docker.io/library/adminer:local_test                         

The problem is that whenever the container starts and PHP attempts to load the oci8 extension, we get an error.

% docker run adminer:local_test       
[Wed Aug 31 15:13:10 2022] PHP Warning:  PHP Startup: Unable to load dynamic library 'oci8.so' (tried: /usr/local/lib/php/extensions/no-debug-non-zts-20190902/oci8.so (Error loading shared library libresolv.so.2: No such file or directory (needed by /usr/local/oracle/instantclient_21_7/libclntsh.so.21.1)), /usr/local/lib/php/extensions/no-debug-non-zts-20190902/oci8.so.so (Error loading shared library /usr/local/lib/php/extensions/no-debug-non-zts-20190902/oci8.so.so: No such file or directory)) in Unknown on line 0
[Wed Aug 31 15:13:10 2022] PHP 7.4.30 Development Server (https://[::]:8080) started

I searched everywhere for this and landed on an interesting Github repo OracleClient_Alpine. This repo uses Alpine Linux as the basis of the container in their Dockerfile and I noticed a number of symbolic links being created. The most notable link is the one for libresolv.so.2! Whenever I checked the output of ldd against the /usr/local/oracle/instantclient_21_7/libclntsh.so.21.1 file, I noticed this was the only thing that couldn’t be found. I adjusted my Dockerfile to now include that symbolic link:

FROM adminer
USER root

RUN apk --no-cache add libaio libnsl libc6-compat curl && \
  ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 && \
  cd /tmp

ENV ORACLE_BASE /usr/local/oracle
ENV LD_LIBRARY_PATH /usr/local/oracle/instantclient_21_7:/lib64
ENV TNS_ADMIN /usr/local/oracle/instantclient_21_7/network/admin
ENV ORACLE_HOME /usr/local/oracle

# # Install Oracle Client and build OCI8 (Oracle Command Interface 8 - PHP extension)
RUN \
## Download and unarchive Instant Client v11
  mkdir /usr/local/oracle && \
  curl -o /tmp/sdk.zip https://download.oracle.com/otn_software/linux/instantclient/217000/instantclient-sdk-linux.x64-21.7.0.0.0dbru.zip && \
  curl -o /tmp/basic_lite.zip https://download.oracle.com/otn_software/linux/instantclient/217000/instantclient-basiclite-linux.x64-21.7.0.0.0dbru.zip && \
  unzip -d /usr/local/oracle /tmp/sdk.zip && \
  unzip -d /usr/local/oracle /tmp/basic_lite.zip

## Build OCI8 with PECL
RUN \
  C_INCLUDE_PATH=/usr/local/oracle/instantclient_21_7/sdk/include/ docker-php-ext-configure oci8 --with-oci8=instantclient,/usr/local/oracle/instantclient_21_7 && docker-php-ext-install oci8

#  Clean up
RUN \
  rm -rf /tmp/*.zip /var/cache/apk/* /tmp/pear/
USER adminer

I then run another build and start the container

% docker build -t adminer:local_test .
[+] Building 0.6s (10/10) FINISHED                                                                                                                                                    
 => [internal] load build definition from Dockerfile                                                                                                                             0.0s
 => => transferring dockerfile: 1.25kB                                                                                                                                           0.0s
 => [internal] load .dockerignore                                                                                                                                                0.0s
 => => transferring context: 2B                                                                                                                                                  0.0s
 => [internal] load metadata for docker.io/library/adminer:latest                                                                                                                0.5s
 => [auth] library/adminer:pull token for registry-1.docker.io                                                                                                                   0.0s
 => [1/5] FROM docker.io/library/adminer@sha256:ffef52f1cdc49e9877f8e73b62afe358396241fd3d018dd6e55bc865c2d58c87                                                                 0.0s
 => CACHED [2/5] RUN apk --no-cache add libaio libnsl libc6-compat curl &&   ln -s /lib/libc.so.6 /usr/lib/libresolv.so.2 &&   cd /tmp                                           0.0s
 => CACHED [3/5] RUN   mkdir /usr/local/oracle &&   curl -o /tmp/sdk.zip https://download.oracle.com/otn_software/linux/instantclient/217000/instantclient-sdk-linux.x64-21.7.0  0.0s
 => CACHED [4/5] RUN   C_INCLUDE_PATH=/usr/local/oracle/instantclient_21_7/sdk/include/ docker-php-ext-configure oci8 --with-oci8=instantclient,/usr/local/oracle/instantclient  0.0s
 => CACHED [5/5] RUN   rm -rf /tmp/*.zip /var/cache/apk/* /tmp/pear/                                                                                                             0.0s
 => exporting to image                                                                                                                                                           0.0s
 => => exporting layers                                                                                                                                                          0.0s
 => => writing image sha256:31d6c9350ab7046253da7d3227b718c1715ca20a1b9974aff7f4898b8300f20f                                                                                     0.0s
 => => naming to docker.io/library/adminer:local_test                                                                                                                            0.0s
salgatt@MacBook-Pro-Instart adminer % docker run adminer:local_test       
[Wed Aug 31 15:22:25 2022] PHP 7.4.30 Development Server (https://[::]:8080) started

The error is resolved but now let’s confirm it works!

Access an Oracle Database From adminer

After a little trial and error, I found that adminer is expecting to connect to an Oracle database via TNS Name. If you looked closely at my Dockerfile, you’ll see that I set the environment variable TNS_ADMIN to the path of /usr/local/oracle/instantclient_21_7/network/admin. This means that I’ll need to create a tnsnames.ora file to be used for my connection to my Oracle database. For those that aren’t familiar with tnsnames.ora and its syntax, you should read up on them in Oracle’s documentation. For those that just don’t care and want to connect to a database, you can use my sample below:

oracle_rds = (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=oracle-rds.amazon.com)(PORT=1521))(CONNECT_DATA=(SID=ORCL)))

This is a sample entry that I’m using to connect to an AWS Oracle RDS instance over the non-TLS port. I created this tnsnames.ora file in a directory on my machine and then I re-ran the docker image mounting that directory to /usr/local/oracle/instantclient_21_7/network/admin and expose port 8080 to my local machine like below:

% docker run -v /Users/salgatt/tmp/adminer/admin:/usr/local/oracle/instantclient_21_7/network/admin -p 8080:8080 adminer:local_test
[Wed Aug 31 15:31:42 2022] PHP 7.4.30 Development Server (https://[::]:8080) started

From there, I can open a browser and access adminer.

At the adminer login page, the server should be the TNS Name of your Oracle connection. Given the example above, the server name of oracle_rds should be used. Supply the proper credentials and a database on the server and click Login.

From there, you’ll probably want to access the USERS database (assuming this is where your table space is configured to host user databases). An example of my USERS database is below

It looks like everything is working!

As a fun side note, it also looks like you put the full connection string as the database name too.

Deploying a Sample PostgreSQL Database

Now that I’m beginning to use Github to manage my Kubernetes cluster as shown in my previous article Making The Leap Into Devops, it’s time to start bringing over some of my sample systems for testing. This article shows how to make use of Bitnami’s PostgreSQL helm chart to deploy a sample PostgreSQL database in Kubernetes.

I make use of the PostgreSQL DVDRental Sample Database that is available here. In order to incorporate this into my deployment, I have created a public Github called sample_dbs to host my sample databases. I pull this down into the deployment so that I have a sample database.

The Helm Chart

Let’s jump right in with the values file that I’m using to deploy the helm chart.

# Config values : https://github.com/bitnami/charts/tree/master/bitnami/postgresql
global:
  postgresql:
    auth:
      postgresPassword: "SuperSecretPGAdminPa55word!"
      username: "dvdrental_admin"
      password: "notAsSecretU5erPa55word"
      database: "dvdrental"
primary:
  persistence:
    enabled: false
  initdb:
    user: "postgres"
    password: "SuperSecretPGAdminPa55word!"
    scripts:
      data-load.sh: |-
        #!/bin/bash
        echo "Loading Sample Data..."
        curl https://raw.githubusercontent.com/salgattcy/sample_dbs/master/sampleData/dvdrental.tar -o /tmp/dvdrental.tar
        PGPASSWORD='SuperSecretPGAdminPa55word!' pg_restore /tmp/dvdrental.tar -d dvdrental -U postgres
        rm /tmp/dvdrental.tar
        echo "Done!!!"

You can view additional configuration settings in Bitnami’s repo. Some quick things to note are the global.postgresql.auth paths:

  • auth.postgresPassword : this sets the password for the admin account, postgres
  • auth.username : this is adding a user to the database called dvdrental_admin
  • auth.password : this is setting the password for the dvdrental_admin account
  • auth.database : this is creating a new database called dvdrental

Next, we’re disabling persistence because I don’t care about keeping the data long term as it’ll reset if the pod restarts. The primary.initdb section tells postgres to come up with the username and password provided along with a custom script to execute. The custom script pulls down the dvdrental.tar file and uses pg_restore to add the data to the database.

Updating the Workflow Action

With this new YAML in place, I need to add this deployment to my github actions file. I now add the following to my ~/.github/workflows/workflow.yml file:

    - name: Deploy Postgres Sample DB
      run: helm upgrade -i psql-test postgresql --namespace my_dbs -f $GITHUB_WORKSPACE/helm_charts/psql.yaml --repo https://charts.bitnami.com/bitnami

    - name: Verify Postgres Sample DB Deployment
      run: kubectl -n my_dbs rollout status statefulset/psql-test-postgresql

The Deploy Postgres Sample DB entry calls helm to deploy this into the my_dbs namespace. The Verify Postgres Sample DB Deployment entry checks the status of the rollout of the helm chart. After committing this, we make sure the workflow runs successfully and everything looks good here.

Validating Our Changes

Now, I can confirm that the database is successfully deployed by checking kubectl

% kubectl -n my_dbs get pod                                        
NAME                          READY   STATUS    RESTARTS   AGE
psql-test-postgresql-0        1/1     Running   0          3m38s

This looks good so now let’s make sure we have can successfully connect to the database and view data.

# psql -h pg-db-host -U dvdrental_admin -d dvdrental
Password for user dvdrental_admin: 
psql (14.5 (Debian 14.5-1.pgdg110+1))
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256, bits: 128, compression: off)
Type "help" for help.

dvdrental=> select * from store;
 store_id | manager_staff_id | address_id |     last_update     
----------+------------------+------------+---------------------
        1 |                1 |          1 | 2006-02-15 09:57:12
        2 |                2 |          2 | 2006-02-15 09:57:12
(2 rows)

dvdrental=> \dt
                List of relations
 Schema |     Name      | Type  |      Owner      
--------+---------------+-------+-----------------
 public | actor         | table | dvdrental_admin
 public | address       | table | dvdrental_admin
 public | category      | table | dvdrental_admin
 public | city          | table | dvdrental_admin
 public | country       | table | dvdrental_admin
 public | customer      | table | dvdrental_admin
 public | film          | table | dvdrental_admin
 public | film_actor    | table | dvdrental_admin
 public | film_category | table | dvdrental_admin
 public | inventory     | table | dvdrental_admin
 public | language      | table | dvdrental_admin
 public | payment       | table | dvdrental_admin
 public | rental        | table | dvdrental_admin
 public | staff         | table | dvdrental_admin
 public | store         | table | dvdrental_admin
(15 rows)

Everything looks good!

Making the Leap into DevOps

It is time for me to quit managing all of my Kubernetes configurations on a random number of servers and Git repos. Sadly, my usage of the Git repos has ONLY been used to perform backups of my code and not used as a way to manage configuration changes.

I have been running in DigitalOcean’s infrastructure since 2017, Member since 1/14/2017. I’ve steadily moved from just Droplets into their more managed infrastructure to include Kubernetes and App Platform. The App Platform is what sits in front of this website (something I’ll need to further document some day). I’m hoping to get some time to also tinker with their Functions but time is not always on my side.

As the title of this blog points out, I try to document my My Battles With Technology in order to remind myself how I have things setup but also to clarify where I diverged from examples I found on the Internet. Enough babbling, let’s get to it!

Failing at My First Attempt to Follow the Guide

As I stated, my next leap into the DevOps world was to try and integrate Github and my Kubernetes configuration. I thought this would be an awesome idea. In order to get started, I just did a really simple Google search for digitalocean kubernetes example which eventually lead me to a great tutorial authored by DigitalOcean, Enable Push-to-Deploy on DigitalOcean Kubernetes Using GitHub Actions. This article was a pretty mindless configuration as they covered all of the bases to get things going or so I thought. I followed the directions and pushed my commit and got an error during the build process:

It took me a second to figure it out but I realized the problem. During the creation of my REGISTRY_NAME secret, I did exactly what the instructions said to do

Create a DigitalOcean Container Registry and add the registry name as a secret to your GitHub repository. Name the secret REGISTRY_NAME.

DigitalOcean

My registry name is testing-registry. It turns out that you actually need to provide the registry endpoint! I updated my REGISTRY_NAME secret to be the endpoint instead, registry.digitalocean.com/testing-registry, and re-ran the failed jobs.

The build was successful! Happy days. I’m hoping to now start moving my infrastructure over so we’ll see what happens next!