DevOps Made Easy! with Oracle Autonomous Database using PL/SQL and Git

DevOps Made Easy! with Oracle Autonomous Database using PL/SQL and Git

DevOps Made Easy Part 1

Introduction

The Oracle Autonomous Database (ADB) includes the DBMS_CLOUD_REPO package, an extremely powerful package that provides easy access to files in Cloud Code (Git) Repositories, including: GitHub, AWS CodeCommit, and Azure Repos. This package serves as a single interface for accessing Multicloud Code repositories and allows you to upload SQL files to Git repositories or install SQL scripts directly from the repository. Additionally, this package allows you to use a Code Repository to manage code versions for SQL scripts and to install or update application code from Git repositories. For example, you can perform actions like listing and managing repositories, committing to and managing code within the repository, exporting database schemas and objects directly to the repository, and checking out and executing SQL statements from files committed to the repository.

Prerequisites

Before we start, please make sure you have completed the following prerequisites listed below. This will enable you to move through this guide seamlessly.

  • OCI Tenancy ✅

  • Provisioning an Autonomous Database ✅

  • Accessing an APEX Instance ✅

  • APEX Application(s) ✅

  • GitHub account with a Personal Access Token (PAT) ✅
    Read more here -> Managing your personal access tokens

💡
Ensure you have access to the Repository you're working with, and read-write permissions for both Administration and Content!

Save your Personal Access Token (PAT) in a Cloud Service Credential

Let's set up the DBMS_CLOUD_REPO package. To do this, sign in to your OCI tenancy, click on Oracle Database in the navigation menu. Then, click on Autonomous Database and choose the ADB you've already set up. When you're on the details page, simply click Database Actions and then SQL.

Now that the SQL-Developer Web is open in a new tab, we can create a credential for the GitHub Personal Access Token (PAT) using the DBMS_CLOUD.CREATE_CREDENTIAL procedure. Here's the command you'll need:

begin
  dbms_cloud.create_credential (
    credential_name => 'GITHUB_CRED',
    username        => 'therwix',
    password        => 'github_pat_...'
  ) ;
end;
/

FYI: You can display the credential information using the USER_CREDENTIALS view ✔

select credential_name,
       username,
       enabled
  from user_credentials
 order by credential_name;

To check if the access works, you can run the following code:

SELECT name, owner, description, created, last_modified 
  FROM dbms_cloud_repo.list_repositories(dbms_cloud_repo.init_github_repo(
                                              credential_name => 'GITHUB_CRED', -- Name of the previously created credential
                                              repo_name       => 'therwix',     -- Name of the GitHub Repository
                                              owner           => 'therwix'      -- Name of the GitHub Repository Owner
                                      ));

If everything works well with the credential setup, you should see a list of repositories that your Personal Access Token (PAT) can access.

Great! We've completed the initial step and are now ready to interact with the repository 😎

Get started with Repository interaction

With all the necessary preparations and configurations now successfully completed, we're well-equipped and ready to start actively engaging with the GitHub repository. This means we can now perform various operations, like managing repositories, managing files, or exporting database schemas, all while using our Personal Access Token (PAT) for secure and efficient access. So, let's dive in 😉

Code repository administration made easy

In this section, we will explore the DBMS_CLOUD_REPO subprograms, which are essential for effectively managing code repositories.

Managing Repositories

If you want to create a new Repository, it's super easy with the dbms_cloud_repo.create_repository procedure. Just run the script below, and you'll have your new repository:

declare
     repoHandle     clob;
     repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
     repoName       varchar2(50 CHAR) := 'TEST_REPO';    -- Name of the new GitHub Repository
     repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    dbms_cloud_repo.create_repository(
        repo        => repoHandle,
        description => 'Repo created with DBMS_CLOUD_REPO',
        private     => TRUE
    );  
end;
/

To see all the repositories, simply run this SQL command:

SELECT * FROM DBMS_CLOUD_REPO.LIST_REPOSITORIES (dbms_cloud_repo.init_github_repo(
                                                  credential_name => 'GITHUB_CRED', 
                                                  repo_name       => 'therwix',     
                                                  owner           => 'therwix'));

If you want to delete a Repository, you can use the dbms_cloud_repo.delete_repository procedure. Run the script below, and the repository will be removed:

declare
     repoHandle     clob;
     repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
     repoName       varchar2(50 CHAR) := 'TEST_REPO';    -- Name of the new GitHub Repository
     repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    dbms_cloud_repo.delete_repository(
        repo        => repoHandle
    );  
end;
/

Managing Branches

With the dbms_cloud_repo.create_branch procedure, you can create a new branch in your GitHub Repository identified by the repo handle argument. Just run the script below, and you'll have a new branch in no time:

declare
     repoHandle     clob;
     repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
     repoName       varchar2(50 CHAR) := 'TM-APEX';      -- Name of the GitHub Repository
     repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    dbms_cloud_repo.create_branch(
        repo                => repoHandle,
        branch_name         => 'test_branch',
        parent_branch_name  => 'main'
    );  
end;
/

To display all branches in a repository, just run this SQL command:

SELECT * FROM DBMS_CLOUD_REPO.LIST_BRANCHES (dbms_cloud_repo.init_github_repo(
                                              credential_name => 'GITHUB_CRED', 
                                              repo_name       => 'TM-APEX',     
                                              owner           => 'therwix'));

If you'd like to delete a branch, just use the dbms_cloud_repo.delete_branch procedure. Simply run the following script.

declare
     repoHandle     clob;
     repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
     repoName       varchar2(50 CHAR) := 'TM-APEX';      -- Name of the GitHub Repository
     repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    dbms_cloud_repo.delete_branch(
        repo                => repoHandle,
        branch_name         => 'test_branch'
    );  
end;
/

Moving content to the Code Repository

Now let's dive into the exciting part and fill up our Code Repository! By doing this, we'll have scripts to recreate the schema structure and its objects in another environment, making migration and replication easier.

Export Schema DDL-Scripts

In this example, we are working with the TMAPEX schema, which has the Oracle APEX Starter-App Sample-Approvals pre-installed. The Starter-App Sample-Approvals includes a collection of database objects, such as tables, views, and packages, that serve as a foundation for demonstrating various Oracle APEX features and functionalities. Our primary objective in this scenario is to export these database objects in the form of Data Definition Language (DDL) scripts. These DDL scripts will enable us to recreate the schema structure and its associated objects in another environment, if needed, thus ensuring seamless migration and replication of the TMAPEX schema.

Feel free to use your own existing Schema or simply use this SQL command to create a new one:

create user "TMAPEX" identified by "Password1234!";
alter user "TMAPEX" quota unlimited on data; 
grant create session to "TMAPEX";

Log in to the APEX Administration Services and assign the Schema to the workspace you're using. Then log out and sign in to your workspace. From there, you're ready to create a new app! Just follow these steps:

  1. In the App Builder, click on Create and choose Starter App

  2. Pick Sample-Approvals and click Install

  3. In Advanced Settings, select the schema you created earlier

  4. Finally, click Install Application

Now we have created several database objects as well as an APEX application that we can move to our repository in the next step. To do this, run the following SQL-Script.

declare
    repoHandle     clob;
    repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
    repoName       varchar2(50 CHAR) := 'TM-APEX';      -- Name of the GitHub Repository
    repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    for rec in (
        select *
          from dba_objects
         where object_type in ('TABLE', 'VIEW', 'PACKAGE', 'PACKAGE BODY')
           and owner = 'TMAPEX'
    ) 
    loop
        dbms_cloud_repo.export_object(
            repo => repoHandle,
            file_path => case rec.object_type
                           when 'TABLE' then 'tables/' || lower(rec.object_name) || '.sql'
                           when 'VIEW' then 'views/' || lower(rec.object_name) || '.sql'
                           when 'PACKAGE' then 'packages/' || lower(rec.object_name) || '.pks'
                           when 'PACKAGE BODY' then 'packages/' || lower(rec.object_name) || '.pkb'
                         end,
            object_type => case rec.object_type
                             when 'TABLE' then 'TABLE'
                             when 'VIEW' then 'VIEW'
                             when 'PACKAGE' then 'PACKAGE_SPEC'
                             when 'PACKAGE BODY' then 'PACKAGE_BODY'
                           end,
            object_name => rec.object_name,
            object_schema => 'TMAPEX',
            branch_name => 'main',
            commit_details => json_object('message' value 'DBMS_CLOUD_REPO commit',
                                          'author'  value 'therwix',
                                          'email'   value 't.herwix@gmx.de'
                                         ),
            append => false
        );
    end loop;
end;
/

Once you're done with that, go ahead and take a look at your repo to see if the right DDL files have been created. If everything went well, it should look something like this. 😊

To view the DDL script, simply click on tables and then on eba_demo_appr_emp.sql, for example.

💡
If you prefer an installation file with all the DDLs included, you can use the dbms_cloud_repo.export_schema procedure. It's super handy 😎
declare
    repoHandle     clob;
    repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
    repoName       varchar2(50 CHAR) := 'TM-APEX';      -- Name of the GitHub Repository
    repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    dbms_cloud_repo.export_schema(
        repo          => repoHandle,
        schema_name   => 'TMAPEX',
        file_path     => 'myschema_ddl.sql'
    );

end;
/

Done, all the database objects are now created in the repository.

Now, let's move on to exporting our APEX application. 😉

Exporting an APEX Application

You can easily export APEX applications to a GitHub repository too. All you need to do is call the APEX_EXPORT package and pass the application ID to the GET_APPLICATION function. But, there's a tiny thing to remember: the output of GET_APPLICATION, which is a CLOB, needs to be converted to a BLOB to work with the DBMS_CLOUD.PUT_FILE procedure. However, APEX_UTIL has a helpful function to do this.

Go ahead and run the SQL-Script below to see how it's done. 😉

declare
    repoHandle     clob;
    repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
    repoName       varchar2(50 CHAR) := 'TM-APEX';      -- Name of the GitHub Repository
    repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner

    l_file         apex_t_export_files;
    l_app_id       number := 108;                       -- App-ID of the exported application
    l_name         varchar2(255 CHAR);                  -- Name of the exported application
    l_app_clob     clob;
    l_app_blob     blob;
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    l_file := apex_export.get_application(p_application_id => l_app_id);
    l_name := l_file(1).name;

    l_app_clob := l_file(1).contents;
    l_app_blob := apex_util.clob_to_blob(l_app_clob);

    dbms_cloud_repo.put_file(
        repo   => repoHandle,
        file_path => 'apex/' || l_name, 
        contents => l_app_blob,
        branch_name => 'main',
        commit_details => json_object('message' value 'DBMS_CLOUD_REPO commit',
                                      'author'  value 'therwix',
                                      'email'   value 't.herwix@gmx.de'
                                     )
   );

end;
/

To verify if the APEX app has been exported, check your repository to see if an SQL file has been created. If everything went well, it should look something like this.

We've successfully exported our Database Objects and the APEX application to our Code Repository. Great! Next up, we'll explore how to install everything from the repository.

Perform SQL Operations from Code Repositories

With the previously exported Data DDLs files, we are now ready to proceed with installing them into our secondary environment. This process will ensure that our database objects and the APEX application are properly set up and configured in the new location, allowing us to maintain consistency across different environments.

Install Database Objects from a Script

Before we get started, we need a second Autonomous Database. If no second ADB exists, sign in to your OCI tenancy and provision a new one. When the new database is up, make sure to create a new schema with the same name as the previous schema (e.g. TMAPEX), where we'll install all the database objects.

To do this, launch the SQL Developer from the second ADB and simply execute the following SQL script:

create user "TMAPEX" identified by "Password1234!";
alter user "TMAPEX" quota unlimited on data; 
grant create session to "TMAPEX";

In fact, since you are on a different database, you'll need to recreate the credentials to make sure you can access the Code Repository.

begin
  dbms_cloud.create_credential (
    credential_name => 'GITHUB_CRED',
    username        => 'therwix',
    password        => 'github_pat_...'
  ) ;
end;
/

Next, we created a new installation file named install_db.sql in our repository that runs every script in the correct order.

declare
    repoHandle     clob;
    repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
    repoName       varchar2(50 CHAR) := 'TM-APEX';      -- Name of the GitHub Repository
    repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    dbms_cloud_repo.put_file(
        repo   => repoHandle,
        file_path => 'install_db.sql', 
        contents => utl_raw.cast_to_raw('
-- install_db.sql
@@tables/eba_demo_appr_approvers.sql
@@tables/eba_demo_appr_dept.sql
@@tables/eba_demo_appr_emp.sql
@@tables/eba_demo_appr_laptop_requests.sql
@@tables/eba_demo_appr_sal_history.sql
@@packages/eba_demo_appr.pks
@@packages/eba_demo_appr_data.pks
@@packages/eba_demo_appr.pkb
@@packages/eba_demo_appr_data.pkb'),
        branch_name => 'main',
        commit_details => json_object('message' value 'DBMS_CLOUD_REPO commit',
                                      'author'  value 'therwix',
                                      'email'   value 't.herwix@gmx.de'
                                     )
   );

end;
/

Once this is completed, you can proceed to run the install script for the entire schema. This will install everything we need in one process.

declare
    repoHandle     clob;
    repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
    repoName       varchar2(50 CHAR) := 'TM-APEX';      -- Name of the GitHub Repository
    repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                       credential_name => repoCredential,
                       repo_name       => repoName,
                       owner           => repoOwner
                  );

    dbms_cloud_repo.install_file(
        repo          => repoHandle,
        file_path     => 'install_db.sql',
        branch_name   => 'main',
        stop_on_error => true 
    );

end;
/

If everything works well, you will see the Database Objects in your schema.

Alright, now let's move on to importing the APEX application.

Deploy an APEX Application from a Script

Last but not least, we want to deploy the APEX application in our new environment.

Make sure you've created a Workspace and assigned the correct schema to it.

If everything is set up correctly, execute the following SQL script.

declare
    repoHandle     clob;
    repoCredential varchar2(50 CHAR) := 'GITHUB_CRED';  -- Name of the previously created credential
    repoName       varchar2(50 CHAR) := 'TM-APEX';      -- Name of the GitHub Repository
    repoOwner      varchar2(50 CHAR) := 'therwix';      -- Name of the GitHub Repository Owner
    repoApp        clob;
    l_file         apex_t_export_files;
    l_app_id       number := 108;
begin
    repoHandle := dbms_cloud_repo.init_github_repo(
                      credential_name => repoCredential,
                      repo_name       => repoName,
                      owner           => repoOwner
                  );

    repoApp := dbms_cloud_repo.get_file(
                   repo           => repoHandle,
                   file_path      =>'apex/f'||l_app_id||'.sql',
                   branch_name    => 'main'
               );

    l_file := apex_t_export_files (
                  apex_t_export_file (
                      name     => 'apex/f'||l_app_id||'.sql',
                      contents => repoApp));

    apex_util.set_workspace('DEMO');

    apex_application_install.set_application_id (
        p_application_id => l_app_id); 

    apex_application_install.install(
        p_source => l_file,
        p_overwrite_existing => true); 

end;
/

If the script runs without any issues, the APEX App will be installed. Verify this by logging into your Workspace and clicking on the App Builder.

So that´s it for now. We have completed all tasks, successfully exporting our source environment to the Code Repository and seamlessly importing it into another target environment ✅

Conclusion

In conclusion, the DBMS_CLOUD_REPO package simplifies CI/CD workflows for APEX applications on Autonomous Database by providing a single interface for managing Code Repositories. This powerful package allows you to perform various repository operations, such as managing branches and repositories, exporting database schemas, and executing SQL scripts directly from the repository. By leveraging this package, you can streamline your DevOps processes and enhance the efficiency of your APEX applications.

References