Integrating migrations in a continuous delivery pipeline

A presentation at Contentful Webinar in August 2018 in Brooklyn, NY, USA by Shy Ruparel

Slide 1

Slide 1

Content testing and deployment done the right way By Shy Ruparel

Slide 2

Slide 2

Shy Ruparel Developer Evangelist Twitter: @ShyRuparel Talks: GitHub/Shy/Talks

Slide 3

Slide 3

Building Software

Slide 4

Slide 4

Nobody gets it right the first time

Slide 5

Slide 5

When you can’t have perfection, the next best thing is change.

Slide 6

Slide 6

What could go wrong?

Slide 7

Slide 7

Let's talk about the Contentful Migration CLI

Slide 8

Slide 8

TL;DR What you can do ⚒ Create content type ❌ Delete content type ⌨ Edit content type ⚒ Create/edit/delete fields Change field ID

Slide 9

Slide 9

All in Javascript module.exports = function runMigration(migration) { const post = migration.editContentType("post"); post.deleteField("last_appearance"); return; };

Slide 10

Slide 10

Callable via the Contentful CLI contentful space migration --space-id $YOUR_SPACE_ID --access-token $CONTENTFUL_MANAGEMENT_ACCESS_TOKEN migration-demo.js

Slide 11

Slide 11

Programmatic Content model changes

Slide 12

Slide 12

Advantages Repeatable Can be kept in version control Sanity checks Use CI to apply.

Slide 13

Slide 13

Demo

Slide 14

Slide 14

That's nice But what if I want to discard, test or preview a migration?

Slide 15

Slide 15

Slide 16

Slide 16

Space Environments

Slide 17

Slide 17

Space environments allow you to create multiple versions of the space, and change them in isolation.

Slide 18

Slide 18

Initialize the client const contentful = require('contentful'); const client = contentful.createClient({ space: 'space_id', accessToken: 'content_delivery_api_key' }); client.getEntries();

Slide 19

Slide 19

Initialize the client with environments const contentful = require('contentful'); const client = contentful.createClient({ space: 'space_id', environment: 'environment_id', accessToken: 'content_delivery_api_key' }); client.getEntries();

Slide 20

Slide 20

Combine migrations with environment contentful space migration --space-id $YOUR_SPACE_ID --environment-id $YOUR_ENV_ID --access-token $CONTENTFUL_MANAGEMENT_ACCESS_TOKEN migration-demo.js

Slide 21

Slide 21

Common uses for space environments Local development Staging / QA Continuous integration

Slide 22

Slide 22

Let's talk about Continuous Integration

Slide 23

Slide 23

A simple Continuous Integration pipeline: Build Test Deploy

Slide 24

Slide 24

Integrating Migrations into CI Pipeline: Build Create a new environment Migrate new environment Test Deploy Migrate master environment

Slide 25

Slide 25

Slide 26

Slide 26

Build & Test

Slide 27

Slide 27

Deploy

Slide 28

Slide 28

Keeping code and content model in sync

Slide 29

Slide 29

Version Tracking Content Model

Slide 30

Slide 30

Version Tracking Content

Slide 31

Slide 31

Migration Files

Slide 32

Slide 32

Slide 33

Slide 33

CircleCI config.yml - Configure Contentful - run: name: Preparing environment for testing command: | . venv/bin/activate scripts/migrate.js $SPACE_ID "CI_$CIRCLE_BRANCH" $MANAGEMENT_API_KEY

Slide 34

Slide 34

CircleCI config.yml - Update Testing - run: name: run tests command: | . venv/bin/activate pytest --environment-id="CI_$CIRCLE_BRANCH"

Slide 35

Slide 35

Let's dig into migrate.js

Slide 36

Slide 36

Utilize the Contentful CMA const { createClient } = require('contentful-management'); //...// const client = createClient({ accessToken: CMA_ACCESS_TOKEN }); const space = await client.getSpace(SPACE_ID);

Slide 37

Slide 37

Does the environment exist? console.log(`Checking for existing versions of environment: ${ENVIRON try { environment = await space.getEnvironment(ENVIRONMENT_ID); if (ENVIRONMENT_ID != 'master'){ await environment.delete(); console.log('Environment deleted'); } } catch(e) { console.log('Environment not found'); }

Slide 38

Slide 38

Create the environment if (ENVIRONMENT_ID != 'master'){ console.log(Creating environment ${ENVIRONMENT_ID}); environment = await space. createEnvironmentWithId(ENVIRONMENT_ID, { name: ENVIRONMENT_ID }); }

Slide 39

Slide 39

Allow API Key access console.log('Update API Keys to allow access to new environment'); const newEnv = { sys: { type: 'Link', linkType: 'Environment', id: ENVIRONMENT_ID } } const {items: keys} = await space.getApiKeys(); await Promise.all(keys.map(key => { console.log(Updating - ${key.sys.id}); key.environments.push(newEnv); return key.update(); }));

Slide 40

Slide 40

Run needed Migrations console.log('Run migrations and update version entry'); while(migrationToRun = migrationsToRun.shift()) { const filePath = path.join(__dirname, '..', 'migrations', getFileOf console.log(Running ${filePath}); await runMigration(Object.assign(migrationOptions, { filePath })); console.log(${migrationToRun} succeeded); storedVersionEntry.fields.version[defaultLocale] = migrationToRun; storedVersionEntry = await storedVersionEntry.update(); storedVersionEntry = await storedVersionEntry.publish(); console.log(Updated version entry to ${migrationToRun}); } console.log('All done!');

Slide 41

Slide 41

Demo

Slide 42

Slide 42

Integrate evolving your content model into your delivery pipeline

Slide 43

Slide 43

Check out the example: github.com/contentful-labs/continous-deliveryenvironments-example

Slide 44

Slide 44

Shy Ruparel Twitter: @ShyRuparel Email: shy@contentful.com Talks: GitHub/Shy/Talks