Skip to content

Commit 1e15368

Browse files
authored
feat (engine, ui, cli): broadcast (#2574)
1 parent 18ee5a7 commit 1e15368

40 files changed

+1123
-51
lines changed

cli/cdsctl/admin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func admin() *cobra.Command {
1919
[]*cobra.Command{
2020
adminServices,
2121
adminHooks,
22+
adminBroadcasts,
2223
usr,
2324
group,
2425
worker,
@@ -29,5 +30,6 @@ func admin() *cobra.Command {
2930
[]*cobra.Command{
3031
adminServices,
3132
adminHooks,
33+
adminBroadcasts,
3234
})
3335
}

cli/cdsctl/admin_broadcasts.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"reflect"
8+
9+
"github.com/spf13/cobra"
10+
11+
"github.com/ovh/cds/cli"
12+
"github.com/ovh/cds/sdk"
13+
)
14+
15+
var (
16+
adminBroadcastsCmd = cli.Command{
17+
Name: "broadcasts",
18+
Short: "Manage CDS broadcasts",
19+
}
20+
21+
adminBroadcasts = cli.NewCommand(adminBroadcastsCmd, nil,
22+
[]*cobra.Command{
23+
cli.NewListCommand(adminBroadcastListCmd, adminBroadcastListRun, nil),
24+
cli.NewGetCommand(adminBroadcastShowCmd, adminBroadcastShowRun, nil),
25+
cli.NewCommand(adminBroadcastDeleteCmd, adminBroadcastDeleteRun, nil),
26+
cli.NewCommand(adminBroadcastCreateCmd, adminBroadcastCreateRun, nil),
27+
})
28+
)
29+
30+
var adminBroadcastCreateCmd = cli.Command{
31+
Name: "create",
32+
Short: "Create a CDS broadcast",
33+
Args: []cli.Arg{
34+
{Name: "title"},
35+
},
36+
Flags: []cli.Flag{
37+
{
38+
Kind: reflect.String,
39+
Name: "level",
40+
ShortHand: "l",
41+
Usage: "Level of broadcast: info or warning",
42+
Default: "info",
43+
IsValid: func(s string) bool {
44+
if s != "info" && s != "warning" {
45+
return false
46+
}
47+
return true
48+
},
49+
},
50+
},
51+
Example: `level info:
52+
53+
cdsctl admin broadcasts create "the title" < content.md
54+
55+
level warning:
56+
57+
cdsctl admin broadcasts create --level warning "the title" "the content"
58+
`,
59+
Aliases: []string{"add"},
60+
}
61+
62+
func adminBroadcastCreateRun(v cli.Values) error {
63+
content, err := ioutil.ReadAll(os.Stdin)
64+
if err != nil {
65+
return err
66+
}
67+
68+
bc := &sdk.Broadcast{
69+
Level: v.GetString("level"),
70+
Title: v["title"],
71+
Content: string(content),
72+
}
73+
return client.BroadcastCreate(bc)
74+
}
75+
76+
var adminBroadcastShowCmd = cli.Command{
77+
Name: "show",
78+
Short: "Show a CDS broadcast",
79+
Args: []cli.Arg{
80+
{Name: "id"},
81+
},
82+
}
83+
84+
func adminBroadcastShowRun(v cli.Values) (interface{}, error) {
85+
bc, err := client.BroadcastGet(v["id"])
86+
if err != nil {
87+
return nil, err
88+
}
89+
return bc, nil
90+
}
91+
92+
var adminBroadcastDeleteCmd = cli.Command{
93+
Name: "delete",
94+
Short: "Delete a CDS broadcast",
95+
Args: []cli.Arg{
96+
{Name: "id"},
97+
},
98+
Flags: []cli.Flag{
99+
{
100+
Name: "force",
101+
Usage: "if true, do not fail if action does not exist",
102+
IsValid: func(s string) bool {
103+
if s != "true" && s != "false" {
104+
return false
105+
}
106+
return true
107+
},
108+
Default: "false",
109+
Kind: reflect.Bool,
110+
},
111+
},
112+
}
113+
114+
func adminBroadcastDeleteRun(v cli.Values) error {
115+
err := client.BroadcastDelete(v["id"])
116+
if v.GetBool("force") && sdk.ErrorIs(err, sdk.ErrNoBroadcast) {
117+
fmt.Println(err)
118+
return nil
119+
}
120+
return err
121+
}
122+
123+
var adminBroadcastListCmd = cli.Command{
124+
Name: "list",
125+
Short: "List CDS broadcasts",
126+
}
127+
128+
func adminBroadcastListRun(v cli.Values) (cli.ListResult, error) {
129+
srvs, err := client.Broadcasts()
130+
if err != nil {
131+
return nil, err
132+
}
133+
return cli.AsListResult(srvs), nil
134+
}

engine/api/api_routes.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ func (api *API) InitRouter() {
8989
// Platform
9090
r.Handle("/platform/models", r.GET(api.getPlatformModels))
9191

92+
// Broadcast
93+
r.Handle("/broadcast", r.POST(api.addBroadcastHandler, NeedAdmin(true)), r.GET(api.getBroadcastsHandler))
94+
r.Handle("/broadcast/{id}", r.GET(api.getBroadcastHandler), r.PUT(api.updateBroadcastHandler, NeedAdmin(true)), r.DELETE(api.deleteBroadcastHandler, NeedAdmin(true)))
95+
9296
// Overall health
9397
r.Handle("/mon/status", r.GET(api.statusHandler, Auth(false)))
9498
r.Handle("/mon/smtp/ping", r.GET(api.smtpPingHandler, Auth(true)))

engine/api/broadcast.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"time"
7+
8+
"github.com/ovh/cds/engine/api/broadcast"
9+
"github.com/ovh/cds/engine/api/project"
10+
"github.com/ovh/cds/sdk"
11+
)
12+
13+
func (api *API) addBroadcastHandler() Handler {
14+
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
15+
var bc sdk.Broadcast
16+
if err := UnmarshalBody(r, &bc); err != nil {
17+
return sdk.WrapError(err, "addBroadcast> cannot unmarshal body")
18+
}
19+
20+
if bc.Title == "" {
21+
return sdk.WrapError(sdk.ErrWrongRequest, "updateBroadcast> wrong title")
22+
}
23+
now := time.Now()
24+
bc.Created = now
25+
bc.Updated = now
26+
27+
if bc.ProjectKey != "" {
28+
proj, errProj := project.Load(api.mustDB(), api.Cache, bc.ProjectKey, getUser(ctx))
29+
if errProj != nil {
30+
return sdk.WrapError(sdk.ErrNoProject, "addBroadcast> Cannot load %s", bc.ProjectKey)
31+
}
32+
bc.ProjectID = &proj.ID
33+
}
34+
35+
if err := broadcast.Insert(api.mustDB(), &bc); err != nil {
36+
return sdk.WrapError(err, "addBroadcast> cannot add broadcast")
37+
}
38+
39+
return WriteJSON(w, bc, http.StatusCreated)
40+
}
41+
}
42+
43+
func (api *API) updateBroadcastHandler() Handler {
44+
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
45+
broadcastID, errr := requestVarInt(r, "id")
46+
if errr != nil {
47+
return sdk.WrapError(errr, "updateBroadcast> Invalid id")
48+
}
49+
50+
if _, err := broadcast.LoadByID(api.mustDB(), broadcastID); err != nil {
51+
return sdk.WrapError(err, "updateBroadcast> cannot load broadcast by id")
52+
}
53+
54+
// Unmarshal body
55+
var bc sdk.Broadcast
56+
if err := UnmarshalBody(r, &bc); err != nil {
57+
return sdk.WrapError(err, "updateBroadcast> cannot unmarshal body")
58+
}
59+
60+
if bc.ProjectKey != "" {
61+
proj, errProj := project.Load(api.mustDB(), api.Cache, bc.ProjectKey, getUser(ctx))
62+
if errProj != nil {
63+
return sdk.WrapError(sdk.ErrNoProject, "updateBroadcast> Cannot load %s", bc.ProjectKey)
64+
}
65+
bc.ProjectID = &proj.ID
66+
}
67+
68+
tx, errtx := api.mustDB().Begin()
69+
if errtx != nil {
70+
return sdk.WrapError(errtx, "updateBroadcast> unable to start transaction")
71+
}
72+
73+
defer tx.Rollback()
74+
75+
if bc.ID <= 0 || broadcastID != bc.ID {
76+
return sdk.WrapError(sdk.ErrWrongRequest, "requestVarInt> %s is not valid. id in path:%d", bc.ID, broadcastID)
77+
}
78+
79+
// update broadcast in db
80+
if err := broadcast.Update(tx, &bc); err != nil {
81+
return sdk.WrapError(err, "updateBroadcast> cannot update broadcast")
82+
}
83+
84+
if err := tx.Commit(); err != nil {
85+
return sdk.WrapError(err, "updateBroadcast> unable to commit transaction")
86+
}
87+
88+
return WriteJSON(w, bc, http.StatusOK)
89+
}
90+
}
91+
92+
func (api *API) deleteBroadcastHandler() Handler {
93+
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
94+
broadcastID, errr := requestVarInt(r, "id")
95+
if errr != nil {
96+
return sdk.WrapError(errr, "deleteBroadcast> Invalid id")
97+
}
98+
99+
tx, err := api.mustDB().Begin()
100+
if err != nil {
101+
return sdk.WrapError(err, "deleteBroadcast> Cannot start transaction")
102+
}
103+
104+
if err := broadcast.Delete(tx, broadcastID); err != nil {
105+
return sdk.WrapError(err, "deleteBroadcast: cannot delete broadcast")
106+
}
107+
108+
if err := tx.Commit(); err != nil {
109+
return sdk.WrapError(err, "deleteBroadcast> Cannot commit transaction")
110+
}
111+
112+
return nil
113+
}
114+
}
115+
116+
func (api *API) getBroadcastHandler() Handler {
117+
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
118+
id, errr := requestVarInt(r, "id")
119+
if errr != nil {
120+
return sdk.WrapError(errr, "getBroadcast> Invalid id")
121+
}
122+
123+
broadcast, err := broadcast.LoadByID(api.mustDB(), id)
124+
if err != nil {
125+
return sdk.WrapError(err, "getBroadcast> cannot load broadcasts")
126+
}
127+
128+
return WriteJSON(w, broadcast, http.StatusOK)
129+
}
130+
}
131+
132+
func (api *API) getBroadcastsHandler() Handler {
133+
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
134+
broadcasts, err := broadcast.LoadAll(api.mustDB())
135+
if err != nil {
136+
return sdk.WrapError(err, "getBroadcasts> cannot load broadcasts")
137+
}
138+
139+
return WriteJSON(w, broadcasts, http.StatusOK)
140+
}
141+
}

engine/api/broadcast/broadcast.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package broadcast
2+
3+
import (
4+
"database/sql"
5+
"time"
6+
7+
"github.com/go-gorp/gorp"
8+
9+
"github.com/ovh/cds/sdk"
10+
)
11+
12+
// Insert insert a new worker broadcast in database
13+
func Insert(db gorp.SqlExecutor, bc *sdk.Broadcast) error {
14+
dbmsg := broadcast(*bc)
15+
if err := db.Insert(&dbmsg); err != nil {
16+
return err
17+
}
18+
bc.ID = dbmsg.ID
19+
return nil
20+
}
21+
22+
// Update update a broadcast
23+
func Update(db gorp.SqlExecutor, bc *sdk.Broadcast) error {
24+
bc.Updated = time.Now()
25+
dbmsg := broadcast(*bc)
26+
if _, err := db.Update(&dbmsg); err != nil {
27+
return err
28+
}
29+
return nil
30+
}
31+
32+
// LoadByID loads broadcast by id
33+
func LoadByID(db gorp.SqlExecutor, id int64) (*sdk.Broadcast, error) {
34+
dbBroadcast := broadcast{}
35+
query := `select * from broadcast where id=$1`
36+
if err := db.SelectOne(&dbBroadcast, query, id); err != nil {
37+
if err == sql.ErrNoRows {
38+
return nil, sdk.WrapError(sdk.ErrBroadcastNotFound, "LoadByID>")
39+
}
40+
return nil, sdk.WrapError(err, "LoadByID>")
41+
}
42+
broadcast := sdk.Broadcast(dbBroadcast)
43+
if broadcast.ProjectID != nil && *broadcast.ProjectID > 0 {
44+
pkey, errP := db.SelectStr("select projectkey from project where id = $1", broadcast.ProjectID)
45+
if errP != nil {
46+
return nil, sdk.WrapError(errP, "LoadByID>")
47+
}
48+
broadcast.ProjectKey = pkey
49+
}
50+
return &broadcast, nil
51+
}
52+
53+
// LoadAll retrieves broadcasts from database
54+
func LoadAll(db gorp.SqlExecutor) ([]sdk.Broadcast, error) {
55+
res := []broadcast{}
56+
if _, err := db.Select(&res, `select * from broadcast`); err != nil {
57+
return nil, sdk.WrapError(err, "LoadAllBroadcasts> ")
58+
}
59+
60+
broadcasts := make([]sdk.Broadcast, len(res))
61+
for i := range res {
62+
p := res[i]
63+
broadcasts[i] = sdk.Broadcast(p)
64+
65+
if broadcasts[i].ProjectID != nil && *broadcasts[i].ProjectID > 0 {
66+
pkey, errP := db.SelectStr("select projectkey from project where id = $1", broadcasts[i].ProjectID)
67+
if errP != nil {
68+
return nil, sdk.WrapError(errP, "LoadAll>")
69+
}
70+
broadcasts[i].ProjectKey = pkey
71+
}
72+
}
73+
74+
return broadcasts, nil
75+
}
76+
77+
// Delete removes broadcast from database
78+
func Delete(db gorp.SqlExecutor, ID int64) error {
79+
m := broadcast(sdk.Broadcast{ID: ID})
80+
count, err := db.Delete(&m)
81+
if err != nil {
82+
return err
83+
}
84+
if count == 0 {
85+
return sdk.ErrNoBroadcast
86+
}
87+
return nil
88+
}

0 commit comments

Comments
 (0)