Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
CiecleCIでもくもく会を支える技術
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
threetreeslight
August 23, 2019
Technology
0
56
CiecleCIでもくもく会を支える技術
2019-08-23-CiecleCIでもくもく会を支える技術
threetreeslight
August 23, 2019
Tweet
Share
More Decks by threetreeslight
See All by threetreeslight
実録 採用一投入魂
threetreeslight
0
22
Bottleneck is You
threetreeslight
0
120
Japan Office Society オフィスはスタートアップの成長を助長するのか?阻害するのか?
threetreeslight
0
120
スタートアップは見極められたくない
threetreeslight
0
48
VPoEの責務とは
threetreeslight
0
82
Ego vs higher self
threetreeslight
0
45
Performance Hack 101
threetreeslight
0
97
複数のスタートアップを 通して得た失敗と学び
threetreeslight
0
75
How to probe prometheus & grafana. What is helm
threetreeslight
0
40
Other Decks in Technology
See All in Technology
茨城の思い出を振り返る ~CDKのセキュリティを添えて~ / 20260201 Mitsutoshi Matsuo
shift_evolve
PRO
1
160
Bill One 開発エンジニア 紹介資料
sansan33
PRO
4
17k
顧客との商談議事録をみんなで読んで顧客解像度を上げよう
shibayu36
0
130
あたらしい上流工程の形。 0日導入からはじめるAI駆動PM
kumaiu
5
740
Meshy Proプラン課金した
henjin0
0
210
クレジットカード決済基盤を支えるSRE - 厳格な監査とSRE運用の両立 (SRE Kaigi 2026)
capytan
6
2.1k
【5分でわかる】セーフィー エンジニア向け会社紹介
safie_recruit
0
41k
AI推進者の視点で見る、Bill OneのAI活用の今
sansantech
PRO
2
330
開発メンバーが語るFindy Conferenceの裏側とこれから
sontixyou
2
600
GitLab Duo Agent Platform × AGENTS.md で実現するSpec-Driven Development / GitLab Duo Agent Platform × AGENTS.md
n11sh1
0
110
20260129_CB_Kansai
takuyay0ne
1
270
Tebiki Engineering Team Deck
tebiki
0
24k
Featured
See All Featured
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
61
49k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
51k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
0
3.4k
Heart Work Chapter 1 - Part 1
lfama
PRO
5
35k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
170
Raft: Consensus for Rubyists
vanstee
141
7.3k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
450
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
400
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
110
Into the Great Unknown - MozCon
thekraken
40
2.2k
A Modern Web Designer's Workflow
chriscoyier
698
190k
Transcript
CiecleCI でもくもく会を ⽀える技術 CircleCI ユーザーコミュニティミートアップ on 2019-08-23 by @threetreeslight 1
/ 32
こんにちわ! 2 / 32
whoami NTT 系SIer (SE -> sales) ⾳楽系スタートアップ CTO メディア系スタートアップ 創業
国内EC 創業 越境系発送代⾏サービス 1 号エンジニア Repro 創業兼CTO -> VP of Engineering -> VP of PeopleOps Akira Miki @threetreeslight コードが書けるイベントおじさん 3 / 32
今⽇話すこと・話さないこと 話さないこと Repro でのCircleCI 活⽤の詳細 話すこと Repro とCircleCI 利⽤どころ ↑スポンサートークっぽいの⼀応⼤事↑
CircleCI でイベント開催を⾃動化している話 4 / 32
早速ですが スポンサートークいきます 5 / 32
What's Repro? 6 / 32
Customer Engagement Platform 7 / 32
Web/App MA tool Right message To the right person At
the right time 8 / 32
T2D3 cf. Tech Crunch - The SaaS Adventure 9 /
32
Traffic 毎⽇10 億を超えるイベントデータを処理 AI でユーザーを⾃動セグメント 毎⽇2 億近いプッシュなど施策を配信 10 / 32
そんなRepro での CircleCI 利⽤ 11 / 32
⾊々使っています Kick to build Image on image build server Kick
to test on EC2 cluster and collect artifacts Attach github label about risk Run plan and apply Terraform 12 / 32
そんなハイトラフィック を楽しみにたい⽅へ 13 / 32
We are hiring 14 / 32
宣伝 終了 15 / 32
それでは本題 参ります 16 / 32
イベント 継続は⼤変 1. 企画するのが⼤変 2. 作成するのが⾯倒 3. 運営に気を使う 17 /
32
これを解決していく 18 / 32
企画するのが⼤変 もくもく会の内容を固定し、企画要素を削る 19 / 32
作成するのが⾯倒 オーガナイザーが複数いる場合は特に⾒合う。 => ⾃動化しましょう 20 / 32
Setup Config 01 const moment = require('moment'); 02 const {
EventDirCreator } = require('./event_dir_creator. 03 const { Slack } = require('./slack.js'); 04 const { ConnpassEventCreator } = require('./connpass_even 05 06 Slack.setup(process.env.SLACK_API_TOKEN); 07 08 // TODO: replase oauth api token 09 EventDirCreator.setup(process.env.GITHUB_API_TOKEN); 10 11 const nextEventNum = EventDirCreator.getNextEventNum(); 12 13 EventDirCreator.createDirWithNum(nextEventNum); 14 EventDirCreator.createPullRequestWithNum(nextEventNum); 21 / 32
Setup Config 16 const eventDate = moment().day('Saturday').add(21, 'd').f 09 EventDirCreator.setup(process.env.GITHUB_API_TOKEN);
10 11 const nextEventNum = EventDirCreator.getNextEventNum(); 12 13 EventDirCreator.createDirWithNum(nextEventNum); 14 EventDirCreator.createPullRequestWithNum(nextEventNum); 15 17 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: eventDate 3W 後の開催⽇を指定 21 / 32
Setup Config 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: eventDate, 24 endTime: '18:00', 14 EventDirCreator.createPullRequestWithNum(nextEventNum); 15 16 const eventDate = moment().day('Saturday').add(21, 'd').f 17 const connpassEventSettings = { 25 participation: [ 26 { 27 attendeeType: ' ⾏きます', maxAttendees: 10, payDoor: イベントのメタ情報を突っ込む 21 / 32
Setup Config 53 { 54 required: true, 55 title: '
本もくもく会は、⾃⼰紹介と当⽇やることを記述したPul 56 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 Config 66 descriptionPath: `${__dirname}/../connpass.md`, 58 }, 59 { 60
required: true, 61 title: ' もくもくして得た学びや気付き、成果をもくもく会終了時 62 answerType: 'radiobutton', 63 options: [' ノープロブレム', 'LT 初⼼者ですががんばります'] 64 }, 65 ], 67 preview: true, 68 }; 69 70 (async () => { 71 const eventUrl = await ConnpassEventCreator.create(conn イベント本⽂の位置を指定 21 / 32
Automatically Create event with Puppeteer 01 const puppeteer = require('puppeteer');
02 const fs = require('fs'); 03 04 const logger = console; 05 06 const ConnpassEventCreator = {}; 07 08 ConnpassEventCreator.getDescription = path => fs.readFile 09 10 ConnpassEventCreator.create = async (settings) => { 11 /* eslint-disable no-await-in-loop */ 12 13 const user = process.env.CONNPASS_USER; 14 const password = process.env.CONNPASS_PASSWORD; 22 / 32
Automatically Create event with Puppeteer 23 await page.goto('https://connpass.com/login/'); 24 const
loginAreaSelector = '#login_form'; 25 await page.type(`${loginAreaSelector} input[name="usern 26 await page.type(`${loginAreaSelector} input[name="passw 27 await page.click(`${loginAreaSelector} button[type="sub 28 await page.waitForSelector('.EventCreate'); 29 19 20 // -------------------------------------------------- 21 // Login to connpass 22 // -------------------------------------------------- 30 // -------------------------------------------------- 31 // Move to group page and create event 32 // -------------------------------------------------- 33 if (typeof (settings group) === 'string') { めちゃめちゃがんばる 22 / 32
Automatically Create event with Puppeteer 78 // delete exist value
79 await page.$eval(`${timeAreaSelector} input[name="end_d 80 await page.keyboard.press('Backspace'); 81 // input data 82 await page.type(`${timeAreaSelector} input[name="end_da 83 // Unforcus from input form to close datepitcker 84 await page.click(`${timeAreaSelector} th`); 85 // Wait to close datepicker animation. Datepicker overw 86 await page.waitFor(500); 73 // wait for fill end date automatically 74 await page.waitFor(500); 75 76 // focus input form 77 await page.click(`${timeAreaSelector} input[name="end_d 87 アニメーションに殺意を覚える 22 / 32
Config circleci 01 # Javascript Node CircleCI 2.0 configuration file
02 # 03 # Check https://circleci.com/docs/2.0/language-javascript 04 # 05 version: 2.1 06 07 orbs: 08 puppeteer: threetreeslight/
[email protected]
09 10 jobs: 11 build: 12 docker: 13 - image: circleci/node:8 14 steps: 23 / 32
Config circleci 07 orbs: 08 puppeteer: threetreeslight/
[email protected]
01 # Javascript
Node CircleCI 2.0 configuration file 02 # 03 # Check https://circleci.com/docs/2.0/language-javascript 04 # 05 version: 2.1 06 09 10 jobs: 11 build: 12 docker: 13 - image: circleci/node:8 14 steps: puppeteer install orb 作って 23 / 32
Config circleci 96 nightly: 97 triggers: 98 - schedule: 99
# Run evey friday 100 cron: "0 0 * * 5" 101 filters: 102 branches: 103 only: 104 - master 91 requires: 92 - build 93 filters: 94 branches: 95 only: master 105 j b nightly 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 command 01 // Prepare for event start
02 // 03 // 1. Create vol-xx channel 04 // 1. Set lunch and due reminder 05 // 1. Set lunch poller 06 // 1. Set announce event channel to general 07 08 const logger = console; 09 const { Slack } = require('./slack.js'); 10 11 const Preparation = {}; 12 13 Preparation.start = async (slackToken, num) => { 14 Slack.setup(slackToken); 27 / 32
slack remind and command 15 const currentChannelName = `vol-${num}`; 16
17 logger.info(`channel name is ${currentChannelName}`); 18 19 await Slack.create_channel(currentChannelName); 10 11 const Preparation = {}; 12 13 Preparation.start = async (slackToken, num) => { 14 Slack.setup(slackToken); 20 const channelId = await Slack.get_channel_id(currentCha 21 22 // Event channel announce 23 const generalId = await Slack.get_channel_id('general') 24 開催回のchannel 作って 27 / 32
slack remind and command 25 // for introduction 26 Slack.message(generalId,
` 今⽇のshinjuku mokumoku slack 27 Slack.message(channelId, 'wifi: \nhttps://gitpitch.com/ 28 Slack.command(channelId, '/remind', `<#${channelId}> \n 19 await Slack.create_channel(currentChannelName); 20 const channelId = await Slack.get_channel_id(currentCha 21 22 // Event channel announce 23 const generalId = await Slack.get_channel_id('general') 24 29 "@channel わからないことがあるときはまず以下を参照しましょう :poi 30 \n 31 イベントページ: https://shinjuku-moku.connpass.com/\n 32 introduction 資料: https://gitpitch.com/shinjuku-mokumoku/s 33 \ 通知を設定 27 / 32
slack remind and command 34 *:warning: Attention :warning:*\n 35 -
会場IP からのスクレイピング・クローリングコードの実⾏は⽌めてくださ 36 - 本イベントは[ アンチハラスメントポリシー](http://25.ruby.or.jp 37 - どなたでもblog などにあげられるよう写真撮影を許可していますので、そ 38 - 途中退出される場合は、**PR に** 今⽇の成果をお出しください\n 28 Slack.command(channelId, /remind , <#${channelId}> \n 29 "@channel わからないことがあるときはまず以下を参照しましょう :poi 30 \n 31 イベントページ: https://shinjuku-moku.connpass.com/\n 32 introduction 資料: https://gitpitch.com/shinjuku-mokumoku/s 33 \n 39 " at 11:30`); 40 41 // Lunch 42 Sl k d( h lId '/ ll' '" 昼⾷どこらへんが好き? 諸注意も再連絡しつつ 27 / 32
slack remind and command 41 // Lunch 42 Slack.command(channelId, '/poll',
'" 昼⾷どこらへんが好き? 34 *:warning: Attention :warning:*\n 35 - 会場IP からのスクレイピング・クローリングコードの実⾏は⽌めてくださ 36 - 本イベントは[ アンチハラスメントポリシー](http://25.ruby.or.jp 37 - どなたでもblog などにあげられるよう写真撮影を許可していますので、そ 38 - 途中退出される場合は、**PR に** 今⽇の成果をお出しください\n 39 " at 11:30`); 40 43 Slack.message(channelId, ' ランチリスト: \nhttps://github. 44 Slack.command(channelId, '/remind', `<#${channelId}> " 45 @channel もうすぐlunch です。ランチアンケートへの回答しましょう!\ 46 ランチリスト: \n 47 https://github.com/shinjuku-mokumoku/shinjuku-mokumoku/bl 公開されていないSlack API を使ってコマンドを叩き 27 / 32
slack remind and command 54 // checkout 55 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 前です!\n 49 Slack.command(channelId, '/remind', `<#${channelId}> "@ 50 51 // check templature 52 Slack.command(channelId, '/remind', `<#${channelId}> " 暑 53 61 今⽇の成果項を更新しshinjuku-mokumoku へPR をお願いします :muscle 62 発表ではchrome cast を使います。 終了のリマインドまで⼊れる 27 / 32
deploy firebase 01 const { execSync } = require('child_process'); 02
const Octokit = require('@octokit/rest'); 03 04 const octokit = new Octokit(); 05 const logger = console; 06 07 const owner = 'shinjuku-mokumoku'; 08 const repo = 'shinjuku-mokumoku'; 09 10 const pullRequestNum = async () => { 11 if (process.env.CIRCLE_PR_NUMBER) { 12 return process.env.CIRCLE_PR_NUMBER; 13 } 14 28 / 32
deploy firebase 27 const hasFunctionDiff = async () => {
28 const num = await pullRequestNum(); 29 logger.debug(`Pull Request is https://github.com/shinju 30 31 const res = await octokit.pulls.listFiles({ owner, repo 32 const functionFileNames = res.data.map(files => files.f 33 logger.debug(`Change Files: ${functionFileNames.join(', 34 35 return functionFileNames.length > 0; 36 }; p [ ] ; 25 }; 26 37 38 const deploy = async () => { 変更があるときだけfirebase functions にdeploy されるようにしておき 28 / 32
build and deploy 01 # Javascript Node CircleCI 2.0 configuration
file 02 # 03 # Check https://circleci.com/docs/2.0/language-javascript 04 # 05 version: 2.1 06 07 orbs: 08 puppeteer: threetreeslight/
[email protected]
09 10 jobs: 11 build: 12 docker: 13 - image: circleci/node:8 14 steps: 29 / 32
build and deploy 61 - run: 62 name: Deploy Master
to Firebase 63 command: npm --prefix functions run deploy g 55 - functions_npm_modules- 56 - run: npm --prefix functions install 57 - save_cache: 58 paths: 59 - functions/node_modules 60 key: functions_npm_modules-{{ checksum "functio 64 65 event: 66 docker: 67 - image: circleci/node:8 68 steps: build & deploy していく 29 / 32
訪れる幸せ 30 / 32
ちなみに community で使うfirebase の登録クレカ、どうす るか悩みますよね 31 / 32
まとめ 誰やるにならないように常に⾃動化 puppeteer の可能性無限⼤ slack のslash command の可能性無限⼤ CI にまかせていく
sheduled workflow 便利! orb 便利! network latency には気をつける 時間⾒つけてイベント⾃動作成が外でもできるよう package 化していこう 32 / 32