Deployment

TIBET Deployment

wins

  • Manifest-driven application packaging requires no additional tooling.
  • Built-in command line deployments via tibet build, tibet release and tibet deploy.
  • Pre-configured support for running shipit-based deployments.
  • Fully extensible via tibet deploy command plugins or tibet make.
  • Integrated test, build, deploy support directly from TIBET Lama™.

concepts

Building

TIBET projects support building (packaging and minification) out of the box using TIBET-specific tooling optimized for creating deployable TIBET applications that conform to your requirements.

Unlike systems which rely on source code analysis TIBET leverages the same application package files it uses to define your application for loading, testing, and other tasks. By using a common package description format TIBET lets you define exactly how you want your application be configured in a single, consistent fashion regardless of the task at hand.

When you launch a TIBET application the TIBET Loader will automatically attempt to load a "built" project configuration. In other words, launching via a URL such as http://127.0.0.1:1407 will try to find and load a built package. When you first create a new project this package won't exist until the first time you execute tibet build for that project.

Once you've built your project at least once TIBET will load the last built package for your project unless you provide it with boot parameters to target a more dynamic development configuration. This is, in fact, why development with TIBET usually happens from a URL of the form: http://127.0.0.1:1407#?boot.profile=development.

Below is the output of tibet build after working through the TIBET Quickstart and TIBET Essentials guides. We specify --minify here for the build so that it can load properly using a 'production target' configuration (which tries to load .min files by default):

$ tibet build --minify
Delegating to 'tibet make build'
building app...
checking for lint...

checked 13 of 15 total files
(2 filtered, 0 unchanged)
0 errors, 0 warnings. Clean!
verifying packages...
verifying ~app_cfg/main.xml@base
scanning for unresolved/unlisted files...
no unresolved files found in project.
processing resources...
generating resources...
# Loading TIBET platform at 2021-04-20T02:02:07.640Z
# TIBET reflection suite loaded and active in 7142ms
filtering 2 computed and 9 specified resources...
building 11 concrete resources...
writing package resource entries...
<script src="~app_build/app_tags-APP.helloworld.app-APP.helloworld.app.xhtml.js"/> (added)
<script src="~app_build/app_tags-APP.helloworld.app-APP.helloworld.app.css.js"/> (added)
<script src="~app_build/app_styles-app.css.js"/> (added)
<script src="~app_build/app_styles-index.css.js"/> (added)
<resource href="~app_media/apple-touch-icon-precomposed.png" no-inline="true"/> (added)
<resource href="~app_media/apple-touch-icon.png" no-inline="true"/> (added)
<resource href="~app_media/favicon.ico" no-inline="true"/> (added)
<script src="~app_build/app_dat-keyrings.xml.js"/> (added)
<script src="~app_build/app_dat-vcards.xml.js"/> (added)
New configuration entries created. Review/Rebuild as needed.
rolling up assets...
writing 10447 chars to ~app_build/app_base.js
writing 5172 chars to ~app_build/app_base.min.js
linking build targets...
skipping link for missing file '~app_build/app_base.min.js.gz'
skipping link for missing file '~app_build/app_base.min.js.br'
build assets linked successfully.
building project documentation...
processing helloworld-sample.1
Task complete: 12640ms.

As you can see, tibet build invokes the TIBET Loader to load your application in a headless context, reflect on it for resource utilization information, and then use that information to not only bundle your source code but also any styles, images, and other resources it can detect.

Experimental features of this load/reflect process include running your tests while maintaining a list of all functions invoked, allowing future `tibet build` processing to optionally strip your application package to the minimum size possible...only the code you actually invoke.

The output of the build process is a set of files specific to your application code. This is the "app" portion of TIBET's "app vs. lib" separation. There's nothing special to do to get TIBET to factor out lib from application code.

Building A Non-Default Target

Sometimes you need to build a 'package@config' combination that's not the default (main@base). In that case you can use --profile= to provide both or you can use --package and/or --config to define what you need.

In the example below we tell tibet build we want to use the baseui target so we can pull the xctrls namespace files into our built package:

$ tibet build --minify --config=baseui
Delegating to 'tibet make build'
building app...
checking for lint...

checked 0 of 24 total files
(8 filtered, 16 unchanged)
0 errors, 0 warnings. Clean!
verifying packages...
verifying ~app_cfg/main.xml@baseui
scanning for unresolved/unlisted files...
no unresolved files found in project.
processing resources...
generating resources...
# Loading TIBET platform at 2021-04-20T02:04:13.165Z
# TIBET reflection suite loaded and active in 6923ms
filtering 2 computed and 9 specified resources...
building 11 concrete resources...
writing package resource entries...
rolling up assets...
writing 10447 chars to ~app_build/app_baseui.js
writing 5172 chars to ~app_build/app_baseui.min.js
linking build targets...
skipping link for missing file '~app_build/app_baseui.min.js.gz'
skipping link for missing file '~app_build/app_baseui.min.js.br'
build assets linked successfully.
building project documentation...
Task complete: 11327ms.

Releasing

TIBET projects also support releasing (project version stamping and git branch tagging) out of the box using two commands: tibet version and tibet release.

These two commands support a standard pattern for managing project versioning and releases. The tibet version command updates internal project files (package.json and others) with the latest versioning information using the semver versioning standard. The tibet release command manages git branches to perform a release.

Let's see how this works in practice by trying with a new project.

Versioning

When you clone a TIBET project, it automatically sets the project version at 0.1.0.

tibet clone testproject
cd testproject
tibet init

`

The package.json file now has a version of 0.1.0. Before we bump the version, we need to make sure our project has been initialized with git and that at least one commit has been done. This is because the tibet version command uses git metadata.

git init
git add .
git commit -m "Initial commit"

Now that we have an initial commit, we can use the tibet version command.

tibet version --patch --suffix final
Set version 0.1.1 (+g2ad61f88d4.1618882437935) ? Type 'yes' to continue:

This command will bump the semver 'patch' level and will append no suffix to the version ('final'). The command can also bump the 'major' and 'minor levels via the --major and --minor flags and can append different suffixes like 'beta' and 'rc' by using different values for the --suffix flag. See the man page for tibet version for more information on all of the flags and values.

If you type 'yes' to the above, TIBET stamps the new version number in 4 different files in the system. These are used by various parts of the TIBET machinery to keep your app up-to-date. It is a good idea to commit these as well:

git add package.json public/TIBET-INF/cfg/testproject.xml public/TIBET-INF/cfg/main.xml public/src/templates/version.js
git commit -m "Bumped patch version for release"

Building a package for release

TIBET supports minification, gzipping and brotliing your application using the tibet build command. While you can specify individual flags --minify, --zip and --brotli, tibet build also supports a --release convenience flag:

tibet build --release

Release branch management

TIBET's authors have found that developing on one branch and releasing on another is a great way to keep things organized. The tibet release command codifies this into a convenient command.

tibet release

While specifics about how this command works can be found in its man page, here's a quick summary:

  1. Verifies that the current branch is the 'source' branch. The source branch defaults to develop - override by setting the cli.release.source config property in your project. See TIBET Configuration.

  2. Verifies that the source branch is not dirty (can override with the --dirty flag).

  3. Verifies that the source branch is up-to-date with its remote (can override with the --local flag). The remote defaults to origin - override by setting the cli.release.remote config property in your project.

  4. Runs tibet lint --clean to lint the codebase. This uses tools such as eslint and stylelint to lint your application's codebase.

  5. Tags the source branch using {tag}-{branchname}. Pushes this to the {remote}/{branchname}.

  6. Checks out the 'target' branch. The target branch defaults to master - override by setting the cli.release.target config property in your project.

  7. Verifies that the target branch is up-to-date with its remote.

  8. Merges the source branch into the target branch.

  9. Commits the changes to the target branch.

  10. Tags the commit with the release tag (the value TIBET computed using the tibet version command above).

  11. Pushes the commit and related tag to the remote.

  12. Checks out the source branch.

Deploying

In TIBET the process of deployment is triggered by invoking the tibet deploy command from either the CLI or the TIBET Lama.

TIBET's deployment process does not do a build first, it simply invokes whatever deployment helper you target. TIBET supports shipit out of the box. It also supports deployment for Dockerized container systems like AWS Elastic Beanstalk and Azure WebApps as well as deployment of versioned Electron applications.

To trigger deployment from the command line use something like:

# tibet deploy {helper} [options]
$ tibet deploy shipit '{"environment":"development"}'

In the prior command line we're asking tibet deploy to leverage the shipit deployment helper and to provide it with the development environment target. The result in that specific case is invocation of the following command line to activate shipit (provided it's installed):

shipit deploy development

You can also leverage tibet make by invoking tibet deploy make assuming you've updated your project's _deploy.js make target. See the discussion on deploying via tibet make for more information.

For specific deployment commands see the cookbook entries which follow.

cookbook

Installing Shipit For Your Project

TIBET projects don't include shipit functionality by default. You can fix this quickly by adding shipit-cli and shipit-deploy to your project via npm.

$ npm install --save-dev shipit-cli
$ npm install --save-dev shipit-deploy

Once you have installed the shipit-cli and shipit-deploy modules you need to add a shipitfile.js file to your project.

Here's a sample shipitfile.js for "deploying" to local machine's /tmp directory:

module.exports = function(shipit) {
    require('shipit-deploy')(shipit);

    shipit.initConfig({
        default: {
            workspace: '/tmp/tibet',
            deployTo: '/tmp/deployed-tibet',
            repositoryUrl: 'https://github.com/TechnicalPursuit/TIBET.git',
            ignores: ['.git', 'node_modules'],
            rsync: ['--del'],
            keepReleases: 3,
            key: '~/.ssh/id_rsa',
            shallowClone: true
        }
        development: {
            servers: 'ss@localhost'
        }
    });
};

You'll want to review the shipit documentation for details on how to configure your specific environments and targets for shipit.

Deploying via shipit

TIBET includes built-in support for invoking targets in a project's shipitfile.js file provided such a file exists (and you have installed shipit for your project).

All you need to do is invoke tibet deploy and provide it with shipit as the deployment helper along with any parameters you want to pass to shipit itself.

If you've configured the sample file we provided in the previous "installing shipit" cookbook entry you can try the following:

$ tibet deploy shipit '{"environment":"development"}'

As you create specific targets (environments) for shipit you can deploy to them quickly and easily from the tibet command line.

Advanced shipit deployment

Here is an example of deployment using shipit in an advanced way that leverages shipit events and TIBET's build, version, release cycle to automatically do project builds, versioning and release branch management when you deploy:

For the purposes of this discussion assume these values:

Application name: testapp
Remote Domain: mycorp.com
GitHub project name: testapp_proj
Local login name: localuser
Remote login name: remoteuser
Remote server name: remoteserver

Also, assume that the remote system manages a daemon process called tds with systemctl.

Here's the sample shipitfile.js for this scenario:

module.exports = function (shipit) {
  require('shipit-deploy')(shipit);

  shipit.initConfig({
    default: {
      workspace: '/tmp/testapp_deploy',
      deployTo: '/var/www/mycorp.com/app',
      repositoryUrl: 'https://github.com/mycorp/testapp_proj.git',
      ignores: ['.git', 'node_modules'],
      keepReleases: 2,
      deleteOnRollback: false,
      key: '~/.ssh/id_rsa.pub',
      shallowClone: true,
      branch: 'master'
    },
    development: {
      servers: 'localuser@localhost'
    }
    production: {
      servers: 'remoteuser@remoteserver.mycorp.com'
    }
  });

  //  At the beginning of the deployment process, build the release,
  //  generate a version and stamp it into the proper files and commit
  //  commit those files to the repo.
  shipit.blTask('testapp-deploy',
    async function() {
        var localCmd;

        localCmd =
          'tibet build --release; ' +
          'tibet version --patch --suffix final; ' +
          'git add package.json public/TIBET-INF/cfg/testapp.xml public/TIBET-INF/cfg/main.xml public/src/templates/version.js; ' +
          'git commit -m "Bumped patch version for release"; ' +
          'git push;';

        await shipit.local(localCmd).then(function(res) {
          console.log(res[0].stdout);
        });
    });

  //  When the repository is fetched, shut down the remote server
  //  This example assumes that the remote server is a *nix server
  //  with a 'tds' service defined.
  shipit.blTask('testapp-fetched',
    async function() {
        var remoteCmd;

        remoteCmd =
          'systemctl stop tds; ';

        await shipit.remote(remoteCmd).then(function(res) {
          console.log(res[0].stdout);
        });
    });

  //  When the repository is published, re-initialize the TIBET project so
  //  that we have the latest NodeJS modules.
  shipit.blTask('testapp-published',
    async function() {
        var remoteCmd;

        remoteCmd =
          'cd /var/www/mycorp.com/app; ' +

          'tibet init --force; ';

        await shipit.remote(remoteCmd).then(function(res) {
          console.log(res[0].stdout);
        });
    });

  //  The repository contents are deployed. Restart the server.
  shipit.blTask('testapp-deployed',
    async function() {
        var remoteCmd;

        remoteCmd =
          'systemctl start tds; ';

        await shipit.remote(remoteCmd).then(function(res) {
          console.log(res[0].stdout);
        });
    });

  shipit.on('deploy', function() {
      shipit.start('testapp-deploy');
  });

  shipit.on('fetched', function() {
      shipit.start('testapp-fetched');
  });

  shipit.on('published', function() {
      shipit.start('testapp-published');
  });

  shipit.on('deployed', function() {
      shipit.start('testapp-deployed');
  });

};

Deploying to cloud PAAS environments via Docker

TIBET includes built-in support for Dockerized applications. It includes a Dockerfile that installs an up-to-date NodeJS image as well as the parts of TIBET required to run an application.

Additionally, tibet deploy provides out-of-the-box support for deploying and running Dockerized TIBET applications in two PAAS environments: AWS Elastic Beanstalk and Azure WebApps.

  1. configure the tds.json file with the required parameters for the particular PAAS environment being targeted. These entries should go under the deploy section which should be placed directly under the desired environment (or the default environment settings if they apply to all environments).

Here is a set of settings for AWS Elastic Beanstalk:

 "deploy": {
            "awselasticbeanstalk": {
                "profile": "development",
                "region": "us-east-1",
                "appname": "TIBETAWSEBSTest",
                "solutionstack": "64bit Amazon Linux 2018.03 v2.16.2 running Docker 19.03.13-ce"
            }
        }

And here is a set of settings for Azure Webapps:

 "deploy": {
            "azurewebapps": {
                "username": "bedney@technicalpursuit.com",
                "resourcegroupname": "TIBETAzureTestResourceGroup",
                "resourcegrouplocation": "Central US",
                "containerregistryname": "TIBETAzureTestContainerRegistry",
                "containerregistrysku": "Basic",
                "appserviceplanname": "TIBETAzureTestPlan",
                "appservicesku": "B1",
                "appname": "TIBETAzureTest"
            }
        }

More information about these parameters can be found in the documentation for the respective PAAS services.

  1. After these settings have been added to the tds.json, perform following steps inside of the project directory:

tibet build --release
tibet version --patch --suffix final
git add package.json public/TIBET-INF/cfg/.xml public/TIBET-INF/cfg/main.xml public/src/templates/version.js
git commit -m "Bumped patch version for release"
git push

For more information about these steps, see the Releasing section of this document.

  1. Run the tibet deploy command with the proper subcommand matching the PAAS service that the app will be deployed to:

tibet deploy awselasticbeanstalk
OR
tibet deploy azurewebapps

Deploying via electron-builder

Customized versions of tibet build and tibet deploy in TIBET Electron projects automatically leverage the electron-builder package, which is included as a dependency in Electron projects.

  1. Configure the tibet.json file with the required parameters. These entries should go under the deploy section. Currently, the Election version of tibet deploy supports two environments for deployment of Electron builds to two different kinds of repositories: S3 buckets and GitHub Releases.

Here is a set of settings for S3 buckets:

"deploy": {
        "electron": {
            "updateURL": "https://helloelectron.s3.amazonaws.com",
            "provider": "s3",
            "s3": {
                "bucket": "helloelectron",
                "region": "us-east-1"
            }
        }
    }

And here is a set of settings for GitHub Releases:

"deploy": {
        "electron": {
            "updateURL": "https://github.com/MyOrganization/MyApp.git",
            "provider": "github",
            "github": {
                "token": "<YOUR_GITHUB_ACCESS_TOKEN>",
            }
        }
    }

  1. After these settings have been added to the tds.json, perform following steps inside of the project directory:

tibet build --release
tibet version --patch --suffix final
git add package.json public/TIBET-INF/cfg/.xml public/TIBET-INF/cfg/main.xml public/src/templates/version.js
git commit -m "Bumped patch version for release"
git push

For more information about these steps, see the Releasing section of this document.

  1. We also freeze a copy of TIBET into the project to make it self-contained. We need to specify both the --standalone and --source flags here to get both the 'server' code and the source code for TIBET.

tibet freeze --standalone --source

  1. Go to the ./etc/ directory in the project directory and edit the electron-builder-config.json file that contains information for signing and publishing your Electron application and edit the appId field to match your organization. This will initially be something like: org.electronjs.MyApp and should be changed to something like com.MyOrganization.MyApp.

  2. Mac: Set up your certificates in Xcode so that you have both Developer ID Application and Developer ID Installer certificates in your keychain.

Windows: TODO

  1. Mac: Obtain notarization app login/password and export shell variables to contain them. You can generate this password by logging into your Apple Developer account and generating one from there: https://appleid.apple.com/account/manage

export APPLEID="YOUR_APPLE_DEVELOPER_ID"
export APPLEIDPASS="YOUR_APPLE_NOTARIZATION_PASSWORD"

Windows: TODO

For more information about signing and notarization see:

Mac: Creating and deploying an auto-updating Electron app for Mac and Windows (this article doesn't really have a lot of information on Windows).

Windows: How to Code Sign an Electron App for Windows

  1. Run tibet deploy electron:

tibet deploy electron

TROUBLESHOOTING:

Mac:

If you get the following error message: xcrun: error: unable to find utility "altool", not a developer tool or in PATH

run the following command from a Terminal:

sudo xcode-select -s /Applications/Xcode.app

If you get an email from Apple that says that they could not notarize the application, you can check the notarization logs by issuing the following command. Note that the notarization request identifier can be found in the email.

"/usr/bin/xcrun" altool --verbose --notarization-info NOTARIZATION_REQUEST_IDENTIFIER -u "YOUR_APPLE_DEVELOPER_ID" -p "YOUR_APPLE_NOTARIZATION_PASSWORD"

Deploying via tibet make

TIBET's tibet make command will invoke any exported function you create and place in the ~app_cmd/make directory. As part of its default configuration all TIBET projects include a _deploy.js file in this directory you can use as a starting point for writing you own custom deployment logic.

Sample _deploy.js from a default TIBET project:

(function() {
    'use strict';

    module.exports = function(make, resolve, reject) {

        make.log('deploying application...');

        //  ---

        //  TODO:   replace this with your concrete deployment logic.
        make.warn('No concrete deployment logic.');

        //  ---

        resolve();
    };

}());

Obviously the previous file doesn't actually deploy, but you can invoke it via tibet make deploy or tibet deploy make (either will work). Adjust the logic to suit your needs and you can leverage tibet make to help you with your deployments.

Extending tibet deploy

The tibet deploy command supports extensions by creating custom "plugins" and placing them in the ~app_cmd directory (typically the cmd directory at the root of your TIBET project) in a deploy subdirectory.

The files in question should be named to match your subcommand name. For our example here we use this as our hypothetical deploy helper so we would create a file this.js in our project's cmd/deploy directory.

Plugins for tibet deploy must be loadable via require and return a function which, when invoked, will patch the deploy command prototype with the desired implementation. The method should be named executeSomething where 'Something' is replaced with the target name.

/**
 * A sample 'tibet deploy' command. This one is built to handle invocations of
 * the TIBET CLI with a command line of 'tibet deploy this'.
 */

(function() {
    'use strict';

    module.exports = function(cmdType) {

        //  NOTE: we patch the prototype since invocation is instance-level.
        cmdType.prototype.executeThis = function() {
            console.log('deploying this...');
        };
    };

}(this));

Additional pre-built logic for common deployment targets is under development.

code

TIBET's build and deployment infrastructure is based on a combination of TIBET CLI commands and tibet make targets.

Code for tibet make targets is found in ~app/cmd/make. In particular, the build make target which invokes clean, build_resources, _rollup, and _linkup as part of its processing.

Code for the CLI is contained in ~lib/src/tibet/cli. The package, resource, and rollup commands do most of the work. Logic in ~lib/etc/common/tibet_package.js is used by a number of these commands to assist them in processing TIBET's manifest files.