2019-08-23-CiecleCIでもくもく会を支える技術
CiecleCIでもくもく会を⽀える技術CircleCIユーザーコミュニティミートアップ on 2019-08-23by @threetreeslight1 / 32
View Slide
こんにちわ!2 / 32
whoamiNTT系SIer (SE -> sales)⾳楽系スタートアップ CTOメディア系スタートアップ 創業国内EC創業越境系発送代⾏サービス 1号エンジニアRepro創業兼CTO-> VP of Engineering-> VP of PeopleOpsAkira Miki@threetreeslightコードが書けるイベントおじさん3 / 32
今⽇話すこと・話さないこと話さないことReproでのCircleCI活⽤の詳細話すことReproとCircleCI利⽤どころ↑スポンサートークっぽいの⼀応⼤事↑CircleCIでイベント開催を⾃動化している話4 / 32
早速ですがスポンサートークいきます5 / 32
What'sRepro?6 / 32
CustomerEngagement Platform7 / 32
Web/App MA toolRight messageTo the right personAt the right time8 / 32
T2D3cf. Tech Crunch - The SaaS Adventure9 / 32
Traffic毎⽇10億を超えるイベントデータを処理AIでユーザーを⾃動セグメント毎⽇2億近いプッシュなど施策を配信10 / 32
そんなReproでのCircleCI利⽤11 / 32
⾊々使っていますKick to build Image on image build serverKick to test on EC2 cluster and collect artifactsAttach github label about riskRun plan and apply Terraform12 / 32
そんなハイトラフィックを楽しみにたい⽅へ13 / 32
We are hiring14 / 32
宣伝終了15 / 32
それでは本題参ります16 / 32
イベント継続は⼤変1.企画するのが⼤変2.作成するのが⾯倒3.運営に気を使う17 / 32
これを解決していく18 / 32
企画するのが⼤変もくもく会の内容を固定し、企画要素を削る19 / 32
作成するのが⾯倒オーガナイザーが複数いる場合は特に⾒合う。=>⾃動化しましょう20 / 32
Setup Config01 const moment = require('moment');02 const { EventDirCreator } = require('./event_dir_creator.03 const { Slack } = require('./slack.js');04 const { ConnpassEventCreator } = require('./connpass_even0506 Slack.setup(process.env.SLACK_API_TOKEN);0708 // TODO: replase oauth api token09 EventDirCreator.setup(process.env.GITHUB_API_TOKEN);1011 const nextEventNum = EventDirCreator.getNextEventNum();1213 EventDirCreator.createDirWithNum(nextEventNum);14 EventDirCreator.createPullRequestWithNum(nextEventNum);21 / 32
Setup Config16 const eventDate = moment().day('Saturday').add(21, 'd').f09 EventDirCreator.setup(process.env.GITHUB_API_TOKEN);1011 const nextEventNum = EventDirCreator.getNextEventNum();1213 EventDirCreator.createDirWithNum(nextEventNum);14 EventDirCreator.createPullRequestWithNum(nextEventNum);1517 const connpassEventSettings = {18 group: 'shinjuku-moku',19 title: `Shinjuku Mokumoku Programming #${nextEventNum}`20 subTitle: 'The Art of Mokumoku Programming',21 startDate: eventDate,22 startTime: '11:00',23 endDate: eventDate3W後の開催⽇を指定21 / 32
Setup Config18 group: 'shinjuku-moku',19 title: `Shinjuku Mokumoku Programming #${nextEventNum}`20 subTitle: 'The Art of Mokumoku Programming',21 startDate: eventDate,22 startTime: '11:00',23 endDate: eventDate,24 endTime: '18:00',14 EventDirCreator.createPullRequestWithNum(nextEventNum);1516 const eventDate = moment().day('Saturday').add(21, 'd').f17 const connpassEventSettings = {25 participation: [26 {27 attendeeType: '⾏きます', maxAttendees: 10, payDoor:イベントのメタ情報を突っ込む21 / 32
Setup Config53 {54 required: true,55 title: '本もくもく会は、⾃⼰紹介と当⽇やることを記述したPul56 answerType: 'radiobutton',57 options: ['問題なく遂⾏できる', 'やったことないので当⽇まで58 },47 {48 required: true,49 title: '本もくもく会は、slackでcommunicationを取ります。50 answerType: 'radiobutton',51 options: ['問題なく遂⾏できる', 'やったことないので当⽇まで52 },59 {60 required: true,61 title: 'もくもくして得た学びや気付き、成果をもくもく会終了時⺠度の担保を⽬的としたアンケートも作る21 / 32
Setup Config66 descriptionPath: `${__dirname}/../connpass.md`,58 },59 {60 required: true,61 title: 'もくもくして得た学びや気付き、成果をもくもく会終了時62 answerType: 'radiobutton',63 options: ['ノープロブレム', 'LT初⼼者ですががんばります']64 },65 ],67 preview: true,68 };6970 (async () => {71 const eventUrl = await ConnpassEventCreator.create(connイベント本⽂の位置を指定21 / 32
Automatically Create event with Puppeteer01 const puppeteer = require('puppeteer');02 const fs = require('fs');0304 const logger = console;0506 const ConnpassEventCreator = {};0708 ConnpassEventCreator.getDescription = path => fs.readFile0910 ConnpassEventCreator.create = async (settings) => {11 /* eslint-disable no-await-in-loop */1213 const user = process.env.CONNPASS_USER;14 const password = process.env.CONNPASS_PASSWORD;22 / 32
Automatically Create event with Puppeteer23 await page.goto('https://connpass.com/login/');24 const loginAreaSelector = '#login_form';25 await page.type(`${loginAreaSelector} input[name="usern26 await page.type(`${loginAreaSelector} input[name="passw27 await page.click(`${loginAreaSelector} button[type="sub28 await page.waitForSelector('.EventCreate');291920 // --------------------------------------------------21 // Login to connpass22 // --------------------------------------------------30 // --------------------------------------------------31 // Move to group page and create event32 // --------------------------------------------------33 if (typeof (settings group) === 'string') {めちゃめちゃがんばる22 / 32
Automatically Create event with Puppeteer78 // delete exist value79 await page.$eval(`${timeAreaSelector} input[name="end_d80 await page.keyboard.press('Backspace');81 // input data82 await page.type(`${timeAreaSelector} input[name="end_da83 // Unforcus from input form to close datepitcker84 await page.click(`${timeAreaSelector} th`);85 // Wait to close datepicker animation. Datepicker overw86 await page.waitFor(500);73 // wait for fill end date automatically74 await page.waitFor(500);7576 // focus input form77 await page.click(`${timeAreaSelector} input[name="end_d87アニメーションに殺意を覚える22 / 32
Config circleci01 # Javascript Node CircleCI 2.0 configuration file02 #03 # Check https://circleci.com/docs/2.0/language-javascript04 #05 version: 2.10607 orbs:08 puppeteer: threetreeslight/[email protected]0910 jobs:11 build:12 docker:13 - image: circleci/node:814 steps:23 / 32
Config circleci07 orbs:08 puppeteer: threetreeslight/[email protected]01 # Javascript Node CircleCI 2.0 configuration file02 #03 # Check https://circleci.com/docs/2.0/language-javascript04 #05 version: 2.1060910 jobs:11 build:12 docker:13 - image: circleci/node:814 steps:puppeteer install orb作って23 / 32
Config circleci96 nightly:97 triggers:98 - schedule:99 # Run evey friday100 cron: "0 0 * * 5"101 filters:102 branches:103 only:104 - master91 requires:92 - build93 filters:94 branches:95 only: master105 j bnightly buildする23 / 32
訪れる幸せ24 / 32
ちなみにcircleciと⽇本からではconnpass serverへのnetwork latencyが異なるので(致し⽅がない)sleepの微調整が必要25 / 32
運営に気を使うtimetableにあわせ、いちいちslackのreminder設定したり連絡したりするの⾟い。=> firebase function + slash commandで凌ぐ26 / 32
slack remind and command01 // Prepare for event start02 //03 // 1. Create vol-xx channel04 // 1. Set lunch and due reminder05 // 1. Set lunch poller06 // 1. Set announce event channel to general0708 const logger = console;09 const { Slack } = require('./slack.js');1011 const Preparation = {};1213 Preparation.start = async (slackToken, num) => {14 Slack.setup(slackToken);27 / 32
slack remind and command15 const currentChannelName = `vol-${num}`;1617 logger.info(`channel name is ${currentChannelName}`);1819 await Slack.create_channel(currentChannelName);1011 const Preparation = {};1213 Preparation.start = async (slackToken, num) => {14 Slack.setup(slackToken);20 const channelId = await Slack.get_channel_id(currentCha2122 // Event channel announce23 const generalId = await Slack.get_channel_id('general')24開催回のchannel作って27 / 32
slack remind and command25 // for introduction26 Slack.message(generalId, `今⽇のshinjuku mokumoku slack27 Slack.message(channelId, 'wifi: \nhttps://gitpitch.com/28 Slack.command(channelId, '/remind', `<#${channelId}> \n19 await Slack.create_channel(currentChannelName);20 const channelId = await Slack.get_channel_id(currentCha2122 // Event channel announce23 const generalId = await Slack.get_channel_id('general')2429 "@channelわからないことがあるときはまず以下を参照しましょう :poi30 \n31イベントページ: https://shinjuku-moku.connpass.com/\n32 introduction資料: https://gitpitch.com/shinjuku-mokumoku/s33 \通知を設定27 / 32
slack remind and command34 *:warning: Attention :warning:*\n35 -会場IPからのスクレイピング・クローリングコードの実⾏は⽌めてくださ36 -本イベントは[アンチハラスメントポリシー](http://25.ruby.or.jp37 -どなたでもblogなどにあげられるよう写真撮影を許可していますので、そ38 -途中退出される場合は、**PRに**今⽇の成果をお出しください\n28 Slack.command(channelId, /remind , <#${channelId}> \n29 "@channelわからないことがあるときはまず以下を参照しましょう :poi30 \n31イベントページ: https://shinjuku-moku.connpass.com/\n32 introduction資料: https://gitpitch.com/shinjuku-mokumoku/s33 \n39 " at 11:30`);4041 // Lunch42 Sl k d( h lId '/ ll' '"昼⾷どこらへんが好き?諸注意も再連絡しつつ27 / 32
slack remind and command41 // Lunch42 Slack.command(channelId, '/poll', '"昼⾷どこらへんが好き?34 *:warning: Attention :warning:*\n35 -会場IPからのスクレイピング・クローリングコードの実⾏は⽌めてくださ36 -本イベントは[アンチハラスメントポリシー](http://25.ruby.or.jp37 -どなたでもblogなどにあげられるよう写真撮影を許可していますので、そ38 -途中退出される場合は、**PRに**今⽇の成果をお出しください\n39 " at 11:30`);4043 Slack.message(channelId, 'ランチリスト: \nhttps://github.44 Slack.command(channelId, '/remind', `<#${channelId}> "45 @channelもうすぐlunchです。ランチアンケートへの回答しましょう!\46ランチリスト: \n47 https://github.com/shinjuku-mokumoku/shinjuku-mokumoku/bl公開されていないSlack APIを使ってコマンドを叩き27 / 32
slack remind and command54 // checkout55 Slack.command(channelId, '/remind', `<#${channelId}> "56 @channel checkoutまであと1hです!成果のまとめなどしていきましょう57発表は *1.5-3 min +質問 0-2min / person*です!58 " at 16:00`);59 Slack.command(channelId, '/remind', `<#${channelId}> "60 @channel checkoutの10min前です!\n49 Slack.command(channelId, '/remind', `<#${channelId}> "@5051 // check templature52 Slack.command(channelId, '/remind', `<#${channelId}> "暑5361今⽇の成果項を更新しshinjuku-mokumokuへPRをお願いします :muscle62発表ではchrome castを使います。終了のリマインドまで⼊れる27 / 32
deploy firebase01 const { execSync } = require('child_process');02 const Octokit = require('@octokit/rest');0304 const octokit = new Octokit();05 const logger = console;0607 const owner = 'shinjuku-mokumoku';08 const repo = 'shinjuku-mokumoku';0910 const pullRequestNum = async () => {11 if (process.env.CIRCLE_PR_NUMBER) {12 return process.env.CIRCLE_PR_NUMBER;13 }1428 / 32
deploy firebase27 const hasFunctionDiff = async () => {28 const num = await pullRequestNum();29 logger.debug(`Pull Request is https://github.com/shinju3031 const res = await octokit.pulls.listFiles({ owner, repo32 const functionFileNames = res.data.map(files => files.f33 logger.debug(`Change Files: ${functionFileNames.join(',3435 return functionFileNames.length > 0;36 };p [ ] ;25 };263738 const deploy = async () => {変更があるときだけfirebase functionsにdeployされるようにしておき28 / 32
build and deploy01 # Javascript Node CircleCI 2.0 configuration file02 #03 # Check https://circleci.com/docs/2.0/language-javascript04 #05 version: 2.10607 orbs:08 puppeteer: threetreeslight/[email protected]0910 jobs:11 build:12 docker:13 - image: circleci/node:814 steps:29 / 32
build and deploy61 - run:62 name: Deploy Master to Firebase63 command: npm --prefix functions run deployg55 - functions_npm_modules-56 - run: npm --prefix functions install57 - save_cache:58 paths:59 - functions/node_modules60 key: functions_npm_modules-{{ checksum "functio6465 event:66 docker:67 - image: circleci/node:868 steps:build & deployしていく29 / 32
訪れる幸せ30 / 32
ちなみにcommunityで使うfirebaseの登録クレカ、どうするか悩みますよね31 / 32
まとめ誰やるにならないように常に⾃動化puppeteerの可能性無限⼤slackのslash commandの可能性無限⼤CIにまかせていくsheduled workflow便利! orb便利!network latencyには気をつける時間⾒つけてイベント⾃動作成が外でもできるようpackage化していこう32 / 32