Giter VIP home page Giter VIP logo

deploy-to-sfdx's Introduction

SFDX Deployer

npm version

Front end: LWC

Back end: nodejs/express/typescript + heroku buildpacks

Test: jest

Video explanation from May 2019 (TDX) (some things have changed, especially LWC instead of vue)

Purpose

You have a dev hub, and an sfdx repo. You'd like to let people spin up scratch orgs based on the repo, and these people might not feel like using a terminal, cloning the repo, loggin in to a dev hub, and executing sfdx commands.

  • because they might not be developers (think admins, or even end users in a training scenario)
  • because they might not be Salesforce developers (say you built an app and give your designer/CSS person github access to "make it cool")
  • because you might have dev hub access and you don't want to give it to them
  • because you want to let people test the app quickly
  • because (like me) you're using it for workshops and demos

Connected app setup

Create a connected app for JWT auth, with certificates, per the SFDX setup guide.

https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_jwt_flow.htm


Environment variables

you can find a list of all the env vars in https://github.com/mshanemc/deploy-to-sfdx/blob/master/src/server/lib/processWrapper.ts

Required

  • HUB_USERNAME the username from your dev hub
  • CONSUMERKEY from your connected app
  • REDIS_URL starts with redis:// (probably set up automatically when you use the heroku button)

Plus one of the following, depending on where you're running

  • JWTKEY use only in heroku cloud. Cut/paste your server.key, including the ---- lines
  • LOCAL_ONLY_KEY_PATH don't use this in the cloud. Put it in your local .env file, and it needs to be an absolute path

strongly recommended

  • HEROKU_API_KEY can run one-off dynos, needed for org pools (see below) and lets you combine the deployer with my sfdx plugin to deploy heroku apps to a team. If you're note using org pools, be sure to delete these heroku apps. See the plugin docs for how to use this

optional

  • UA_ID for google analytics measurement protocol
  • GITHUB_USERNAME_WHITELIST lets you whitelist usernames. It's a comma-separated list. Ex: mshanemc,andrew,bebraw
  • GITHUB_REPO_WHITELIST lets you whitelist username/repo combinations. It's a comma-separated list. Ex: mshanemc/DF17integrationWorkshops,torvalds/linux
  • if you need to use the prerelease version of the sfdx plugin, then set SFDX_PRERELEASE to true.
  • org pools -- see below for details
  • BYOO (bring your own org) -- see below for details

What's whitelisting do? Normally, this app will parse your orgInit.sh and throw an error if you're doing any funny business. BUT if you're on the whitelist, the app owner trusts you and you can do things with bash metacharacters (think &&, |, >) and execute non-sfdx commands (grep, rm, whatever!) etc. BE CAREFUL!

Here's a heroku button so you can have your own instance of the Deployer

Deploy


Heroku setup

The button will start this on free hobby dynos. For real life, I'd recommend a pair of web 1x and a pair of workers (orgbuilder) at 1x.

If you're going to be doing a lot of users (imagine a workshop where lots of people are pushing the button at the same time) you can scale out more workers so the people last in line don't have to wait so long. Otherwise, it'll spin while the workers are busy processing the deploy request queue.

The oneoffbuilder is the same as the orgbuilder but exits when it's finished with its queue item. They do take ~30 seconds to spin up before starting to build an org, so having "live workers" is better. These give you the ability to scale out work horizontally (booting up a new dyno will be faster than waiting in line behind several other deploys on the always-live workers)

Up to you to balance costs between live workers (orgbuilder) and on-demand (oneoffbuilder).

Add Heroku Scheduler to your app and set orgdeleter to run every 10 minutes or so. Deleted orgs go into a deletion queue that isn't actively monitored so this will free up some scratch org capacity for you. Then have dynoskimmer run every 10 minutes or so...it kills one-off dyno processes that stalled out If you're using org pools, you also want to have scheduler running poolwatcher (checks actual pools vs. configuration and starts poolbuilder processes) and poolskimmer (deletes expired orgs from the pools) scheduled.


Architectural overview

Nodejs, express run the web server. When the web server receives a request, it creates a unique deployID (user-repo-timestamp plus a few random digits) and a message on the deploy queue (using Heroku Redis) The server redirects the user to a web page which polls an http endpoint (/results/:deployId) for updated results

When a process starts up, it auths to a devhub via its environment variables. Then it listens to the deploy queue and executes jobs

  • clone the repo into local filestorage
  • execute the orgInit.sh script, or the default create/push/open flow if there isn't one
  • drop the output results into a redis key (the deployId) so that the results endpoint can find it
  • delete the local folder and send the ALLDONE message

It runs a plugin that give it powers SFDX doesn't out-of-the-box https://github.com/mshanemc/shane-sfdx-plugins along with sfdx-migration-automatic and @salesforce/analytics and sfdmu

Put plugins in the package.json dependencies, then linked from source in lib/hubAuth.js. Feel free to add additional plugins using yarn add some-plugin-of-yours and then add it in hubAuth.ts.


BYOO Bring your own org (optional, mildly dangerous)

Let people deploy from a repo to an existing org, and run some scripts. Set the BYOO environment variables to match a connected app (it needs to be a separate connected app from the one your hub uses because that one uses cert/jwt to auth, and this one won't have a cert).

You'll need to set the appropriate callback URIs for the /token page. Mine look like this (3 cloud environments plus local)

BYOO ConnectedApp Details

Your app should have the following scope

  • Access your basic information (id, profile, email, address, phone)
  • Access and manage your data (api)
  • Provide access to your data via the Web (web)
  • Allow access to your unique identifier (openid)

Users sign into their app, and then the deployer connects that instead of using a new scratch org. Password commands are omitted from the scripts, since that would be cruel.

Change the launcher url from .../launch?template=... to .../byoo?template=... to use the BYOO feature.

The page warns people about the risks of executing scripts in a non-scratch org. Expect failures because you can't guarantee that the target org has the features/settings your repo depends on.


Org pools (optional, advanced!)

Building orgs that take too long? Ever have one that doesn't get its DNS ready in time? Know you're mostly deploying the same orgs all the time?

Org Pools are the answer. You tell it which username/repo pairs, and how many orgs you'd like pre-built. When the user requests one, you simply grab one from the pool. Orgs in the pool are less than 12 hours old so they stay fresh.

There's 3 worker dynos, both off by default (leave them that way).

  • If you want pools, use Heroku Scheduler to run the poolwatcher task up to every 10 minutes (as a one-off dyno). If any pool orgs need to be created, it'll start up one-off dynos to handle that
  • run poolskimmer with Heroku Scheduler every hour or so--it'll check for expired orgs to help keep you within your limits.

Then, in your .env/heroku config vars, point the deployer to some url that returns json. POOLCONFIG_URL = https://where.yourstuff/is.

Finally, since poolwatcher is starting dynos to handle this pool stuff, you want to enable a heroku Labs setting for getting dyno metadata heroku labs:enable runtime-dyno-metadata -a. This lets heroku start more heroku with the name of your app being dynamically fed into the environment variables without you having to 1) set that up 2) maintain different names for each instance/stage

Example code here, but feel free to generate it however you like. https://github.com/mshanemc/poolsConfig

[
    {
        "user": "mshanemc", //ex: lives at https://github.com/mshanemc/cg1
        "repo": "cg1",
        "quantity": 4, //how many of this org to keep handy
        "lifeHours": 12 //how long it should live, in hours
    }
    // ... use 1 for each repo
]

pooldeployer should have 0 dynos running. It runs as a one-off dyno called by poolwatcher


Local Setup and Build (Mac...others, who knows?)

in your .env add

  • LOCAL_ONLY_KEY_PATH=/Users/shane.mclaughlin/code/certificates/server.key or your equivalent to where your cert for jwt is
  • in your process.env file, put the Heroku Redis url (it can be the same as you're using in the cloud. Don't commit this file to your repo.
  • yarn install will get all your modules installed, including my plugin.
  • yarn build will get the typescript and LWC output from /src to /built, which is where the executables go.
  • then start this app with yarn local:web and use localhost:8443

Building for Local Dev

Backend: npx nodemon will recompile all your typescript and restart the local web and worker servers.

Frontend (LWC): yarn watch:clien will start a server on localhost:3001. It'll rebuild and hotswap anytime you save a file.

Running both is good if you're working both front and backend

Things you might need

Ensure the following is installed:


Debugging on [non-local] Heroku

if hosted-scratch-qa is the name of your app

heroku logs --tail -a hosted-scratch will give you the logs.

heroku redis:cli -a hosted-scratch -c hosted-scratch will lets you look at your redis stuff and see what's in the queue

heroku ps:exec -a hosted-scratch-qa --dyno=worker.1 lets you ssh into the dyno of your choice and take a look around, clean stuff up, etc.


Setting up a repo (optional)

So you need a target repo to deploy (see examples below). If your repo is simple, the tool will do the default behavior (create, push source, open).

But with an orgInit.sh file, you can list out all your sfdx commands and they'll be executed by the deployer. Remember, no bash metacharacters and only sfdx commands are allowed (We can't let anyone run any arbitrary command on our servers...security, yo!)

That lets you create records, assign permsets, create users, install packages, run tests, generate passwords, and do anything you can do with an SFDX command

Private repos

By default, the deployer expects all repos to be public. If you really need private repos, you can use the following buildpack and a Github Personal Access Token.

https://github.com/mshanemc/github-via-pat

Whatever the personal access token user has access to see, the deployer can then see.

Testing

uses Jest.

There's a file called testRepos that you'll want to customize with any repos you want to use for verification. It'll probably fail if you use mine.

Run them with yarn test:unit. A few of them are not true unit tests...the require a server and redis running, and will try to connect to github for your testRepos. Run each of these commands in a separate terminal.

yarn local:web

Integration (tests/integrationTests) are slower/harder.

yarn test:generate will parse testRepos.ts and create a integration tests for each repo that

  1. tests that it deploys
  2. builds a pooled org using org pools if you specify testPool=true in testrepos
  3. tests that it deploys from the pool

Modify repoCodeGen.ts to change the generator.

NOTE: This is using up your scratch org quotas. The tests delete the orgs, so it's minimally wasteful, but still expect it to take a while AND watch your daily limit. Especially if you're testing deploys and tests are failing...you might be using orgs that never get to the delete phase.


Contributing

I'm using typescript...

  • yarn install will get all your modules installed, including my plugins.
  • yarn build will get any typescript changes from /src to /built, which is where the executables go.

Finally, the front end app is Lightning Web Components. You'll figure it out...if not, start here: https://lwc.dev/


Launcher URLs

There's not anything at / on the server. Don't worry. The only page you probably care about is /launch which takes 1 parameter template

So your path should be https://whatever.herokuapp.com/launch?template=https://github.com/username/reponame

Also handles branches on github, like https://whatever.herokuapp.com/launch?template=https://github.com/username/reponame/tree/somebranch

You can optionally pass in an email parameter so that the emails go to someone who's not the hub owner :) https://whatever.herokuapp.com/launch?template=https://github.com/username/reponame&[email protected]

You can pass in &email=required to force user to a form where they add their email. This is useful if they need to deal with emails from the org, or password resets, or security challenges, etc.


Example Repos with orgInit.sh scripts

Deploy https://github.com/mshanemc/DF17integrationWorkshops


Deploy https://github.com/mshanemc/community-apps-workshop-df17


Deploy https://github.com/mshanemc/process-automation-workshop-df17


Deployhttps://github.com/mshanemc/df17AppBuilding

deploy-to-sfdx's People

Contributors

mshanemc avatar wadewegner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

deploy-to-sfdx's Issues

package-only repos

collect package(Version)IDs and use them as a template parameter instead of github repos with scripts

Dyno in an exit loop when the pool is empty

Hi @mshanemc,

Looking at the Procfile, I noticed that the process that monitors the pool exits every time the pool is empty, causing the dyno to restart each time.

If the pool is empty most of the time, it means the dyno will be on an exit loop, constantly restarting.
I don't know the specifics of this application and I'm not a Javascript expert, so please forgive me if I'm missing something, but I'm wondering if something like this would work better for this use case:

import { poolBuild } from '../lib/poolBuild';

(async () => {
    while (true) {
        let builtSomething = await poolBuild();
        if (!builtSomething) {
            // the pool is empty, let's wait N seconds and try again
            sleep(5);
        }
    }
})();

What do you think?

[Enhancement request] Have an integration with an Oauth provider to put the creator behind login/domain validation.

It would be awesome if I could plug this easily into something Auth0 for example to require users to authenticate with Google and only authorize my company's domain perhaps?

Since Heroku is kinda open to the entire world and that this allows to create scratch orgs which are a limited resource, it would be nice to be able to secure that app very easily once deployed. The Auth0 Heroku addon seemed like a good candidate for that I think and it would support tons of OAuth identity providers.

Too many console.log statements

With the latest change, there is a console.log statement every 1-2 seconds. There are just too many logs in the end in browser console.

Can this be changed back to original where it only logged when status changed?
sfdx_console_log_errors

Branches are case sensitive in git, but deployer forces lowercase

When trying to deploy from a branch where the branch name is MixedCase, deployer fails. This issue is new since Jan 22, 2020
Example repo:
https://github.com/rreboucas/sparkle-dx/tree/SparkleADK

Using the latest commit prior to Jan 22 the clone command is:
git clone -b SparkleADK --single-branch https://github.com/rreboucas/sparkle-dx.git rreboucas-sparkle-dx-158395585139371

Using the latest master commit the clone command is:
git clone -b sparkleadk --single-branch https://github.com/rreboucas/sparkle-dx.git rreboucas-sparkle-dx-1583956074826ad

And the error:
warning: Could not find remote branch sparkleadk to clone. fatal: Remote branch sparkleadk not found in upstream origin

multi-repo

provide multiple &template= and have them all deployed to a single org

auto-open

use js redirect to open the page in a new tab/window when the open task arrives

Feature Request: A way to check for error on a command in orgInit.sh

Currently, if we have an error in a command then, most often, it will continue to try and run other commands which may also fail. We don't want to whitelist repos to allow users to use bash commands to check for errors.

If there can be a mechanism to see if previous command has failed and gracefully exit out of our script (maybe with a message) then that'll go a long way.

I tried to use BYOO in one of our orgs. The very first commands failed because community is not setup yet it continued to try and run other commands.

http://hosted-scratch.herokuapp.com/byoo?template=https://github.com/forcedotcom/WorkDotCom-Partners&email=required

BYOO_errors
BYOO_Errors_2

The URL /userinfo?<repolink> looks like it's going to work but does not

When you use the url /launch?template=&email=required, you are redirected to /userinfo? which prompts for an email address.

This is a very attractive link to copy and share, but reusing that link and submitting sends you to
/error?msg=TypeError:%20Cannot%20read%20property%20%27length%27%20of%20undefined

Can this error be captured up front?

Was spawn() considered vs exec() to execute shell script lines ?

Hi @mshanemc, I was curious to know whether you considered using spawn() instead of exec() to run shell scripts line by line ?

It seems it would have been an easier solution to track an ongoing job.
If so, did you encounter any memory (maxBuffer size) problems ?

Thanks for your insights on exec() vs spawn() in this situation.

Browser messages late by 1 step

In the browser, when any action happens, it shows the step after it happens.

For example, "1. creating an org" shows up after the org has been created rather than before.

Question about an error, deploying example repo mshanemc/df17AppBuilding

Hi Shane,
I've just set up deploy-to-sfdx, and tried deploying mshanemc/df17AppBuilding as an example. I'm getting the following error :

2020-04-04T21:15:32.459990+00:00 app[orgbuilder.1]: status=1 name=TypeError message="error running line sfdx shane:org:create -f config/project-scratch-def.json -s -d 1 --userprefix crash --userdomain course.org --json from deploy that includes rupertbarrow/df17appbuilding: Cannot read property 'orgId' of undefined" exitCode=1 commandName=CreateOrg stack="TypeError: Cannot read property 'orgId' of undefined

I have cloned deploy-to-sfdx, activated all the dynos, checked that auth is going well :

auth res = {

stdout: '{\n' +
 '  "status": 0,\n' +
 '  "result": {\n' +
 '    "orgId": "00D1i000000U______",\n' +
 '    "accessToken": "YYY",\n' +
 '    "instanceUrl": "https://XXX.my.salesforce.com",\n' +
 '    "loginUrl": "https://login.salesforce.com",\n' +
 '    "username": "[email protected]",\n' +
 '    "clientId": "123",\n' +
 '    "privateKey": "/app/tmp/server.key"\n' +
 '  }\n' +
 '}\n',
stderr: ''

}

Have you got an idea of what could be the problem ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.