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
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
threetreeslight
August 23, 2019
Technology
68
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
CiecleCIでもくもく会を支える技術
2019-08-23-CiecleCIでもくもく会を支える技術
threetreeslight
August 23, 2019
More Decks by threetreeslight
See All by threetreeslight
実録 採用一投入魂
threetreeslight
0
38
Bottleneck is You
threetreeslight
0
130
Japan Office Society オフィスはスタートアップの成長を助長するのか?阻害するのか?
threetreeslight
0
130
スタートアップは見極められたくない
threetreeslight
0
57
VPoEの責務とは
threetreeslight
0
92
Ego vs higher self
threetreeslight
0
51
Performance Hack 101
threetreeslight
0
120
複数のスタートアップを 通して得た失敗と学び
threetreeslight
0
89
How to probe prometheus & grafana. What is helm
threetreeslight
0
53
Other Decks in Technology
See All in Technology
不要なレビューをAIにまかせて AIコーディングの環境改善を加速した
shoota
1
240
白金鉱業Meetup_Vol.24_「AIエージェントは分けるほど良い」は本当か? / Is it true that “the more you divide AI agents, the better”?
brainpadpr
1
420
SONiCのLinuxベースを活かしたZabbix監視
sonic
0
260
エラーバジェットのアラートのタイミングを考える.pdf
kairim0
0
180
徹底討論!ECS vs EKS!
daitak
3
1.1k
あなたの知らないPDFのアクセシビリティ
lycorptech_jp
PRO
0
220
iAEONの段階的リアーキテクト戦略 / iAEON's_Gradual_Re-architecture_Strategy
aeonpeople
0
230
LayerX コーポレートエンジニアリング室におけるサプライチェーンセキュリティへの取り組み / Supply Chain Security at LayerX Corporate Engineering
yuyatakeyama
2
730
手塩にかけりゃいいってもんじゃない
ming_ayami
0
620
Oracle AI Database@AWS:サービス概要のご紹介
oracle4engineer
PRO
4
3k
[チョークトーク資料]AWS DevOps Agent を使いこなす / AWS Dev Ops Agent Chalk Talk AWS Summit Japan 2026
kinunori
3
670
攻撃者視点で考えるDetection Engineering
cryptopeg
3
2k
Featured
See All Featured
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
290
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
210
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
34
2.8k
Making Projects Easy
brettharned
120
6.7k
Fireside Chat
paigeccino
42
4k
Building an army of robots
kneath
306
46k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.5k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
1.8k
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