~repos /website
git clone https://pyrossh.dev/repos/website.git
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
94a7e7c3
—
pyrossh 1 year ago
cleanup
- cv.png +0 -0
- islands/LemonDrop.tsx +2 -105
- routes/index.tsx +8 -20
- routes/posts.tsx +83 -0
- static/posts/eyecandy-golang-error-reporting.md +91 -0
- static/posts/gopibot-to-the-rescue.md +139 -0
- static/styles.css +97 -0
cv.png
ADDED
|
Binary file
|
islands/LemonDrop.tsx
CHANGED
|
@@ -1,81 +1,14 @@
|
|
|
1
1
|
import { useEffect, useRef } from "preact/hooks";
|
|
2
2
|
import { useSignal } from "@preact/signals";
|
|
3
3
|
|
|
4
|
-
export interface Spring {
|
|
5
|
-
p: number;
|
|
6
|
-
v: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class WaveTank {
|
|
10
|
-
springs = [] as Spring[];
|
|
11
|
-
waveLength = 100;
|
|
12
|
-
k = 0.02;
|
|
13
|
-
damping = 0.02;
|
|
14
|
-
spread = 0.02;
|
|
15
|
-
|
|
16
|
-
constructor() {
|
|
17
|
-
for (let i = 0; i < this.waveLength; i++) {
|
|
18
|
-
this.springs[i] = {
|
|
19
|
-
p: 0,
|
|
20
|
-
v: 0,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
update(springs: Spring[]) {
|
|
26
|
-
for (const i of springs) {
|
|
27
|
-
const a = -this.k * i.p - this.damping * i.v;
|
|
28
|
-
i.p += i.v;
|
|
29
|
-
i.v += a;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const leftDeltas = [];
|
|
33
|
-
const rightDeltas = [];
|
|
34
|
-
|
|
35
|
-
for (let t = 0; t < 8; t++) {
|
|
36
|
-
for (let i = 0; i < springs.length; i++) {
|
|
37
|
-
const prev = springs[(i - 1 + springs.length) % springs.length];
|
|
38
|
-
const next = springs[(i + 1) % springs.length];
|
|
39
|
-
|
|
40
|
-
leftDeltas[i] = this.spread * (springs[i].p - prev.p);
|
|
41
|
-
rightDeltas[i] = this.spread * (springs[i].p - next.p);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (let i = 0; i < springs.length; i++) {
|
|
45
|
-
const prev = springs[(i - 1 + springs.length) % springs.length];
|
|
46
|
-
const next = springs[(i + 1) % springs.length];
|
|
47
|
-
prev.v += leftDeltas[i];
|
|
48
|
-
next.v += rightDeltas[i];
|
|
49
|
-
prev.p += leftDeltas[i];
|
|
50
|
-
next.p += rightDeltas[i];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
4
|
function easeInCirc(x: number) {
|
|
57
5
|
return 1 - Math.sqrt(1 - Math.pow(x, 2));
|
|
58
6
|
}
|
|
59
7
|
|
|
60
|
-
const waveTank = new WaveTank();
|
|
61
|
-
|
|
62
8
|
function LemonDrop() {
|
|
63
|
-
const SVG_WIDTH = 100;
|
|
64
9
|
const counter = useSignal(0);
|
|
65
10
|
const dropy = useSignal(60);
|
|
66
|
-
const width = useSignal(SVG_WIDTH);
|
|
67
|
-
const widthRef = useRef(width.value);
|
|
68
|
-
const springs = useSignal(waveTank.springs);
|
|
69
11
|
const requestIdRef = useRef<number>();
|
|
70
|
-
const grid = SVG_WIDTH / waveTank.waveLength;
|
|
71
|
-
const points = [
|
|
72
|
-
[0, 100],
|
|
73
|
-
[0, 0],
|
|
74
|
-
...springs.value.map((x, i) => [i * grid, x.p]),
|
|
75
|
-
[width.value, 0],
|
|
76
|
-
[width.value, 100],
|
|
77
|
-
];
|
|
78
|
-
const springsPath = `${points.map((x) => x.join(",")).join(" ")}`;
|
|
79
12
|
const juice = `M18 ${63 + counter.value} C15 ${63 + counter.value} 16 ${
|
|
80
13
|
63 + counter.value
|
|
81
14
|
} 12 61L9 56C2 33 62 -3 80 12C103 27 44 56 29 58C27 58 25 59 24 61C20 ${
|
|
@@ -93,49 +26,13 @@ function LemonDrop() {
|
|
|
93
26
|
counter.value = easeInCirc(1 - saw) * amp * 0.1;
|
|
94
27
|
dropy.value = 70 + Math.pow(saw - 0.6, 2) * 10000;
|
|
95
28
|
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function update(timestamp: number) {
|
|
99
|
-
updateJuice(timestamp);
|
|
100
|
-
waveTank.update(waveTank.springs);
|
|
101
|
-
springs.value = [...waveTank.springs];
|
|
102
|
-
|
|
103
|
-
const offset = 500;
|
|
104
|
-
const saw = (timestamp + offset) / 2000 -
|
|
105
|
-
Math.floor((timestamp + offset) / 2000);
|
|
106
|
-
if (saw < 0.01) {
|
|
107
|
-
drop();
|
|
108
|
-
}
|
|
109
|
-
requestIdRef.current = globalThis.requestAnimationFrame(
|
|
29
|
+
requestIdRef.current = globalThis.requestAnimationFrame(updateJuice);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function resize() {
|
|
113
|
-
width.value = document.body.clientWidth;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function drop() {
|
|
117
|
-
const dropPosition = Math.round(
|
|
118
|
-
((widthRef.current / 2 - 30) / widthRef.current) * 100,
|
|
119
|
-
);
|
|
120
|
-
waveTank.springs[dropPosition].p = -60;
|
|
121
30
|
}
|
|
122
31
|
|
|
123
32
|
useEffect(() => {
|
|
124
|
-
widthRef.current = width.value;
|
|
125
|
-
}, [width.value]);
|
|
126
|
-
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
129
|
-
if (mediaQuery.matches) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
requestIdRef.current = requestAnimationFrame(
|
|
33
|
+
requestIdRef.current = requestAnimationFrame(updateJuice);
|
|
134
|
-
globalThis.addEventListener("resize", resize);
|
|
135
|
-
resize();
|
|
136
34
|
|
|
137
35
|
return () => {
|
|
138
|
-
globalThis.removeEventListener("resize", resize);
|
|
139
36
|
if (requestIdRef.current !== undefined) {
|
|
140
37
|
cancelAnimationFrame(requestIdRef.current);
|
|
141
38
|
}
|
routes/index.tsx
CHANGED
|
@@ -103,22 +103,10 @@ export default function Home() {
|
|
|
103
103
|
<HardwareIcon size="32" />
|
|
104
104
|
<h2>Hardware</h2>
|
|
105
105
|
</div>
|
|
106
|
-
<ul class="grid gap-2 grid-cols-
|
|
106
|
+
<ul class="grid gap-2 grid-cols-1 text-center mt-4 heir-a:text-blue-900 child:bg-gray-200 child:p-1">
|
|
107
|
-
<li>
|
|
108
|
-
<strong>Laptop:</strong> M2 Macbook Air
|
|
109
|
-
</li>
|
|
110
|
-
<li>
|
|
111
|
-
<strong>CPU:</strong> Apple Silicon M2
|
|
112
|
-
</li>
|
|
113
|
-
<li>
|
|
114
|
-
|
|
107
|
+
<li>M2 Macbook Air</li>
|
|
115
|
-
</li>
|
|
116
|
-
<li>
|
|
117
|
-
<strong>SSD:</strong> 512GB
|
|
118
|
-
</li>
|
|
119
108
|
<li>Raspberry Pi 4B</li>
|
|
120
109
|
<li>Raspberry Pi Zero 2W</li>
|
|
121
|
-
<li>M5Stack</li>
|
|
122
110
|
</ul>
|
|
123
111
|
</section>
|
|
124
112
|
<section>
|
|
@@ -126,7 +114,7 @@ export default function Home() {
|
|
|
126
114
|
<SoftwareIcon size="32" />
|
|
127
115
|
<h2>Software</h2>
|
|
128
116
|
</div>
|
|
129
|
-
<ul class="grid gap-2 grid-cols-3 text-center mt-4
|
|
117
|
+
<ul class="grid gap-2 grid-cols-3 text-center mt-4 heir-a:text-blue-900 child:bg-gray-200 child:p-1">
|
|
130
118
|
<li>
|
|
131
119
|
<a
|
|
132
120
|
href="https://github.com/exelban/stats"
|
|
@@ -142,7 +130,7 @@ export default function Home() {
|
|
|
142
130
|
target="_blank"
|
|
143
131
|
rel="noopener noreferrer"
|
|
144
132
|
>
|
|
145
|
-
Brave
|
|
133
|
+
Brave
|
|
146
134
|
</a>
|
|
147
135
|
</li>
|
|
148
136
|
<li>
|
|
@@ -169,7 +157,7 @@ export default function Home() {
|
|
|
169
157
|
target="_blank"
|
|
170
158
|
rel="noopener noreferrer"
|
|
171
159
|
>
|
|
172
|
-
Fish
|
|
160
|
+
Fish
|
|
173
161
|
</a>
|
|
174
162
|
</li>
|
|
175
163
|
<li>
|
|
@@ -187,7 +175,7 @@ export default function Home() {
|
|
|
187
175
|
target="_blank"
|
|
188
176
|
rel="noopener noreferrer"
|
|
189
177
|
>
|
|
190
|
-
|
|
178
|
+
Slurp
|
|
191
179
|
</a>
|
|
192
180
|
</li>
|
|
193
181
|
<li>
|
|
@@ -206,7 +194,7 @@ export default function Home() {
|
|
|
206
194
|
<TreeIcon size="36" />
|
|
207
195
|
<h2>Interests</h2>
|
|
208
196
|
</div>
|
|
209
|
-
<ul class="grid gap-2 grid-cols-3 text-center mt-4
|
|
197
|
+
<ul class="grid gap-2 grid-cols-3 text-center mt-4 child:bg-slate-100 child:p-1">
|
|
210
198
|
<li>HTML</li>
|
|
211
199
|
<li>CSS</li>
|
|
212
200
|
<li>Tailwind</li>
|
|
@@ -226,7 +214,7 @@ export default function Home() {
|
|
|
226
214
|
<ContactIcon size="36" />
|
|
227
215
|
<h2>Contact</h2>
|
|
228
216
|
</div>
|
|
229
|
-
<ul class="grid gap-2 grid-cols-1 text-lg text-left mt-4
|
|
217
|
+
<ul class="grid gap-2 grid-cols-1 text-lg text-left mt-4 heir-strong:mr-2 child:bg-slate-100 child:p-2">
|
|
230
218
|
<li>
|
|
231
219
|
<strong>Email:</strong>
|
|
232
220
|
<span>pyros2097@gmail.com</span>
|
routes/posts.tsx
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
import { Head } from "$fresh/runtime.ts";
|
|
2
2
|
|
|
3
|
+
// const files = await Astro.glob("./**/*.{md,mdx}");
|
|
4
|
+
// const posts = files.map(({ frontmatter: item }) => ({
|
|
5
|
+
// id: item.title.toLowerCase().replaceAll(" ", "-"),
|
|
6
|
+
// title: item.title,
|
|
7
|
+
// date: new Date(item.date),
|
|
8
|
+
// }));
|
|
9
|
+
// ---
|
|
10
|
+
|
|
11
|
+
// <style>
|
|
12
|
+
// .container {
|
|
13
|
+
// display: flex;
|
|
14
|
+
// flex-direction: column;
|
|
15
|
+
// min-height: calc(100vh - 120px);
|
|
16
|
+
// }
|
|
17
|
+
|
|
18
|
+
// .row {
|
|
19
|
+
// display: flex;
|
|
20
|
+
// flex-direction: row;
|
|
21
|
+
// align-items: center;
|
|
22
|
+
// margin-top: 1.5rem;
|
|
23
|
+
// line-height: 1.5rem;
|
|
24
|
+
|
|
25
|
+
// & span {
|
|
26
|
+
// width: 9rem;
|
|
27
|
+
// }
|
|
28
|
+
|
|
29
|
+
// & a {
|
|
30
|
+
// margin-left: 2rem;
|
|
31
|
+
// text-decoration: none;
|
|
32
|
+
// color: black;
|
|
33
|
+
// border-bottom: 2px solid black;
|
|
34
|
+
|
|
35
|
+
// &:hover,
|
|
36
|
+
// &:visited {
|
|
37
|
+
// text-decoration: none;
|
|
38
|
+
// }
|
|
39
|
+
|
|
40
|
+
// @media (--mobile) {
|
|
41
|
+
// margin-left: 0rem;
|
|
42
|
+
// }
|
|
43
|
+
// }
|
|
44
|
+
// }
|
|
45
|
+
// </style>
|
|
46
|
+
|
|
3
47
|
export default function Posts() {
|
|
4
48
|
return (
|
|
5
49
|
<div class="mx-auto">
|
|
@@ -8,6 +52,45 @@ export default function Posts() {
|
|
|
8
52
|
<meta name="description" content="Peter John's Posts" />
|
|
9
53
|
</Head>
|
|
10
54
|
<div class="px-4 py-40 mx-auto">TBD</div>
|
|
55
|
+
{
|
|
56
|
+
/* <div slot="body" class="container">
|
|
57
|
+
{
|
|
58
|
+
posts.map((post) => (
|
|
59
|
+
<div class="row">
|
|
60
|
+
<span>{post.date.toISOString().split("T")[0]}</span>
|
|
61
|
+
<a href={`/blog/${post.date.getFullYear()}/${post.id}`}>
|
|
62
|
+
{post.title}
|
|
63
|
+
</a>
|
|
64
|
+
</div>
|
|
65
|
+
))
|
|
66
|
+
}
|
|
67
|
+
</div> */
|
|
68
|
+
}
|
|
11
69
|
</div>
|
|
12
70
|
);
|
|
13
71
|
}
|
|
72
|
+
|
|
73
|
+
// ---
|
|
74
|
+
// const { title, description, image, date, tags } = Astro.props.frontmatter;
|
|
75
|
+
// ---
|
|
76
|
+
|
|
77
|
+
// <title slot="head">pyros.sh | {title}</title>
|
|
78
|
+
// <meta slot="head" name="description" content={description} />
|
|
79
|
+
// <meta slot="head" name="keywords" content={tags} />
|
|
80
|
+
// <div slot="body" class="post-page">
|
|
81
|
+
// <div class="title-container">
|
|
82
|
+
// <div>
|
|
83
|
+
// <h1>{title}</h1>
|
|
84
|
+
// <h2>{description}</h2>
|
|
85
|
+
// </div>
|
|
86
|
+
// <div class="date">
|
|
87
|
+
// <h3>{date}</h3>
|
|
88
|
+
// </div>
|
|
89
|
+
// </div>
|
|
90
|
+
// <div class="tags-container">
|
|
91
|
+
// {tags.map((text) => <Tag text={text} />)}
|
|
92
|
+
// </div>
|
|
93
|
+
// <div>
|
|
94
|
+
// <slot />
|
|
95
|
+
// </div>
|
|
96
|
+
// </div>
|
static/posts/eyecandy-golang-error-reporting.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: ../../../layouts/post.astro
|
|
3
|
+
title: Eyecandy golang error reporting
|
|
4
|
+
description: Better error messages logging in golang
|
|
5
|
+
image: ../../../assets/images/terminal1.png
|
|
6
|
+
date: September 17, 2016
|
|
7
|
+
tags:
|
|
8
|
+
- golang
|
|
9
|
+
- error
|
|
10
|
+
- formatting
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
We at playlyfe wanted to get an email report as soon as an error occurred on our production servers. Since golang does not have
|
|
14
|
+
stack traces with its inbuilt error mechanism we had to find a quick and simple solution which wouldn’t require too much refactoring
|
|
15
|
+
of our existing codebase. So this is how we went about accomplishing this task. First we decided to wrap our errors so that we can
|
|
16
|
+
get the runtime stack whenever an error occurs.
|
|
17
|
+
|
|
18
|
+
We first started using this, https://github.com/go-errors/errors
|
|
19
|
+
but soon found out that it wasn’t exactly suited for our use case. So we created this minimalistic and easy approach to wrap all our
|
|
20
|
+
existing errors. First we decided to wrap our errors so that we can get the runtime stack whenever an error occurs.
|
|
21
|
+
|
|
22
|
+
```go
|
|
23
|
+
package utils
|
|
24
|
+
|
|
25
|
+
import (
|
|
26
|
+
"bytes"
|
|
27
|
+
"database/sql"
|
|
28
|
+
"runtime"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
type WrappedError struct {
|
|
32
|
+
Err error
|
|
33
|
+
StackTrace string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func(e * WrappedError) Error() string {
|
|
37
|
+
return e.Err.Error()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func E(e error) error {
|
|
41
|
+
switch e.(type) {
|
|
42
|
+
case *WrappedError:
|
|
43
|
+
return e
|
|
44
|
+
case nil:
|
|
45
|
+
return nil
|
|
46
|
+
default:
|
|
47
|
+
stackTrace := make([]byte , 1 << 16)
|
|
48
|
+
runtime.Stack(stackTrace, false)
|
|
49
|
+
buffer := &bytes.Buffer{}
|
|
50
|
+
for _ , a := range stackTrace {
|
|
51
|
+
if a != 0 {
|
|
52
|
+
buffer.WriteByte(a)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return &WrappedError {
|
|
56
|
+
Err: e,
|
|
57
|
+
StackTrace: buffer.String(),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
But then just sending the error stack in plain format to our emails wasn’t going to be nice to read at all. We needed more
|
|
64
|
+
information like context and session information and things like that. On top of that I also wanted to make our stack traces
|
|
65
|
+
prettier so that it would easier to figure out where the error started from.
|
|
66
|
+
|
|
67
|
+
So to parse the error stack trace I found this cool library which does that for and on top of that it also themes it very well
|
|
68
|
+
https://github.com/maruel/panicparse
|
|
69
|
+
|
|
70
|
+
But then it didn’t properly expose an API to do it properly and after a few dabblings here,
|
|
71
|
+
https://github.com/maruel/panicparse/issues/8
|
|
72
|
+
with the developer and +1’s we got a proper api which I could use.
|
|
73
|
+
And now I haz got a prettier stack traces like this,
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
So great I got ANSI coloring setup and the errors look great in our console but what about our
|
|
78
|
+
mails. Of course this wasn’t going to work since emails primarily render text and HTML only, and
|
|
79
|
+
ANSI color codes was going to make our messages a mess.
|
|
80
|
+
|
|
81
|
+
So then I went about digging github for an ANSI terminal codes to HTML converter so that it would
|
|
82
|
+
look exactly like this in my mail. And then I found this cool go library which does that,
|
|
83
|
+
https://github.com/buildkite/terminal
|
|
84
|
+
|
|
85
|
+
Now all emails require inline CSS or else they wouldn’t work so then I had to find out a way to do that too.
|
|
86
|
+
And this was it,
|
|
87
|
+
https://github.com/aymerick/douceur
|
|
88
|
+
|
|
89
|
+
Finally after messing around with so many libraries I got around to getting it to work and this is how it looks in my email,
|
|
90
|
+
|
|
91
|
+

|
static/posts/gopibot-to-the-rescue.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: ../../../layouts/post.astro
|
|
3
|
+
title: Gopibot to the rescue
|
|
4
|
+
description: A slackbot for deploying your applications (chatops)
|
|
5
|
+
image: ../../../assets/images/gopibot.png
|
|
6
|
+
date: October 04, 2017
|
|
7
|
+
tags:
|
|
8
|
+
- nodejs
|
|
9
|
+
- slack
|
|
10
|
+
- bot
|
|
11
|
+
- chatops
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
High Ho Gopibot away!
|
|
15
|
+
|
|
16
|
+
Everybody please meet Gopibot our chatops bot which I built at Numberz to help us deploy our countless microservices to QA.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
So here is the backstory,
|
|
21
|
+
I was one of the developers who had access to our QA and Prod servers and the other person was the Head of Engineering and he is generally a busy guy.
|
|
22
|
+
So whenever there is a change that needs to be deployed everyone comes to me and tells me to deploy their microservice/frontend to the QA and blatantly
|
|
23
|
+
interrupts my awesome coding cycle.
|
|
24
|
+
|
|
25
|
+
Alright then, I break off from my flow, ssh into the server and start running the deploy command. And all of you jsdev wannabes who have worked with
|
|
26
|
+
react and webpack will know the horrors about deploying frontend code right. It takes forever so I have to wait there looking at the console along with
|
|
27
|
+
the dev who wanted me to deploy it (lets call him kokill for now). So kokill and I patiently wait for the webpack build to finish. 1m , 2m, 3m and WTH
|
|
28
|
+
15m. And then its built and the new frontend is deployed to QA. YES! Now I can continue with my work. But wait then some other dev comes likes call him
|
|
29
|
+
(D-Ne0) and he asks to deploy something else and again the same process of ssh’ing the server and another wait. This got repetitive and irritating. Then
|
|
30
|
+
I started searching for solutions to the problem and looked high and low and thought that CI/CD is the only thing that can solve this problem. But then
|
|
31
|
+
I saw something new called ChatOps where developers have chatbots to talk to automate this manual work. Just like we have bots these days to help you
|
|
32
|
+
out in your work like getting your laundry, grocery and making orders.
|
|
33
|
+
|
|
34
|
+
So I decided to take a shot at this in my free time. And it seems it was simpler than I thought and decided to use Slack our primary team communication
|
|
35
|
+
platform. We used it daily for everything and I thought why not have a specific channel just where the bot resides and people could talk to the bot.
|
|
36
|
+
|
|
37
|
+
Since we are typically a nodejs shop I decided to find a way to send messages to a slack bot. And slack has this really great sdk for nodejs.
|
|
38
|
+
https://github.com/slackapi/node-slack-sdk
|
|
39
|
+
First I went and created the bot in my slack team settings. And then wrote a script which would allow it to read messages from the channel it was added.
|
|
40
|
+
|
|
41
|
+
Here is the simple script,
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const RtmClient = require("@slack/client").RtmClient;
|
|
45
|
+
const RTM_EVENTS = require("@slack/client").RTM_EVENTS;
|
|
46
|
+
const CLIENT_EVENTS = require("@slack/client").CLIENT_EVENTS;
|
|
47
|
+
const bot_token = process.env.SLACK_BOT_TOKEN;
|
|
48
|
+
|
|
49
|
+
const rtm = new RtmClient(bot_token);
|
|
50
|
+
const COMMANDS = {
|
|
51
|
+
web: "ssh -i qa.pem user@url docker pull image-name && docker rm -f container-id && docker run -d image-name",
|
|
52
|
+
};
|
|
53
|
+
let deploymentInProgress = false;
|
|
54
|
+
let counter = 0;
|
|
55
|
+
|
|
56
|
+
rtm.on(RTM_EVENTS.MESSAGE, (event) => {
|
|
57
|
+
console.log("Got event", event);
|
|
58
|
+
if (
|
|
59
|
+
(event.subtype === "message_changed" || event.subtype === "message_deleted") &&
|
|
60
|
+
event.text &&
|
|
61
|
+
event.text.indexOf("$slackBotId") > -1
|
|
62
|
+
) {
|
|
63
|
+
return rtm.sendMessage("Please dont change the message and expect me to correct your past mistakes", event.channel);
|
|
64
|
+
}
|
|
65
|
+
if (event.subtype) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (event.type === "message" && event.text && event.text.indexOf("$slackBotId") > -1) {
|
|
69
|
+
if (deploymentInProgress === true) {
|
|
70
|
+
counter = counter + 1;
|
|
71
|
+
if (counter > 3) {
|
|
72
|
+
counter = 0;
|
|
73
|
+
return rtm.sendMessage("Stop bugging me noob or I'll tell to raise you bugs", event.channel);
|
|
74
|
+
}
|
|
75
|
+
return rtm.sendMessage("I am already processing a deploy request please wait", event.channel);
|
|
76
|
+
}
|
|
77
|
+
var input = event.text.trim().replace("$slackBotId ", "");
|
|
78
|
+
console.log("Got input", input);
|
|
79
|
+
const arr = input.split(" ");
|
|
80
|
+
arr.forEach((word) => {
|
|
81
|
+
word = word.replace(/\s/g, "");
|
|
82
|
+
});
|
|
83
|
+
const currCommand = arr[0];
|
|
84
|
+
if (COMMANDS.indexOf(currCommand) > -1) {
|
|
85
|
+
rtm.sendMessage("Starting to deploy ${currCommand}", event.channel);
|
|
86
|
+
const ssh = spawn(COMMANDS[currCommand]);
|
|
87
|
+
deploymentInProgress = true;
|
|
88
|
+
ssh.stdout.on("data", (data) => {
|
|
89
|
+
rtm.sendMessage(data, event.channel);
|
|
90
|
+
});
|
|
91
|
+
ssh.stderr.on("data", (data) => {
|
|
92
|
+
rtm.sendMessage(data, event.channel);
|
|
93
|
+
});
|
|
94
|
+
ssh.on("close", (code) => {
|
|
95
|
+
deploymentInProgress = false;
|
|
96
|
+
if (code === 0) {
|
|
97
|
+
console.log("Deployed Successfully", currCommand);
|
|
98
|
+
rtm.sendMessage("Deployed Successfully " + currCommand, event.channel);
|
|
99
|
+
} else {
|
|
100
|
+
console.log("child process exited with code ", code);
|
|
101
|
+
rtm.sendMessage("child process exited with code " + code);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
} else {
|
|
106
|
+
counter = counter + 1;
|
|
107
|
+
if (counter > 3) {
|
|
108
|
+
counter = 0;
|
|
109
|
+
return rtm.sendMessage("Stop bugging me noob or I'll tell <@U30TXGLS1|gopi> to raise you bugs", event.channel);
|
|
110
|
+
}
|
|
111
|
+
return rtm.sendMessage(
|
|
112
|
+
`command '${event.text} ' not found.You need to specify one of these commands [${COMMANDS.map((v, k) => k).join(
|
|
113
|
+
","
|
|
114
|
+
)} ]`,
|
|
115
|
+
event.channel
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
console.log("Starting deploybot");
|
|
121
|
+
rtm.start();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
So what the bot does is when someone mentions the bot with a command to run. It first checks if the command is defined in our COMMANDS map and then if
|
|
125
|
+
it is, it executes the corresponding shell command for it on our QA server and then gives back progress/error/finished messages back to the channel so
|
|
126
|
+
that everyone will be notified that someone had done a deployment. This is how it looks like,
|
|
127
|
+
|
|
128
|
+

|
|
129
|
+
|
|
130
|
+
Anyways to just have a boring bot that just runs boring commands was kinda boring. I thought of spicing up the bot interaction by making it say weird
|
|
131
|
+
things if you keep giving it invalid commands. Making it more of a life like bot.
|
|
132
|
+
|
|
133
|
+
Initially the bot was called deploybot and had a rocket icon but then there was our QA/Bug creator/Hell Raiser/Injoker in our team so I thought creating
|
|
134
|
+
a mini him would be better and give the bot a real person’s personality and it worked and people kind a started talking to bot some random stuff and
|
|
135
|
+
all.
|
|
136
|
+
|
|
137
|
+
Further on we can maybe introduce natural language processing and deep learning to make the bot learn from our messages and not just take a single
|
|
138
|
+
command. Like instead of me saying @gopibot cfm I can say @gopibot please deploy our cashflow server or please revert the deployment to the previous
|
|
139
|
+
version and things like that.
|
static/styles.css
CHANGED
|
@@ -5,4 +5,101 @@
|
|
|
5
5
|
@page {
|
|
6
6
|
size: A4;
|
|
7
7
|
margin: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.post-page {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
width: 100%;
|
|
14
|
+
|
|
15
|
+
& code {
|
|
16
|
+
font-family: Menlo, Monaco, Courier New, monospace;
|
|
17
|
+
font-size: 0.9em;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
& p {
|
|
21
|
+
margin-top: 1rem;
|
|
22
|
+
margin-bottom: 1rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
& pre {
|
|
26
|
+
max-width: 64rem;
|
|
27
|
+
font-family: monospace;
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
border-radius: 16px;
|
|
30
|
+
padding: 16px;
|
|
31
|
+
margin: 8px;
|
|
32
|
+
line-height: 20px;
|
|
33
|
+
overflow-x: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
& img {
|
|
37
|
+
width: auto;
|
|
38
|
+
@media (--mobile) {
|
|
39
|
+
width: 100%;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
& .title-container {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex: 1;
|
|
46
|
+
font-family: serif;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
|
|
49
|
+
& h1 {
|
|
50
|
+
color: var(--black-light);
|
|
51
|
+
margin: 0;
|
|
52
|
+
line-height: 3rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
& h2 {
|
|
56
|
+
color: var(--yellow-dark);
|
|
57
|
+
font-size: 1.5rem;
|
|
58
|
+
font-weight: 500;
|
|
59
|
+
margin-top: 20px;
|
|
60
|
+
margin-bottom: 20px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
& h3 {
|
|
64
|
+
color: var(--black-light);
|
|
65
|
+
font-size: 1.5rem;
|
|
66
|
+
font-weight: 500;
|
|
67
|
+
margin: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
& .date {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex: 1;
|
|
73
|
+
flex-direction: row;
|
|
74
|
+
justify-content: flex-end;
|
|
75
|
+
margin-right: var(--space-10);
|
|
76
|
+
|
|
77
|
+
@media (--mobile) {
|
|
78
|
+
justify-content: flex-start;
|
|
79
|
+
margin-top: 0.5rem;
|
|
80
|
+
margin-bottom: 0.5rem;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
& .tags-container {
|
|
86
|
+
margin-top: var(--space-1);
|
|
87
|
+
margin-bottom: var(--space-1);
|
|
88
|
+
|
|
89
|
+
@media (--mobile) {
|
|
90
|
+
margin-top: var(--space-4);
|
|
91
|
+
margin-bottom: var(--space-4);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
.tag {
|
|
98
|
+
background-color: var(--black-light);
|
|
99
|
+
color: white;
|
|
100
|
+
display: inline-block;
|
|
101
|
+
padding-left: 8px;
|
|
102
|
+
padding-right: 8px;
|
|
103
|
+
text-align: center;
|
|
104
|
+
margin-right: 1rem;
|
|
8
105
|
}
|