Skip to content

Commit a8da795

Browse files
committed
feat(slack): add slack command
1 parent 82bafc4 commit a8da795

File tree

13 files changed

+479
-15
lines changed

13 files changed

+479
-15
lines changed

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ gitops operations i.e. update a helm value
77

88
## Usage
99

10+
Within the `~/.gitops-commit/manifest.yaml`
11+
12+
```yaml
13+
repositories:
14+
# Example of what the fields map to
15+
# https://github.com/gsdevme/test/blob/master/deployments/foo/values.yaml
16+
# https://github.com/gsdevme/test
17+
- name: my-app-test
18+
repository: gsdevme/test
19+
file: deployments/test/values.yaml
20+
notation: image.tag
21+
- name: my-app-test
22+
repository: gsdevme/test
23+
file: deployments/prod/values.yaml
24+
notation: image.tag
25+
branch: master
26+
```
27+
28+
---
29+
1030
```bash
1131
$: gitops-commit -h
1232
Usage:
@@ -31,4 +51,5 @@ Flags:
3151

3252
- Github only
3353
- YAML only
34-
- Semver tag only
54+
- Semver tag only
55+
- Passwordless keys only

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.17
44

55
require (
66
github.com/go-git/go-git/v5 v5.4.2
7+
github.com/gorilla/mux v1.8.0
8+
github.com/slack-go/slack v0.9.5
79
github.com/spf13/cobra v1.2.1
810
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
911
)
@@ -15,11 +17,13 @@ require (
1517
github.com/emirpasic/gods v1.12.0 // indirect
1618
github.com/go-git/gcfg v1.5.0 // indirect
1719
github.com/go-git/go-billy/v5 v5.3.1 // indirect
20+
github.com/gorilla/websocket v1.4.2 // indirect
1821
github.com/imdario/mergo v0.3.12 // indirect
1922
github.com/inconshreveable/mousetrap v1.0.0 // indirect
2023
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
2124
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
2225
github.com/mitchellh/go-homedir v1.1.0 // indirect
26+
github.com/pkg/errors v0.9.1 // indirect
2327
github.com/sergi/go-diff v1.1.0 // indirect
2428
github.com/spf13/pflag v1.0.5 // indirect
2529
github.com/xanzy/ssh-agent v0.3.0 // indirect

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti
9898
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
9999
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
100100
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
101+
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
102+
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
101103
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
102104
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
103105
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -163,6 +165,10 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
163165
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
164166
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
165167
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
168+
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
169+
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
170+
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
171+
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
166172
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
167173
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
168174
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@@ -232,6 +238,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
232238
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
233239
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
234240
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
241+
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
235242
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
236243
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
237244
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -249,6 +256,8 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
249256
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
250257
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
251258
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
259+
github.com/slack-go/slack v0.9.5 h1:j7uOUDowybWf9eSgZg/AbGx6J1OPJB6SE8Z5dNl6Mtw=
260+
github.com/slack-go/slack v0.9.5/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ=
252261
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
253262
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
254263
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=

internal/app/gitops-commit/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func NewRootCommand() *cobra.Command {
1212
}
1313

1414
c.AddCommand(newRunCommand())
15+
c.AddCommand(newServeCommand())
1516

1617
return &c
1718
}

internal/app/gitops-commit/cmd/run.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ func newRunCommand() *cobra.Command {
1919
repo := strings.TrimRight(cmd.Flag("repo").Value.String(), "/")
2020
file := strings.TrimLeft(cmd.Flag("file").Value.String(), "/")
2121

22-
options, c, err := gitops.NewGitOptions(key)
22+
keys, err := gitops.GetPasswordlessKey(key)
23+
24+
if err != nil {
25+
return err
26+
}
27+
28+
options, c, err := gitops.NewGitOptions(keys)
2329

2430
if len(email) > 0 {
2531
options.Email = email
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"github.com/gsdevme/gitops-commit/internal/app/gitops-commit/slackhttp"
6+
"github.com/gsdevme/gitops-commit/internal/pkg/gitops"
7+
"github.com/spf13/cobra"
8+
"net"
9+
"net/http"
10+
"os"
11+
"strconv"
12+
"time"
13+
)
14+
15+
func newServeCommand() *cobra.Command {
16+
c := cobra.Command{
17+
Use: "serve",
18+
RunE: func(cmd *cobra.Command, args []string) error {
19+
port := cmd.Flag("port").Value.String()
20+
key := cmd.Flag("key").Value.String()
21+
file := cmd.Flag("manifest").Value.String()
22+
23+
keys, err := gitops.GetPasswordlessKey(key)
24+
25+
if err != nil {
26+
return err
27+
}
28+
29+
manifest, err := slackhttp.LoadManifest(file)
30+
if err != nil {
31+
return err
32+
}
33+
34+
r := manifest.GetRegistry()
35+
36+
s := slackhttp.NewSlackCommandServer(*r, keys)
37+
server := &http.Server{
38+
Addr: fmt.Sprintf(":%s", port),
39+
Handler: s,
40+
ReadTimeout: 3 * time.Second,
41+
WriteTimeout: 5 * time.Second,
42+
ConnState: func(conn net.Conn, state http.ConnState) {
43+
//fmt.Fprintf(os.Stdout, "%s - %s\n", conn.RemoteAddr(), state.String())
44+
},
45+
}
46+
47+
fmt.Fprintf(os.Stdout, "Listening on http://127.0.0.1:%s", port)
48+
49+
defer server.Close()
50+
51+
return server.ListenAndServe()
52+
},
53+
}
54+
55+
p := 8080
56+
57+
if len(os.Getenv("PORT")) > 0 {
58+
envPort, err := strconv.Atoi(os.Getenv("PORT"))
59+
60+
if err != nil {
61+
p = envPort
62+
}
63+
}
64+
65+
c.Flags().Int("port", p, "The web server port to listen on")
66+
c.Flags().String("key", fmt.Sprintf("%s/.ssh/id_rsa", os.Getenv("HOME")), "Absolute path to the private key")
67+
c.Flags().String("manifest", fmt.Sprintf("%s/.gitops-commit/manifest.yaml", os.Getenv("HOME")), "Absolute path to the manifest")
68+
69+
return &c
70+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package slackhttp
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/gsdevme/gitops-commit/internal/pkg/gitops"
7+
"github.com/slack-go/slack"
8+
"net/http"
9+
"strings"
10+
)
11+
12+
func (s *server) handleSlackCommand(registry *NamedRepositoryRegistry) func(http.ResponseWriter, slack.SlashCommand) {
13+
return func(w http.ResponseWriter, sl slack.SlashCommand) {
14+
text := strings.Split(sl.Text, " ")
15+
16+
if len(text) < 3 || len(text) > 3 {
17+
sendEphemeralMsg("Incorrect usage, expected /gitops-commit [command] [name] [tag]", w)
18+
19+
return
20+
}
21+
22+
switch text[0] {
23+
case "deploy":
24+
deploy(s, w, registry, text[1], text[2])
25+
26+
return
27+
}
28+
}
29+
}
30+
31+
func deploy(s *server, w http.ResponseWriter, registry *NamedRepositoryRegistry, name string, version string) {
32+
33+
r, err := registry.findNamedRepository(name)
34+
35+
if err != nil {
36+
sendEphemeralMsg(fmt.Sprintf("Unknown named repository, cannot handle \"%s\", availabe options (%s)", name, registry.getNamesFlattened()), w)
37+
38+
return
39+
}
40+
41+
if len(version) != 7 {
42+
sendEphemeralMsg(fmt.Sprintf("version does not look semver? %s", version), w)
43+
44+
return
45+
}
46+
47+
options, f, err := gitops.NewGitOptions(s.keys)
48+
if err != nil {
49+
return
50+
}
51+
52+
defer f()
53+
54+
command := gitops.DeployVersionCommand{
55+
GitOptions: *options,
56+
Repository: r.Repository,
57+
Notation: r.Notation,
58+
File: r.File,
59+
Version: version,
60+
}
61+
62+
go func() {
63+
err = gitops.DeployVersionHandler(command)
64+
if err != nil {
65+
sendEphemeralMsg(fmt.Sprintf("failed to deploy: %s", err), w)
66+
67+
return
68+
}
69+
}()
70+
71+
params := &slack.Msg{
72+
Text: fmt.Sprintf(":alert: Deploying tag `%s` to `%s`:`%s`", version, r.Repository, r.File),
73+
ResponseType: slack.ResponseTypeInChannel,
74+
}
75+
b, err := json.Marshal(params)
76+
if err != nil {
77+
w.WriteHeader(http.StatusInternalServerError)
78+
return
79+
}
80+
w.Header().Set("Content-Type", "application/json")
81+
_, err = w.Write(b)
82+
83+
if err != nil {
84+
return
85+
}
86+
}
87+
88+
func sendEphemeralMsg(m string, w http.ResponseWriter) {
89+
b, err := json.Marshal(slack.Msg{
90+
Text: m,
91+
ResponseType: slack.ResponseTypeEphemeral,
92+
})
93+
if err != nil {
94+
fmt.Println(err)
95+
w.WriteHeader(http.StatusInternalServerError)
96+
return
97+
}
98+
99+
w.Header().Set("Content-Type", "application/json")
100+
101+
_, err = w.Write(b)
102+
if err != nil {
103+
return
104+
}
105+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package slackhttp
2+
3+
import (
4+
"fmt"
5+
"gopkg.in/yaml.v3"
6+
"io/ioutil"
7+
)
8+
9+
type Manifest struct {
10+
registry *NamedRepositoryRegistry
11+
}
12+
13+
type manifestYaml struct {
14+
Repositories []struct {
15+
Name string `yaml:"name"`
16+
File string `yaml:"file"`
17+
Notation string `yaml:"notation"`
18+
Repository string `yaml:"repository"`
19+
Branch string `yaml:"branch,omitempty"`
20+
} `yaml:"repositories"`
21+
}
22+
23+
func (m *Manifest) GetRegistry() *NamedRepositoryRegistry {
24+
return m.registry
25+
}
26+
27+
func LoadManifest(f string) (*Manifest, error) {
28+
m := Manifest{
29+
registry: NewNamedRepositoryRegistry(),
30+
}
31+
32+
d, err := ioutil.ReadFile(f)
33+
34+
if err != nil {
35+
return nil, fmt.Errorf("cannot read yaml file: %w", err)
36+
}
37+
38+
var manifest manifestYaml
39+
40+
err = yaml.Unmarshal(d, &manifest)
41+
42+
if err != nil {
43+
return nil, fmt.Errorf("invalid yaml file: %w", err)
44+
}
45+
46+
var branch string
47+
48+
for _, r := range manifest.Repositories {
49+
branch = "master"
50+
51+
if len(r.Branch) > 0 {
52+
branch = r.Branch
53+
}
54+
55+
m.registry.Add(r.Name, r.Repository, r.File, r.Notation, branch)
56+
}
57+
58+
return &m, nil
59+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package slackhttp
2+
3+
import (
4+
"github.com/slack-go/slack"
5+
"io"
6+
"io/ioutil"
7+
"net/http"
8+
)
9+
10+
func (s *server) SlackCommandMiddleware(next func(w http.ResponseWriter, s slack.SlashCommand)) http.HandlerFunc {
11+
return func(w http.ResponseWriter, r *http.Request) {
12+
verifier, err := slack.NewSecretsVerifier(r.Header, s.slackSecret)
13+
if err != nil {
14+
w.WriteHeader(http.StatusInternalServerError)
15+
16+
return
17+
}
18+
19+
r.Body = ioutil.NopCloser(io.TeeReader(r.Body, &verifier))
20+
s, err := slack.SlashCommandParse(r)
21+
if err != nil {
22+
w.WriteHeader(http.StatusInternalServerError)
23+
24+
return
25+
}
26+
27+
if err = verifier.Ensure(); err != nil {
28+
w.WriteHeader(http.StatusUnauthorized)
29+
30+
return
31+
}
32+
33+
next(w, s)
34+
}
35+
}

0 commit comments

Comments
 (0)