-
-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
85 changed files
with
15,593 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package cmd | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"os/exec" | ||
|
||
log "github.com/go-pkgz/lgr" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type Executor interface { | ||
Do(cmd string) error | ||
Run(cmd string, params ...interface{}) | ||
} | ||
|
||
// LastShow get the number of latest published podcast via site-api | ||
// GET /last/{posts}?categories=podcast | ||
func LastShow(client http.Client, siteAPI string) (int, error) { | ||
resp, err := client.Get(fmt.Sprintf("%s/last/1?categories=podcast", siteAPI)) | ||
if err != nil { | ||
return -1, errors.Wrap(err, "can't get last shows") | ||
} | ||
defer resp.Body.Close() | ||
if resp.StatusCode != http.StatusOK { | ||
return -1, errors.Errorf("invalid status code %s", resp.Status) | ||
} | ||
|
||
//noinspection GoPreferNilSlice | ||
showInfo := []struct { | ||
Num int `json:"show_num"` | ||
}{} | ||
|
||
if err = json.NewDecoder(resp.Body).Decode(&showInfo); err != nil { | ||
return -1, errors.Wrap(err, "can't read and decode") | ||
} | ||
|
||
if len(showInfo) < 1 { | ||
return -1, errors.New("list of podcasts is empty") | ||
} | ||
|
||
return showInfo[0].Num, nil | ||
} | ||
|
||
// ShellExecutor is a simple wrapper to execute command within shell | ||
type ShellExecutor struct { | ||
Dry bool | ||
} | ||
|
||
// Do executes command and returns error if failed | ||
func (c *ShellExecutor) Do(cmd string) error { | ||
log.Printf("[DEBUG] execute %q", cmd) | ||
if c.Dry { | ||
return nil | ||
} | ||
ex := exec.Command("sh", "-c", cmd) | ||
ex.Stdout = log.ToWriter(log.Default(), "INFO") | ||
ex.Stderr = log.ToWriter(log.Default(), "WARN") | ||
return errors.Wrapf(ex.Run(), "failed to run %q", cmd) | ||
} | ||
|
||
// Run makes the final command in printf style and panic on error | ||
func (c *ShellExecutor) Run(cmd string, params ...interface{}) { | ||
command := fmt.Sprintf(cmd, params...) | ||
if err := c.Do(command); err != nil { | ||
log.Fatalf("[ERROR] %v", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package cmd | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestCmd_LastShow(t *testing.T) { | ||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
require.Equal(t, "/last/1?categories=podcast", r.URL.String()) | ||
w.Write([]byte(`[{"show_num": 683}]`)) | ||
})) | ||
defer ts.Close() | ||
|
||
res, err := LastShow(http.Client{Timeout: 10 * time.Millisecond}, ts.URL) | ||
require.NoError(t, err) | ||
assert.Equal(t, 683, res) | ||
} | ||
|
||
func TestCmd_LastShowFailed(t *testing.T) { | ||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
require.Equal(t, "/last/1?categories=podcast", r.URL.String()) | ||
w.Write([]byte(`[]`)) | ||
})) | ||
defer ts.Close() | ||
|
||
_, err := LastShow(http.Client{Timeout: 10 * time.Millisecond}, "http://127.0.0.2:9999/xyz") | ||
require.Error(t, err) | ||
assert.Contains(t, err.Error(), "can't get last shows") | ||
} | ||
|
||
func TestShellExecutor_Do(t *testing.T) { | ||
c := ShellExecutor{} | ||
err := c.Do("ls -la") | ||
assert.NoError(t, err) | ||
|
||
err = c.Do("ls -la && pwd") | ||
assert.NoError(t, err) | ||
|
||
err = c.Do("lxxxxxxs -la") | ||
assert.Error(t, err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
log "github.com/go-pkgz/lgr" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// Deploy delivers site update | ||
type Deploy struct { | ||
Executor Executor | ||
NewsPasswd string | ||
NewsAPI string | ||
NewsDuration time.Duration | ||
Client http.Client | ||
Dry bool | ||
} | ||
|
||
// Do run deploy sequence for the given episodeNum | ||
// may panic on executor error | ||
func (d *Deploy) Do(episodeNum int) error { | ||
log.Printf("[INFO] commit new episode to git") | ||
d.Executor.Run("git pull && git commit -am episode %d && git push", episodeNum) | ||
|
||
log.Printf("[INFO] remote site update") | ||
d.Executor.Run(`ssh [email protected] "cd /srv/site.hugo && git pull && docker-compose run --rm hugo"`) | ||
|
||
log.Printf("[INFO] create chat log") | ||
d.Executor.Run(`ssh [email protected] "docker exec -i super-bot /srv/telegram-rt-bot --super=umputun --super=bobuk --super=ksenks --super=grayru --dbg --export-num=%d --export-path=/srv/html"`, episodeNum) | ||
|
||
log.Printf("[INFO] archive news") | ||
err := d.archiveNews() | ||
return err | ||
} | ||
|
||
func (d *Deploy) archiveNews() error { | ||
req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/active/last/%d", d.NewsAPI, int(d.NewsDuration.Hours())), nil) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to prepare news archive request") | ||
} | ||
if d.Dry { | ||
log.Printf("[INFO] %s", req.URL.String()) | ||
return nil | ||
} | ||
|
||
req.SetBasicAuth("admin", d.NewsPasswd) | ||
resp, err := d.Client.Do(req) | ||
if err != nil { | ||
return errors.Wrap(err, "can't make news archive request") | ||
} | ||
defer resp.Body.Close() | ||
if resp.StatusCode != http.StatusOK { | ||
return errors.Wrapf(err, "news archive request returned %s", resp.Status) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package cmd | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"text/template" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
var newShowTmpl = `+++ | ||
title = "Радио-Т {{.EpisodeNum}}" | ||
date = {{.TS.Format "2006-01-02T15:04:05"}} | ||
categories = ["podcast"] | ||
image = "https://radio-t.com/images/radio-t/rt{{.EpisodeNum}}.jpg" | ||
filename = "rt_podcast{{.EpisodeNum}}" | ||
+++ | ||
![](https://radio-t.com/images/radio-t/rt{{.EpisodeNum}}.jpg) | ||
{{.News}} | ||
*Спонсор этого выпуска [DigitalOcean](https://www.digitalocean.com)* | ||
[аудио](https://cdn.radio-t.com/rt_podcast{{.EpisodeNum}}.mp3) • [лог чата](https://chat.radio-t.com/logs/radio-t-{{.EpisodeNum}}.html) | ||
<audio src="https://cdn.radio-t.com/rt_podcast{{.EpisodeNum}}.mp3" preload="none"></audio> | ||
` | ||
|
||
var prepShowTmpl = `+++ | ||
title = "Темы для {{.EpisodeNum}}" | ||
date = {{.TS.Format "2006-01-02T15:04:05"}} | ||
categories = ["prep"] | ||
+++ | ||
` | ||
|
||
// Prep implements both preparation of md file for the new podcast and for prep-show post | ||
type Prep struct { | ||
Client http.Client | ||
NewsDuration time.Duration | ||
NewsAPI string | ||
Dest string | ||
Dry bool | ||
|
||
now func() time.Time | ||
} | ||
|
||
// MakeShow creates md file like podcast-123.md based on newShowTmpl and populated from news response | ||
func (p *Prep) MakeShow(episodeNum int) (err error) { | ||
if p.now == nil { | ||
p.now = time.Now | ||
} | ||
|
||
tp := struct { | ||
EpisodeNum int | ||
TS time.Time | ||
News string | ||
}{ | ||
EpisodeNum: episodeNum, | ||
TS: p.now(), | ||
} | ||
|
||
if tp.News, err = p.lastNews(int(p.NewsDuration.Hours())); err != nil { | ||
return errors.Wrap(err, "failed to load last news") | ||
} | ||
|
||
return p.applyTemplate(fmt.Sprintf("%s/podcast-%d.md", p.Dest, episodeNum), newShowTmpl, tp) | ||
} | ||
|
||
// MakePrep creates a post for news collection, i.e. prep-123.md | ||
func (p *Prep) MakePrep(episodeNum int) (err error) { | ||
if p.now == nil { | ||
p.now = time.Now | ||
} | ||
|
||
tp := struct { | ||
EpisodeNum int | ||
TS time.Time | ||
}{ | ||
EpisodeNum: episodeNum, | ||
TS: p.now(), | ||
} | ||
|
||
return p.applyTemplate(fmt.Sprintf("%s/prep-%d.md", p.Dest, episodeNum), prepShowTmpl, tp) | ||
} | ||
|
||
// applyTemplate writes the applied template to outFile | ||
func (p *Prep) applyTemplate(outFile string, tmpl string, tp interface{}) error { | ||
t, err := template.New("tmpl").Parse(tmpl) | ||
if err != nil { | ||
return errors.Wrapf(err, "can't parse template") | ||
} | ||
msg := bytes.Buffer{} | ||
if err = t.Execute(&msg, tp); err != nil { | ||
return errors.Wrapf(err, "can't apply template") | ||
} | ||
if p.Dry { | ||
log.Printf(msg.String()) | ||
return nil | ||
} | ||
return errors.Wrapf(ioutil.WriteFile(outFile, msg.Bytes(), 0660), "can't write %s", outFile) | ||
} | ||
|
||
// lastNews gets news from news API for the lase hrs hours | ||
func (p *Prep) lastNews(hrs int) (string, error) { | ||
resp, err := p.Client.Get(fmt.Sprintf("%s/lastmd/%d", p.NewsAPI, hrs)) | ||
if err != nil { | ||
return "", errors.Wrap(err, "can't get news") | ||
} | ||
defer resp.Body.Close() | ||
if resp.StatusCode != http.StatusOK { | ||
return "", errors.Errorf("invalid status code %s", resp.Status) | ||
} | ||
b, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return "", errors.Wrap(err, "can't read news body") | ||
} | ||
return string(b), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package cmd | ||
|
||
import ( | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestPrep_MakeShow(t *testing.T) { | ||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
require.Equal(t, "/lastmd/12", r.URL.Path) | ||
w.Write([]byte("- blah1\n- blah2")) | ||
})) | ||
defer ts.Close() | ||
|
||
p := Prep{ | ||
Client: http.Client{Timeout: 100 * time.Millisecond}, | ||
NewsDuration: 12 * time.Hour, | ||
NewsAPI: ts.URL, | ||
Dest: "/tmp", | ||
now: func() time.Time { return time.Date(2020, 2, 3, 20, 18, 53, 0, time.Local) }, | ||
} | ||
|
||
err := p.MakeShow(123) | ||
require.NoError(t, err) | ||
defer os.Remove("/tmp/podcast-123.md") | ||
|
||
b, err := ioutil.ReadFile("/tmp/podcast-123.md") | ||
require.NoError(t, err) | ||
exp := `+++ | ||
title = "Радио-Т 123" | ||
date = 2020-02-03T20:18:53 | ||
categories = ["podcast"] | ||
image = "https://radio-t.com/images/radio-t/rt123.jpg" | ||
filename = "rt_podcast123" | ||
+++ | ||
![](https://radio-t.com/images/radio-t/rt123.jpg) | ||
- blah1 | ||
- blah2 | ||
*Спонсор этого выпуска [DigitalOcean](https://www.digitalocean.com)* | ||
[аудио](https://cdn.radio-t.com/rt_podcast123.mp3) • [лог чата](https://chat.radio-t.com/logs/radio-t-123.html) | ||
<audio src="https://cdn.radio-t.com/rt_podcast123.mp3" preload="none"></audio> | ||
` | ||
assert.Equal(t, exp, string(b)) | ||
} | ||
|
||
func TestPrep_MakePrep(t *testing.T) { | ||
p := Prep{ | ||
Client: http.Client{Timeout: 100 * time.Millisecond}, | ||
NewsDuration: 12 * time.Hour, | ||
Dest: "/tmp", | ||
now: func() time.Time { return time.Date(2020, 2, 3, 20, 18, 53, 0, time.Local) }, | ||
} | ||
|
||
err := p.MakePrep(123) | ||
require.NoError(t, err) | ||
defer os.Remove("/tmp/prep-123.md") | ||
|
||
b, err := ioutil.ReadFile("/tmp/prep-123.md") | ||
require.NoError(t, err) | ||
exp := `+++ | ||
title = "Темы для 123" | ||
date = 2020-02-03T20:18:53 | ||
categories = ["prep"] | ||
+++ | ||
` | ||
assert.Equal(t, exp, string(b)) | ||
} |
Oops, something went wrong.