Compare commits

...
44 Commits
Author SHA1 Message Date
a.pivkin 1a9bc04ef9 sdf 2026-01-26 18:07:51 +03:00
Alexander Pivkin 95f257a4d0 sdf 2026-01-23 14:36:31 +03:00
a.pivkin 23e5e11a1d d 2026-01-13 06:28:39 +03:00
a.pivkin 410010a974 fg 2026-01-11 11:05:35 +03:00
anpivkin 2e763f0dd6 sf 2026-01-07 09:07:11 +03:00
a.pivkin 8aceabf0f1 ret 2026-01-04 19:16:49 +03:00
Alexander Pivkin 204c4c783f sdf 2026-01-04 15:57:56 +03:00
Alexander Pivkin 9f41cbe454 sdf 2026-01-04 14:56:29 +03:00
a.pivkin 747b022b9c df 2026-01-03 22:07:46 +03:00
a.pivkin dec7db5f5e jkh 2026-01-03 21:55:54 +03:00
anpivkin 389dcbc366 Added devp-deploy to rbd-exporter 2026-01-03 21:19:07 +03:00
a.pivkin ec2b96105f asdf 2026-01-03 18:10:08 +03:00
Пивкин Александр Николаевич 77c0e5419c Added devp-deploy to rbd-exporter 2026-01-02 16:13:44 +03:00
a.pivkin 0e1294eb8e sf 2025-12-31 13:08:10 +03:00
a.pivkin 23931220bb fgdfg 2025-12-30 21:55:12 +03:00
Alexander Pivkin 00b9ce122d dfg 2025-12-29 17:38:54 +03:00
Alexander Pivkin 1072400433 asdf 2025-12-29 10:35:19 +03:00
Alexander Pivkin 83bbe32525 sd 2025-12-28 13:45:51 +03:00
a.pivkin 62ef2b7eb2 jhg 2025-12-28 13:35:36 +03:00
Alexander Pivkin b03d18828c xc 2025-12-28 13:27:12 +03:00
Alexander Pivkin 92d37b5217 sd 2025-12-28 12:51:18 +03:00
a.pivkin c14a385a8a sadf 2025-12-28 12:50:20 +03:00
Alexander Pivkin 1f098de743 xcv 2025-12-28 07:36:12 +03:00
Alexander Pivkin 48f498a1dd OMDB_api 2025-12-27 19:20:28 +03:00
Alexander Pivkin 75ffdefb4a Added devp-deploy to rbd-exporter 2025-12-27 18:58:23 +03:00
a.pivkin c1bae790ed sdf 2025-12-27 18:55:08 +03:00
a.pivkin 0124fc6ace sdf 2025-12-25 21:11:11 +03:00
a.pivkin a49427e88b asd 2025-12-25 21:04:30 +03:00
a.pivkin a88d65d32f update 2025-12-24 08:54:36 +03:00
a.pivkin 2294202009 linter defeated! 2025-12-23 19:20:42 +03:00
a.pivkin 5ca21e635d cc 2025-12-23 06:53:20 +03:00
a.pivkin 54ce83f458 fixed some errors from linter 2025-12-23 06:49:59 +03:00
a.pivkin 20a59dceae Added devp linter 2025-12-23 06:40:56 +03:00
a.pivkin ab3a912c5a asdf 2025-12-22 17:39:04 +03:00
a.pivkin e125ead164 sss 2025-12-22 09:17:21 +03:00
a.pivkin 2e19663a63 puk 2025-12-22 07:57:42 +03:00
a.pivkin af4a1c46e1 new exporter version 2025-12-20 17:00:05 +03:00
a.pivkin bf8f562768 new exporter version 2025-12-20 16:59:32 +03:00
Alexander Pivkin 640a6d2a46 Added plan of tests 2025-12-20 10:21:45 +03:00
Alexander Pivkin a889e1c7dd Added plan of tests 2025-12-20 09:59:10 +03:00
a.pivkin 4833874324 interfaces 2025-12-20 08:48:06 +03:00
a.pivkin 05bd6f3c03 new exporter version 2025-12-18 14:15:00 +03:00
a.pivkin 59d2508e7f new exporter version 2025-12-17 10:02:26 +03:00
a.pivkin 91cc8ffe61 kek 2025-12-15 20:34:28 +03:00
301 changed files with 62217 additions and 89 deletions
+72
View File
@@ -0,0 +1,72 @@
package main
BAD
// Producer
type CustomerStorage interface {
StoreCustomer(customer string) error
GetCustomer(is string) error
UpdateCustomer(customer string) error
GetAllCustomers() ([]string,error)
GetCustomerWithoutContract() ([]string,error)
}
//======================================
// Consumer
type customerGetter interface {
GetAllCustomers() ([]string,error)
}
//===================================================================
type IntConfig struct {
// ...
}
func (c IntConfig) Get() int { <======== PRODUCER
return 0
}
func (c IntConfig) Set(value int) {
//...
}
Consumer
type confifSetter interface{
Set()
}
type configGetter interface {
Get() int
}
type Mixed interface {
confifSetter
configGetter
}
type Foo struct {
threshold configGetter
}
func NewFoo(new_threshold configGetter) Foo {
return Foo{threshold: new_threshold}
}
func (f Foo) Bar() {
threshold := f.threshold.Get()
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.25.5
+65
View File
@@ -0,0 +1,65 @@
package main
import (
"encoding/json"
"fmt"
"text/template"
"net/http"
"os"
)
const (
URL = "http://www.omdbapi.com"
KEY = "fa22ab55"
)
type SearchRes struct {
Title string `json:"Title"`
Year string `json:"Year"`
Genre string `json:"Genre"`
Ratings []Ratings
}
type Ratings struct {
Source string `json:"Source"`
Value string `json:"Value"`
}
const templ = `
=======================
Title: {{.Title}}
Year: {{.Year}}
Genre: {{.Genre}}
Rating:
{{range .Ratings}}
Source: {{.Source}}
Rating: {{.Value}}
{{end}}
=======================
`
var report = template.Must(template.New("MovieReport").Parse(templ))
func Search(s string) (SearchRes,error) {
resp,err := http.Get(URL + "?apikey=" + KEY + "&t=" + s)
if err != nil {
return SearchRes{},err
}
var res SearchRes
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
fmt.Print("Error in unmarshalling ",err)
}
return res,nil
}
func main() {
arg := os.Args[1]
// fmt.Println(arg)
res,_ := Search(arg)
if err := report.Execute(os.Stdout,res); err != nil {
fmt.Printf("Cannot show report ",err)
}
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.24.5
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.24.5
+36
View File
@@ -0,0 +1,36 @@
package main
import (
"fmt"
"os"
"time"
)
func launch() {
fmt.Println("To abort launch press Enter")
for i:=10;i >=0; i-- {
fmt.Printf("%d...\n",i)
}
}
func main() {
abort := make(chan struct{})
go func() {
os.Stdin.Read(make([]byte,1))
abort <- struct{}{}
}()
tick := time.Tick(1*time.Second)
for i:=10; i>=0; i-- {
fmt.Printf("%d...\n",i)
select {
case <-tick:
case <- abort:
fmt.Println("Launch cancelled")
return
}
}
}
+26
View File
@@ -0,0 +1,26 @@
package main
import "fmt"
func kek(i int) {
res_string := ""
for range i {
res_string += " "
}
fmt.Println(res_string,i)
}
func main() {
done := make(chan int)
for i:=range 10 {
go func() {
kek(i)
done <- 1
}()
}
for range 10 {
<- done
}
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.24.5
+133
View File
@@ -0,0 +1,133 @@
package main
import (
"bufio"
"fmt"
"log"
"net"
"time"
)
type client struct {
_chan chan <- string
name string
}
var (
entering = make(chan client)
leaving = make(chan client)
messages = make(chan string)
cancel = make(chan time.Time)
)
func handleConn(conn net.Conn) {
defer conn.Close()
ch := make(chan string)
go clientWriter(conn,ch)
who := conn.RemoteAddr().String()
client := client{
_chan: ch,
name: who,
}
ch <- "You " + who
messages <- who + " connected"
entering <- client
inputChan := make(chan string)
timeout := time.NewTimer(40*time.Second)
defer timeout.Stop()
input := bufio.NewScanner(conn)
go func() {
log.Println("inside of goroutine")
for input.Scan() {
log.Println("inside of Scan for loop")
inputChan <- client.name + ": " + input.Text()
}
if err := input.Err(); err != nil {
log.Printf("Scanner error: %v", err)
} else {
log.Println("Scanner reached EOF (connection closed)")
}
log.Println("Closing input channel")
close(inputChan)
}()
loop:
for {
select {
case text,ok := <- inputChan:
if !ok {
leaving <- client
messages <- who + " disconnected"
break loop
}
if !timeout.Stop() {
<- timeout.C
}
timeout.Reset(40*time.Second)
messages <- text
case <- timeout.C:
ch <- "Connection timed out due to inactivity"
leaving <- client
messages <- who + " disconnected (timeout)"
break loop
}
}
}
func clientWriter(conn net.Conn, ch <- chan string) {
for msg := range ch {
fmt.Fprintln(conn,msg)
}
}
func broadcaster() {
clients := make(map[client]bool)
for {
select {
case msg := <- messages:
log.Println("Got a msg")
for cli := range clients {
cli._chan <- msg
}
case cli := <- entering:
cli._chan <- "List of connected clients"
for i := range clients {
cli._chan <- i.name
}
clients[cli] = true
case cli := <- leaving:
log.Printf("Deleting client %s")
delete(clients,cli)
close(cli._chan)
}
}
}
func main() {
listener,err := net.Listen("tcp",":9090")
if err != nil {
log.Fatalf("Error in Lister %v",err)
}
go broadcaster()
for {
conn,err := listener.Accept()
if err != nil {
log.Fatalf("Error in Accept %v",err)
continue
}
go handleConn(conn)
}
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.24.5
+15
View File
@@ -0,0 +1,15 @@
package main
import (
"fmt"
"time"
)
func main() {
var k time.Duration = 1
for i:=0; i < 100; i++ {
k = k<<1
time.Sleep(k*time.Second)
fmt.Println(k)
}
}
+24
View File
@@ -0,0 +1,24 @@
package main
import (
"fmt"
"time"
)
func logger(s string) func() {
fmt.Printf("Enter in %s\n",s)
now := time.Now()
return func () {
fmt.Printf("Time elapsed %s\n",time.Since(now))
fmt.Printf("Exit from func %s\n",s)
}
}
func SlowOP() {
defer logger("SlowOP")()
time.Sleep(2*time.Second)
}
func main() {
SlowOP()
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.25.5
BIN
View File
Binary file not shown.
+74
View File
@@ -0,0 +1,74 @@
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"time"
)
var verbos = flag.Bool("v",false,"show verbose")
func main() {
flag.Parse()
roots := flag.Args()
if len(roots) == 0 {
roots = []string{"."}
}
fileSizes := make(chan int64)
go func() {
for _,root := range roots {
WalkDir(root,fileSizes)
}
close(fileSizes)
}()
var tick <- chan time.Time
if *verbos{
tick = time.Tick(500 * time.Millisecond)
}
var nfiles,nbytes int64
loop:
for {
select {
case size,ok := <- fileSizes:
if !ok {
break loop
}
nfiles++
nbytes += size
case <- tick:
printDU(nfiles,nbytes)
}
}
}
func printDU(nfiles, nbytes int64) {
fmt.Printf("%d files, %.1f GB\n",nfiles,float64(nbytes)/1e9)
}
func WalkDir(dir string, fileSizes chan <- int64) {
for _, entry := range dirents(dir) {
if entry.IsDir() {
subdir := filepath.Join(dir,entry.Name())
WalkDir(subdir,fileSizes)
} else {
size,_ := entry.Info()
fileSizes <- size.Size()
}
}
}
func dirents(dir string) []os.DirEntry{
entries, err := os.ReadDir(dir)
if err != nil {
fmt.Fprintf(os.Stderr,"du1: %v\n",err)
return nil
}
return entries
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.25.5
+31
View File
@@ -0,0 +1,31 @@
package main
import "fmt"
type Kek interface {
lol()
azaza()
}
type Test struct {
x,y string
}
func(t Test) lol() {
fmt.Printf("I am lol method - %s\n",t.x)
}
func(t Test) azaza() {
fmt.Printf("I am azaza method %s\n",t.y)
}
var _ Kek = (&Test{})
func main() {
t := Test{
x: "one",
y: "two",
}
t.lol()
t.azaza()
}
+62
View File
@@ -0,0 +1,62 @@
package ftp
import (
"bufio"
"fmt"
"net"
"strings"
)
type Conn struct {
conn net.Conn
dataType dataType
dataPort *dataPort
rootDir string
workDir string
}
func NewConn(conn net.Conn, rootDir string) *Conn {
return &Conn{
conn: conn,
rootDir: rootDir,
workDir: "/",
}
}
func Serve (c *Conn) {
c.respond(status220)
s := bufio.NewScanner(c.conn)
for s.Scan() {
input := strings.Fields(s.Text())
if len(input) == 0 {
continue
}
command, args := input[0], input[1:]
fmt.Printf("<< %s %v", command, args)
switch command {
case "CWD": // cd
c.cwd(args)
case "LIST": // ls
c.list(args)
case "PORT":
c.port(args)
case "USER":
c.user(args)
case "QUIT": // close
c.respond(status221)
return
// case "RETR": // get
// c.retr(args)
case "TYPE":
c.setDataType(args)
default:
c.respond(status502)
}
}
if s.Err() != nil {
fmt.Print(s.Err())
}
}
+25
View File
@@ -0,0 +1,25 @@
package ftp
import (
"fmt"
"os"
"path/filepath"
)
func (c *Conn) cwd(args []string) {
if len(args) != 1 {
c.respond(status501)
return
}
workDir := filepath.Join(c.workDir, args[0])
absPath := filepath.Join(c.rootDir, workDir)
_, err := os.Stat(absPath)
if err != nil {
fmt.Print(err)
c.respond(status550)
return
}
c.workDir = workDir
c.respond(status200)
}
@@ -0,0 +1,65 @@
package ftp
import "net"
func (c *Conn) dataConnect() (net.Conn, error) {
conn, err := net.Dial("tcp", c.dataPort.toAddress())
if err != nil {
return nil, err
}
return conn, nil
}
dataConn, err = c.dataConnect()
if err != nil {
log.Print(err)
c.respond(status425)
return
}
defer dataConn.Close()
for _, file := range files {
_, err := fmt.Fprint(dataConn, file.Name(), c.EOL())
if err != nil {
log.Print(err)
c.respond(status426)
}
}
_, err = fmt.Fprintf(dataConn, c.EOL())
if err != nil {
log.Print(err)
c.respond(status426)
}
c.respond(status226)
func (c *Conn) retr(args []string) {
if len(args) != 1 {
c.respond(status501)
return
}
path := filepath.Join(c.rootDir, c.workDir, args[0])
file, err := os.Open(path)
if err != nil {
log.Print(err)
c.respond(status550)
}
c.respond(status150)
dataConn, err := c.dataConnect()
if err != nil {
log.Print(err)
c.respond(status425)
}
defer dataConn.Close()
_, err = io.Copy(dataConn, file)
if err != nil {
log.Print(err)
c.respond(status426)
return
}
io.WriteString(dataConn, c.EOL())
c.respond(status226)
}
@@ -0,0 +1,25 @@
package ftp
type dataType int
const (
ascii dataType = iota
binary
)
func (c *Conn) setDataType(args []string) {
if len(args) == 0 {
c.respond(status501)
}
switch args[0] {
case "A":
c.dataType = ascii
case "I": // image/binary
c.dataType = binary
default:
c.respond(status504)
return
}
c.respond(status200)
}
+47
View File
@@ -0,0 +1,47 @@
package ftp
import (
"fmt"
"io/ioutil"
"path/filepath"
)
func (c *Conn) list(args []string) {
var target string
if len(args) > 0 {
target = filepath.Join(c.rootDir, c.workDir, args[0])
} else {
target = filepath.Join(c.rootDir, c.workDir)
}
files, err := ioutil.ReadDir(target)
if err != nil {
fmt.Print(err)
c.respond(status550)
return
}
c.respond(status150)
dataConn, err := c.dataConnect()
if err != nil {
fmt.Print(err)
c.respond(status425)
return
}
defer dataConn.Close()
for _, file := range files {
_, err := fmt.Fprint(dataConn, file.Name(), c.EOL())
if err != nil {
fmt.Print(err)
c.respond(status426)
}
}
_, err = fmt.Fprintf(dataConn, c.EOL())
if err != nil {
fmt.Print(err)
c.respond(status426)
}
c.respond(status226)
}
+42
View File
@@ -0,0 +1,42 @@
package ftp
import "fmt"
func (c *Conn) port(args []string) {
if len(args) != 1 {
c.respond(status501)
return
}
dataPort, err := dataPortFromHostPort(args[0])
if err != nil {
fmt.Print(err)
c.respond(status501)
return
}
c.dataPort = dataPort
c.respond(status200)
}
type dataPort struct {
h1, h2, h3, h4 int // host
p1, p2 int // port
}
func dataPortFromHostPort(hostPort string) (*dataPort, error) {
var dp dataPort
_, err := fmt.Sscanf(hostPort, "%d,%d,%d,%d,%d,%d",
&dp.h1, &dp.h2, &dp.h3, &dp.h4, &dp.p1, &dp.p2)
if err != nil {
return nil, err
}
return &dp, nil
}
func (d *dataPort) toAddress() string {
if d == nil {
return ""
}
// convert hex port bytes to decimal port
port := d.p1<<8 + d.p2
return fmt.Sprintf("%d.%d.%d.%d:%d", d.h1, d.h2, d.h3, d.h4, port)
}
@@ -0,0 +1,40 @@
package ftp
import "fmt"
const (
status150 = "150 File status okay; about to open data connection."
status200 = "200 Command okay."
status220 = "220 Service ready for new user."
status221 = "221 Service closing control connection."
status226 = "226 Closing data connection. Requested file action successful."
status230 = "230 User %s logged in, proceed."
status425 = "425 Can't open data connection."
status426 = "426 Connection closed; transfer aborted."
status501 = "501 Syntax error in parameters or arguments."
status502 = "502 Command not implemented."
status504 = "504 Cammand not implemented for that parameter."
status550 = "550 Requested action not taken. File unavailable."
)
// respond copies a string to the client and terminates it with the appropriate FTP line terminator
// for the datatype.
func (c *Conn) respond(s string) {
fmt.Print(">> ", s)
_, err := fmt.Fprint(c.conn, s, c.EOL())
if err != nil {
fmt.Print(err)
}
}
// EOL returns the line terminator matching the FTP standard for the datatype.
func (c *Conn) EOL() string {
switch c.dataType {
case ascii:
return "\r\n"
case binary:
return "\n"
default:
return "\n"
}
}
+10
View File
@@ -0,0 +1,10 @@
package ftp
import (
"fmt"
"strings"
)
func (c *Conn) user(args []string) {
c.respond(fmt.Sprintf(status230, strings.Join(args, " ")))
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.24.5
+45
View File
@@ -0,0 +1,45 @@
package main
import (
"flag"
"fmt"
"log"
"net"
"path/filepath"
"main/ftp"
)
var port int
var rootDir string
func init() {
flag.IntVar(&port,"port",9090,"port to listet")
flag.StringVar(&rootDir,"rootDir","public","root Directory")
flag.Parse()
}
func handleConn(conn net.Conn) {
defer conn.Close()
absPath, err := filepath.Abs(rootDir)
if err != nil {
fmt.Print(err)
}
ftp.Serve(ftp.NewConn(conn,absPath))
}
func main() {
server := fmt.Sprintf(":%d",port)
listener,err := net.Listen("tcp",server)
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Print(err)
continue
}
handleConn(conn)
}
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.25.5
+50
View File
@@ -0,0 +1,50 @@
package main
import (
"flag"
"fmt"
)
func findSub(s, sub, repl string) (finStr string) {
if len(s) < len(sub) {
return s[:]
}
found := false
currCount := 0
newStr := ""
if s[:len(sub)] == sub {
newStr = repl
currCount++
found = true
// return
} else {
newStr = string(s[0])
}
var resStr string
if found {
resStr = findSub(s[len(sub):], sub, repl)
} else {
resStr = findSub(s[1:],sub,repl)
}
return newStr + resStr
}
func expand(s,sub,repl string, f func(init string,sub string,repl string) string) string{
res := f(s,sub,repl)
return res
}
func main() {
str := flag.String("str", "lolkeklolkeklol", "Initital string")
sub := flag.String("sub", "kek", "substring to find")
repl := flag.String("repl", "puk", "String to replace with")
// res := findSub(*str, *sub, *repl)
res := expand(*str,*sub,*repl,findSub)
fmt.Println(res)
}
@@ -0,0 +1,3 @@
module main
go 1.25.5
@@ -0,0 +1,46 @@
package main
import (
"io"
"log"
"net"
"time"
)
type newWriter struct {
conn io.Writer
}
func (nw newWriter) Write(p []byte) (n int, err error) {
prefix := []byte("curr time is ")
prefix = append(prefix, p...)
n, err = nw.conn.Write(prefix)
return
}
func handleConn(conn net.Conn) {
defer conn.Close()
for {
_, err := io.WriteString(newWriter{conn}, time.Now().Format("15:04:05\n"))
if err != nil {
return
}
time.Sleep(1 * time.Second)
}
}
func main() {
listener, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatalf("Shit happened %s",err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("Error is %s",err)
continue
}
go handleConn(conn)
}
}
@@ -0,0 +1,3 @@
module main
go 1.25.5
@@ -0,0 +1,15 @@
package main
import (
"fmt"
"time"
)
func main() {
for {
for _,r := range `-\|/` {
fmt.Printf("\r processing in progress %c",r)
time.Sleep(50*time.Millisecond)
}
}
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.25.5
+96
View File
@@ -0,0 +1,96 @@
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"html/template"
)
type rub float64
func (r rub) String() string {
return fmt.Sprintf("%.2f",r)
}
var tmpl *template.Template
func Render(w http.ResponseWriter, req *http.Request) {
err := tmpl.Execute(w,req)
if err != nil {
http.Error(w,err.Error(),http.StatusInternalServerError)
}
}
type database map[string]rub
type List struct {
Items database
}
func (db *database) list(w http.ResponseWriter, req *http.Request) {
data := List{
Items: *db,
}
err := tmpl.ExecuteTemplate(w,"template.html",data)
if err != nil {
http.Error(w,err.Error(),http.StatusInternalServerError)
}
}
func (db *database) price(w http.ResponseWriter,req *http.Request) {
item := req.URL.Query().Get("item")
v,ok := (*db)[item]
if !ok {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w,"%s not found in goods list",item)
return
}
fmt.Fprintf(w,"Price of %s is %s",item,v)
}
func (db *database) update(w http.ResponseWriter, req *http.Request) {
item := req.URL.Query().Get("item")
price := req.URL.Query().Get("price")
f,err := strconv.ParseFloat(price,2)
if err != nil {
log.Fatal(err)
}
(*db)[item] = rub(f)
fmt.Fprintf(w,"item %s and price %s",item,(*db)[item])
}
// func (db database) ServeHTTP(w http.ResponseWriter, r *http.Request ) {
// switch r.URL.Path {
// case "/list":
// for k,v := range db {
// fmt.Fprintf(w,"%s costs %s rub\n",k,v)
// }
// case "/price":
// item := r.URL.Query().Get("item")
// v,ok := db[item]
// if !ok {
// w.WriteHeader(http.StatusNotFound)
// fmt.Fprintf(w,"%s not found in goods list",item)
// return
// }
// fmt.Fprintf(w,"Price of %s is %s",item,v)
// }
// }
func main() {
db := database{
"sneakers": 10.0,
"boots": 20,
}
// mux.Handle("/list",http.HandlerFunc(db.list))
// mux.Handle("/price",http.HandlerFunc(db.price))
tmpl = template.Must(template.ParseFiles("template.html"))
http.HandleFunc("/list",db.list)
http.HandleFunc("/price",db.price)
http.HandleFunc("/update",db.update)
log.Fatal(http.ListenAndServe(":9090",nil))
}
+34
View File
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>TABLE</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.header { background: #3498db; color: white; padding: 20px; border-radius: 8px; }
.content { margin: 20px 0; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 10px; border: 1px solid #ddd; text-align: left; }
th { background: #f4f4f4; }
.price { text-align: right; font-weight: bold; color: #27ae60; }
.total { background: #ecf0f1; font-weight: bold; }
</style>
</head>
<body>
<div class="content">
<h2>Items</h2>
<table>
<tr>
<th>Item Name</th>
<th>Price</th>
</tr>
{{range $key, $value := .Items}}
<tr>
<td>{{$key}}</td>
<td class="price">{{$value.String}}</td>
</tr>
{{end}}
</table>
</div>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.25.5
+123
View File
@@ -0,0 +1,123 @@
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"strings"
"time"
)
type ByteCount int
func (bc *ByteCount) Write(p []byte) (n int,err error) {
*bc = ByteCount(len(p))
return len(p),nil
}
type WordsNum int
func (wn *WordsNum) Write(p []byte) (n int,err error) {
*wn = WordsNum(len(p))
return len(p),nil
}
func Count() []byte {
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Enter text here: ")
if scanner.Scan() {
line := scanner.Text()
wordCount := bufio.NewScanner(strings.NewReader(line))
wordCount.Split(bufio.ScanWords)
wordsNum := []byte{}
for wordCount.Scan() {
wordsNum = append(wordsNum, 1)
fmt.Println("Word is: ",wordCount.Text())
}
fmt.Printf("Amount of words is %d\n",wordsNum)
return wordsNum
}
return nil
}
type NewWriter struct {
writer io.Writer
counter *int64
}
func (nw *NewWriter) Write(p []byte) (n int,err error) {
n,err = nw.writer.Write(p)
fmt.Println()
*nw.counter = int64(n)
return n,err
}
func CountingWriter(w io.Writer) (io.Writer, *int64) {
counter := int64(0)
return &NewWriter{
writer: w,
counter: &counter,
},&counter
}
// Упражнения на возврат LimitReader
func GetNewReader(s string) io.Reader {
return strings.NewReader(s)
}
type MyReader struct {
reader io.Reader
constraint int
}
func (nr MyReader) Read(p []byte) (n int,err error) {
n,err = nr.reader.Read(p[:nr.constraint])
return
}
func LimitReader(r io.Reader,n int) io.Reader {
return MyReader{
reader: r,
constraint: n,
}
}
// Проверка, что ByteCount действительно соответствует интерфейсу io.Writer
var _ io.Writer = new(ByteCount)
var period = flag.Duration("period",1 * time.Second,"sleep period")
func main() {
flag.Parse()
fmt.Printf("Waiting %v...",period)
time.Sleep(*period)
fmt.Println()
// var bc ByteCount
// bc.Write([]byte("Hello dodik"))
// fmt.Println(bc)
// var wn WordsNum
// wn.Write(Count())
// fmt.Println(wn)
// writer,count := CountingWriter(os.Stdout)
// writer.Write([]byte("Hello Baga"))
// fmt.Printf("Bytes %d",*count)
// str := "hello"
// buf := make([]byte,100)
// reader := LimitReader(strings.NewReader(str),3)
// // n,_ := reader.Read(buf)
// // reader := strings.NewReader(str)
// n,_ := reader.Read(buf)
// fmt.Printf("Result is %d\n",n)
// fmt.Printf("String is %s",string(buf))
}
@@ -0,0 +1,3 @@
module main
go 1.25.5
@@ -0,0 +1,34 @@
package main
import (
"fmt"
"io"
"log"
"net"
"os"
)
func mustCopy(dst io.Writer,src io.Reader) {
if _,err := io.Copy(dst,src); err != nil {
log.Fatalf("Error in mustCopy %s",err)
}
}
func main() {
conn,err := net.Dial("tcp",":9090")
if err != nil {
log.Fatalf("Error in Dial %s",err)
}
defer conn.Close()
done := make(chan struct{})
go func() {
io.Copy(os.Stdin,conn)
fmt.Println("done")
done <- struct{}{}
}()
io.Copy(conn,os.Stdout)
conn.Close()
<-done
}
@@ -0,0 +1,3 @@
module main
go 1.25.5
@@ -0,0 +1,68 @@
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
"time"
)
func echo(c net.Conn, shout string, t time.Duration) {
for _ = range 3 {
fmt.Fprintln(c, "\t", strings.ToUpper(shout))
time.Sleep(t)
}
}
func handleConn(conn net.Conn) {
count := 1
defer fmt.Println("CLosed Connection")
defer conn.Close()
fmt.Println("Accepted connection")
ticker := time.NewTicker(2*time.Second)
input := bufio.NewScanner(conn)
select {
case <- ticker.C:
fmt.Println("TIMEOUT")
default:
fmt.Println("DEFAULT")
}
for input.Scan() {
fmt.Println("TICK")
switch input.Text() {
case "info":
fmt.Printf("INFO %d",count)
default:
count++
go func() {
defer func (){
count--
}()
echo(conn, input.Text(), 1*time.Second)
}()
}
}
}
func main() {
listener, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatalf("Error %v", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatalf("Error in Accept %s", err)
}
go handleConn(conn)
}
}
+36
View File
@@ -0,0 +1,36 @@
package main
import (
"fmt"
"time"
)
func main() {
count := 1
side1 := make(chan int)
side2 := make(chan int)
tick := time.NewTicker(1*time.Second)
go func(){
for range side1 {
fmt.Println("Tick")
}
}()
go func() {
for range side2 {
fmt.Println("Tack")
}
}()
for range tick.C {
if count % 2 == 0 {
side2 <- 1
count = 1
} else {
side1 <- 1
count++
}
}
}
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.25.5
+80
View File
@@ -0,0 +1,80 @@
package main
import (
"fmt"
"html/template"
// "log"
"os"
"sort"
"text/tabwriter"
"time"
)
type Track struct {
Title string
Artist string
Album string
Year int
Length time.Duration
}
var tracks = []*Track{
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
{"Go", "Moby", "Moby", 1992, length("3m37s")},
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
}
var issueList = template.Must(template.New("tracklist").Parse(`
<table>
<tr style='text-align: left'>
<th>#</th>
<th>Title</th>
<th>Artist</th>
<th>Album</th>
</tr>
{{range .}}
<tr>
<td>{{.Title}}</td>
<td>{{.Artist}}</td>
<td>{{.Album}}</td>
</tr>
{{end}}
</table>
`))
func length(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
panic(s)
}
return d
}
func PrintTracks(tracks []*Track) {
const format = "%v\t%v\t%v\t%v\t%v\n"
tw := tabwriter.NewWriter(os.Stdout,0,8,2,' ',0)
fmt.Fprintf(tw,format,"Title","Artist","Album","Year","Length")
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
for _, t := range tracks {
fmt.Fprintf(tw,format,t.Title,t.Artist,t.Album,t.Year,t.Length)
}
tw.Flush()
}
type byArtists []*Track
func (x byArtists) Len() int {return len(x)}
func (x byArtists) Less(i,j int) bool {return x[i].Artist < x[j].Artist}
func (x byArtists) Swap(i,j int) { x[i],x[j] = x[j],x[i]}
func main() {
PrintTracks(tracks)
fmt.Println()
sort.Sort(byArtists(tracks))
PrintTracks(tracks)
// if err := issueList.Execute(os.Stdout,tracks); err != nil {
// log.Fatal(err)
// }
}
+2
View File
@@ -1,3 +1,5 @@
module proj1 module proj1
go 1.24.5 go 1.24.5
require go.uber.org/zap v1.27.1
+2
View File
@@ -0,0 +1,2 @@
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+50 -5
View File
@@ -1,14 +1,59 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"io"
"net/http"
"os" "os"
// "strings" "time"
) )
const URL = "https://api.github.com/search/issues"
type IssuerSearchReport struct {
Total int `json:"total_count"`
Incomplete_results bool `json:"incomplete_results"`
Created_at time.Time `json:"created_at"`
Items []Issue
}
type Issue struct {
Title string `json:"title"`
User User
}
type User struct {
Login string `json:"login"`
}
func IssuerSearch(s []string) (IssuerSearchReport,error) {
new_s := s[0]
resp,err := http.Get(URL + "?q=" + new_s)
if err != nil {
return IssuerSearchReport{},err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return IssuerSearchReport{},fmt.Errorf("Error in request",resp.Status)
}
var result IssuerSearchReport
body,_ := io.ReadAll(resp.Body)
if err := json.Unmarshal(body,&result); err != nil {
resp.Body.Close()
return IssuerSearchReport{},nil
}
resp.Body.Close()
return result,nil
}
func main() { func main() {
// var sum string result,err := IssuerSearch(os.Args[1:])
for key,val := range os.Args[:]{ if err != nil {
fmt.Println("num is",key,"value is", val) fmt.Println("Error in IssuerSearch")
} }
fmt.Printf("%#v\n",result)
fmt.Println(result)
} }
+12
View File
@@ -0,0 +1,12 @@
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
librbd-dev \
librados-dev \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir /app
WORKDIR /app
COPY ./rbd-exporter /app/rbd-exporter
EXPOSE 9040
CMD [ "./rbd-exporter" ]
+19
View File
@@ -0,0 +1,19 @@
.PHONY: test test-unit test-integration test-coverage
test-unit:
go test -v -short ./...
test-integration:
go test -v -tags=integration ./...
test-coverage:
go test -v -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
test: test-unit
clean:
rm -f coverage.out coverage.html
total-rm:
rm -f main_test.go metrics_test.go pool_test.go types_test.go integration_test.go mock.go
+29 -32
View File
@@ -1,64 +1,61 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
// "github.com/ceph/go-ceph/rados"
"github.com/ceph/go-ceph/rbd"
pipe "gopkg.in/pipe.v2"
) )
func PoolFactory(cephConn CephConnection, poolName string) (Pool, error) { func PoolFactory(cephConn CephConnector, poolName string) (Pool, error) {
var rbdlist []RBD = []RBD{} var rbdlist []iRBD = []iRBD{}
ioctx, err := cephConn.conn.OpenIOContext(poolName) ioctx, err := cephConn.OpenIOContext(poolName)
if err != nil { if err != nil {
return Pool{}, fmt.Errorf("Couldn't set context for pool %s %w", poolName, err) return Pool{}, fmt.Errorf("Couldn't set context for pool %s %w", poolName, err)
} }
defer ioctx.Destroy() defer ioctx.Destroy()
imageList, err := rbd.GetImageNames(ioctx) imageList, err := ioctx.GetImageNames()
if err != nil { if err != nil {
return Pool{}, fmt.Errorf("Couldn't get list of rbds %w", err) return Pool{}, fmt.Errorf("Couldn't get list of rbds %w", err)
} }
for _, v := range imageList { for _, rbdname := range imageList {
stat, err := RBDFacroty(poolName, v) stat, err := RBDFacroty(ioctx, rbdname)
if err != nil { if err != nil {
fmt.Errorf("Coundn't get stat from disk %s %w", v, err) logger.Errorf("Coundn't get stat from disk %s %w", rbdname, err)
panic(err) panic(err)
} }
// fmt.Println(stat)
rbdlist = append(rbdlist, stat) rbdlist = append(rbdlist, stat)
} }
return Pool{ return Pool{
Name: poolName, Name: poolName,
HasRBD: len(imageList) != 0,
RBDlist: rbdlist, RBDlist: rbdlist,
}, nil }, nil
} }
func RBDFacroty(poolname string, rbdname string) (RBD, error) { func RBDFacroty(ioctx IOContexter, rbdname string) (RBD, error) {
defer func() {
if v := recover(); v != nil {
fmt.Println("Shit happened in RBDFactory")
}
}()
rbdPath := fmt.Sprintf("%s/%s", poolname, rbdname) image := ioctx.GetImage(rbdname)
args := []string{"du", "--format", "json", rbdPath} err := image.Open()
p := pipe.Line(
pipe.Exec("rbd", args...),
)
output, err := pipe.CombinedOutput(p)
if err != nil { if err != nil {
fmt.Errorf("Error in processing RBD %v", err) panic(err)
}
defer image.Close()
info,err := image.Stat()
if err != nil {
return RBD{},fmt.Errorf("Couldn't get stats for image %s %w",rbdname,err)
} }
var rbdImage RBDUsage
if err := json.Unmarshal(output, &rbdImage); err != nil {
fmt.Errorf("Error in unmarshaling %w", err)
}
return RBD{ return RBD{
Name: rbdImage.Images[0].Name, Name: rbdname,
Id: rbdImage.Images[0].Id, ImageInfo: *info,
RequestedSize: rbdImage.Images[0].RequestedSize, },nil
UsedSize: rbdImage.Images[0].UsedSize, }
}, nil
}
+255
View File
@@ -0,0 +1,255 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: types.go
//
// Generated by this command:
//
// mockgen -source=types.go -destination=RBDFactory_mockgen_mock.go -package=main
//
// Package main is a generated GoMock package.
package main
import (
reflect "reflect"
rbd "github.com/ceph/go-ceph/rbd"
gomock "go.uber.org/mock/gomock"
)
// MockCephConnector is a mock of CephConnector interface.
type MockCephConnector struct {
ctrl *gomock.Controller
recorder *MockCephConnectorMockRecorder
isgomock struct{}
}
// MockCephConnectorMockRecorder is the mock recorder for MockCephConnector.
type MockCephConnectorMockRecorder struct {
mock *MockCephConnector
}
// NewMockCephConnector creates a new mock instance.
func NewMockCephConnector(ctrl *gomock.Controller) *MockCephConnector {
mock := &MockCephConnector{ctrl: ctrl}
mock.recorder = &MockCephConnectorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCephConnector) EXPECT() *MockCephConnectorMockRecorder {
return m.recorder
}
// ListPools mocks base method.
func (m *MockCephConnector) ListPools() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListPools")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListPools indicates an expected call of ListPools.
func (mr *MockCephConnectorMockRecorder) ListPools() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPools", reflect.TypeOf((*MockCephConnector)(nil).ListPools))
}
// OpenIOContext mocks base method.
func (m *MockCephConnector) OpenIOContext(poolName string) (IOContexter, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenIOContext", poolName)
ret0, _ := ret[0].(IOContexter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenIOContext indicates an expected call of OpenIOContext.
func (mr *MockCephConnectorMockRecorder) OpenIOContext(poolName any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenIOContext", reflect.TypeOf((*MockCephConnector)(nil).OpenIOContext), poolName)
}
// MockIOContexter is a mock of IOContexter interface.
type MockIOContexter struct {
ctrl *gomock.Controller
recorder *MockIOContexterMockRecorder
isgomock struct{}
}
// MockIOContexterMockRecorder is the mock recorder for MockIOContexter.
type MockIOContexterMockRecorder struct {
mock *MockIOContexter
}
// NewMockIOContexter creates a new mock instance.
func NewMockIOContexter(ctrl *gomock.Controller) *MockIOContexter {
mock := &MockIOContexter{ctrl: ctrl}
mock.recorder = &MockIOContexterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIOContexter) EXPECT() *MockIOContexterMockRecorder {
return m.recorder
}
// Destroy mocks base method.
func (m *MockIOContexter) Destroy() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Destroy")
}
// Destroy indicates an expected call of Destroy.
func (mr *MockIOContexterMockRecorder) Destroy() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockIOContexter)(nil).Destroy))
}
// GetImage mocks base method.
func (m *MockIOContexter) GetImage(name string) RBDImager {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetImage", name)
ret0, _ := ret[0].(RBDImager)
return ret0
}
// GetImage indicates an expected call of GetImage.
func (mr *MockIOContexterMockRecorder) GetImage(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImage", reflect.TypeOf((*MockIOContexter)(nil).GetImage), name)
}
// GetImageNames mocks base method.
func (m *MockIOContexter) GetImageNames() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetImageNames")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetImageNames indicates an expected call of GetImageNames.
func (mr *MockIOContexterMockRecorder) GetImageNames() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageNames", reflect.TypeOf((*MockIOContexter)(nil).GetImageNames))
}
// MockRBDImager is a mock of RBDImager interface.
type MockRBDImager struct {
ctrl *gomock.Controller
recorder *MockRBDImagerMockRecorder
isgomock struct{}
}
// MockRBDImagerMockRecorder is the mock recorder for MockRBDImager.
type MockRBDImagerMockRecorder struct {
mock *MockRBDImager
}
// NewMockRBDImager creates a new mock instance.
func NewMockRBDImager(ctrl *gomock.Controller) *MockRBDImager {
mock := &MockRBDImager{ctrl: ctrl}
mock.recorder = &MockRBDImagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRBDImager) EXPECT() *MockRBDImagerMockRecorder {
return m.recorder
}
// Close mocks base method.
func (m *MockRBDImager) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close.
func (mr *MockRBDImagerMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRBDImager)(nil).Close))
}
// Open mocks base method.
func (m *MockRBDImager) Open() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Open")
ret0, _ := ret[0].(error)
return ret0
}
// Open indicates an expected call of Open.
func (mr *MockRBDImagerMockRecorder) Open() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockRBDImager)(nil).Open))
}
// Stat mocks base method.
func (m *MockRBDImager) Stat() (*rbd.ImageInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Stat")
ret0, _ := ret[0].(*rbd.ImageInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Stat indicates an expected call of Stat.
func (mr *MockRBDImagerMockRecorder) Stat() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockRBDImager)(nil).Stat))
}
// MockiRBD is a mock of iRBD interface.
type MockiRBD struct {
ctrl *gomock.Controller
recorder *MockiRBDMockRecorder
isgomock struct{}
}
// MockiRBDMockRecorder is the mock recorder for MockiRBD.
type MockiRBDMockRecorder struct {
mock *MockiRBD
}
// NewMockiRBD creates a new mock instance.
func NewMockiRBD(ctrl *gomock.Controller) *MockiRBD {
mock := &MockiRBD{ctrl: ctrl}
mock.recorder = &MockiRBDMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockiRBD) EXPECT() *MockiRBDMockRecorder {
return m.recorder
}
// getName mocks base method.
func (m *MockiRBD) getName() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "getName")
ret0, _ := ret[0].(string)
return ret0
}
// getName indicates an expected call of getName.
func (mr *MockiRBDMockRecorder) getName() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getName", reflect.TypeOf((*MockiRBD)(nil).getName))
}
// getSize mocks base method.
func (m *MockiRBD) getSize() int64 {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "getSize")
ret0, _ := ret[0].(int64)
return ret0
}
// getSize indicates an expected call of getSize.
func (mr *MockiRBDMockRecorder) getSize() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getSize", reflect.TypeOf((*MockiRBD)(nil).getSize))
}
+2 -2
View File
@@ -20,12 +20,12 @@ func connect() (_ CephConnection, err error) {
return CephConnection{}, err return CephConnection{}, err
} }
err = cephConn.conn.ReadConfigFile(params.config) err = cephConn.conn.ReadConfigFile(params.Config)
if err != nil { if err != nil {
return CephConnection{}, err return CephConnection{}, err
} }
err = cephConn.conn.SetConfigOption("keyring", params.keyring) err = cephConn.conn.SetConfigOption("keyring", params.Keyring)
if err != nil { if err != nil {
return CephConnection{}, err return CephConnection{}, err
} }
+1
View File
@@ -0,0 +1 @@
mode: set
+151
View File
@@ -0,0 +1,151 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: export-deploy
namespace: rook-ceph
labels:
app: export-deploy
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9040"
prometheus.io/path: "/metrics"
spec:
replicas: 1
selector:
matchLabels:
app: export-box
template:
metadata:
labels:
app: export-box
spec:
containers:
- name: export
image: serviceplant/goexp:0.0.31
ports:
- containerPort: 9040
name: metrics
command:
- /bin/bash
- -c
- |
# Replicate the script from toolbox.sh inline so the ceph image
# can be run directly, instead of requiring the rook toolbox
CEPH_CONFIG="/etc/ceph/ceph.conf"
MON_CONFIG="/etc/rook/mon-endpoints"
KEYRING_FILE="/etc/ceph/keyring"
# create a ceph config file in its default location so ceph/rados tools can be used
# without specifying any arguments
write_endpoints() {
endpoints=$(cat ${MON_CONFIG})
# filter out the mon names
# external cluster can have numbers or hyphens in mon names, handling them in regex
# shellcheck disable=SC2001
mon_endpoints=$(echo "${endpoints}"| sed 's/[a-z0-9_-]\+=//g')
DATE=$(date)
echo "$DATE writing mon endpoints to ${CEPH_CONFIG}: ${endpoints}"
cat <<EOF > ${CEPH_CONFIG}
[global]
mon_host = ${mon_endpoints}
[client.admin]
keyring = ${KEYRING_FILE}
EOF
}
# watch the endpoints config file and update if the mon endpoints ever change
watch_endpoints() {
# get the timestamp for the target of the soft link
real_path=$(realpath ${MON_CONFIG})
initial_time=$(stat -c %Z "${real_path}")
while true; do
echo "I am watching!!!"
real_path=$(realpath ${MON_CONFIG})
latest_time=$(stat -c %Z "${real_path}")
if [[ "${latest_time}" != "${initial_time}" ]]; then
write_endpoints
initial_time=${latest_time}
fi
sleep 10
done
}
# read the secret from an env var (for backward compatibility), or from the secret file
ceph_secret=${ROOK_CEPH_SECRET}
if [[ "$ceph_secret" == "" ]]; then
ceph_secret=$(cat /var/lib/rook-ceph-mon/secret.keyring)
fi
# create the keyring file
cat <<EOF > ${KEYRING_FILE}
[${ROOK_CEPH_USERNAME}]
key = ${ceph_secret}
EOF
# write the initial config file
write_endpoints
# continuously update the mon endpoints if they fail over
exec /app/rbd-exporter --keyring /etc/ceph/keyring &
watch_endpoints
imagePullPolicy: IfNotPresent
tty: true
securityContext:
runAsNonRoot: true
runAsUser: 2016
runAsGroup: 2016
capabilities:
drop: ["ALL"]
env:
- name: ROOK_CEPH_USERNAME
valueFrom:
secretKeyRef:
name: rook-ceph-mon
key: ceph-username
volumeMounts:
- mountPath: /etc/ceph
name: ceph-config
- name: mon-endpoint-volume
mountPath: /etc/rook
- name: ceph-admin-secret
mountPath: /var/lib/rook-ceph-mon
readOnly: true
volumes:
- name: ceph-admin-secret
secret:
secretName: rook-ceph-mon
optional: false
items:
- key: ceph-secret
path: secret.keyring
- name: mon-endpoint-volume
configMap:
name: rook-ceph-mon-endpoints
items:
- key: data
path: mon-endpoints
- name: ceph-config
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: export-service
namespace: rook-ceph
labels:
svc_app: export
rook_cluster: rook-ceph
spec:
selector:
app: export-box
ports:
- name: metrics
port: 9040
targetPort: 9040
protocol: TCP
type: ClusterIP
+26 -4
View File
@@ -1,8 +1,13 @@
package main package main
import "flag" import (
"flag"
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var params Params
// This func runs even before main() // This func runs even before main()
func init() { func init() {
@@ -11,6 +16,23 @@ func init() {
_keyring := flag.String("keyring", "/etc/ceph/ceph.client.admin.keyring", "placement of ceph keyring file") _keyring := flag.String("keyring", "/etc/ceph/ceph.client.admin.keyring", "placement of ceph keyring file")
flag.Parse() flag.Parse()
params.config = *config_file params.Config = *config_file
params.keyring = *_keyring params.Keyring = *_keyring
logger = loggerInit()
logger.Info("Setting up logger is complete successfully")
logger.Info("Registering prom metrics")
}
// Here I initialize logger and set some custom settings
func loggerInit() *zap.SugaredLogger {
config := zap.NewDevelopmentConfig()
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, err := config.Build()
if err != nil {
panic(fmt.Sprintf("Logger set up failed: %v", err))
}
defer logger.Sync()
return logger.Sugar()
} }
+13 -4
View File
@@ -3,11 +3,20 @@ module main.go
go 1.25.5 go 1.25.5
require ( require (
github.com/ceph/go-ceph v0.36.0 github.com/ceph/go-ceph v0.37.0
gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 github.com/prometheus/client_golang v1.23.2
go.uber.org/zap v1.27.1
) )
require ( require (
golang.org/x/sys v0.36.0 // indirect github.com/beorn7/perks v1.0.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/sys v0.38.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
) )
+43 -13
View File
@@ -1,23 +1,53 @@
github.com/ceph/go-ceph v0.36.0 h1:IDE4vEF+4fmjve+CPjD1WStgfQ+Lh6vD+9PMUI712KI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/ceph/go-ceph v0.36.0/go.mod h1:fGCbndVDLuHW7q2954d6y+tgPFOBnRLqJRe2YXyngw4= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/ceph/go-ceph v0.37.0 h1:KXliBe3ZDr3/AtfY7n9d1MG7ippYNCVhMPcAgm05CFI=
github.com/ceph/go-ceph v0.37.0/go.mod h1:3y2tOlITlyuVFhy8v6PpCEfjMwKPfXJiH0/2hKZZQRE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 h1:WJH1qsOB4/zb/li+zLMn0vaAUJ5FqPv6HYLI3aQVg1k=
gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544/go.mod h1:UhTeH/yXCK/KY7TX24mqPkaQ7gZeqmWd/8SSS8B3aHw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+59 -11
View File
@@ -3,11 +3,54 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/http"
"os" "os"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
) )
var params Params
var logger *zap.SugaredLogger
func getMetrics(cephConn CephConnector) {
metrics := InitMetrics()
prometheus.MustRegister(
metrics.Total_rbd_requested_size_per_pool,
)
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
var result []Pool = []Pool{}
poolList, err := cephConn.ListPools()
if err != nil {
logger.Error("Cannot get list of pools")
// do not exit but continue checking
continue
}
for _, v := range poolList {
x, _ := PoolFactory(cephConn, v)
result = append(result, x)
}
metrics.Total_rbd_requested_size_per_pool.Reset()
for _,v := range result {
if !v.HasRBD {continue}
FillMetrics(v,metrics)
}
fmt.Println("====================")
// fmt.Println(result)
}
}
func main() { func main() {
var result []Pool = []Pool{}
cephConn, err := connect() cephConn, err := connect()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@@ -17,17 +60,22 @@ func main() {
os.Exit(1) os.Exit(1)
} }
defer cephConn.conn.Shutdown() defer cephConn.conn.Shutdown()
fmt.Println("Successfully connected") logger.Info("Successfully connected")
poolList, err := cephConn.conn.ListPools() http.Handle("/metrics", promhttp.Handler())
if err != nil {
errors.New("Cannot get list of pools")
}
for _, v := range poolList { // HTTP runs in separate thread cuz it blocks futher execution of main
x, _ := PoolFactory(cephConn, v) go func() {
result = append(result, x) logger.Info("Starting http server")
} // Here I check for errors if HTTP fails
fmt.Println(result) if err := http.ListenAndServe(":9040", nil); err != nil {
logger.Fatalf("HTTP server failed to start %v", err)
}
logger.Info("HTTP server started")
}()
go getMetrics(cephConn)
select {}
} }
+41
View File
@@ -0,0 +1,41 @@
package main
import (
"github.com/prometheus/client_golang/prometheus"
)
func InitMetrics() *Metrics {
m := &Metrics{
Total_rbd_requested_size_per_pool: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "total_rbd_requested_size_per_pool",
Help: "total size of all requested RBDs in a specific pool",
},
[]string{"poolname"},
),
// total_rbd_requested_size: prometheus.NewGauge(
// prometheus.GaugeOpts{
// Name: "total_rbd_requested_size",
// Help: "total size of all RBDs in the cluster",
// },
// ),
}
return m
}
func FillMetrics(pool Pool,metrics *Metrics) {
var totalSizePerPool uint64 = 0
logger.Debugf("Processing pool %s",pool.Name)
for _,v := range pool.RBDlist {
totalSizePerPool += uint64(v.GetSize())
}
logger.Debugf("Total size of RBDs in pool %s is %d",pool.Name,totalSizePerPool)
metrics.Total_rbd_requested_size_per_pool.WithLabelValues(pool.Name,
).Set(
float64(totalSizePerPool))
}
BIN
View File
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: export-monitor
namespace: rook-ceph
labels:
team: rook
spec:
namespaceSelector:
matchNames:
- rook-ceph
selector:
matchLabels:
svc_app: export
rook_cluster: rook-ceph
endpoints:
- port: metrics
interval: 30s
path: /metrics
honorLabels: true
+88 -15
View File
@@ -1,42 +1,115 @@
package main package main
import "github.com/ceph/go-ceph/rados" import (
"github.com/ceph/go-ceph/rados"
"github.com/ceph/go-ceph/rbd"
"github.com/prometheus/client_golang/prometheus"
)
type Params struct { type Params struct {
config string Config string
keyring string Keyring string
}
type CephConnector interface {
ListPools() ([]string,error)
OpenIOContext(poolName string) (IOContexter, error)
} }
type CephConnection struct { type CephConnection struct {
conn *rados.Conn conn *rados.Conn
} }
// Here I do implement CephConnector interface
func (cc CephConnection) ListPools() ([]string,error) {
return cc.conn.ListPools()
}
// Here I do implement CephConnector interface
func (cc CephConnection) OpenIOContext(poolName string) (IOContexter,error) {
ioctx,err := cc.conn.OpenIOContext(poolName)
if err != nil {
return nil,err
}
return IOContextWrapper{ioctx: ioctx},nil
}
type IOContexter interface {
Destroy()
GetImageNames() ([]string, error)
GetImage(name string) RBDImager
}
type IOContextWrapper struct {
ioctx *rados.IOContext
}
func (iocw IOContextWrapper) Destroy() {
iocw.ioctx.Destroy()
}
func (iocw IOContextWrapper) GetImageNames() ([]string,error){
return rbd.GetImageNames(iocw.ioctx)
}
func (iocw IOContextWrapper) GetImage(rbdName string) RBDImager {
img := rbd.GetImage(iocw.ioctx,rbdName)
return RBDImageWrapper{image: img}
}
type RBDImager interface {
Open() error
Close() error
Stat() (*rbd.ImageInfo,error)
}
type RBDImageWrapper struct {
image *rbd.Image
}
func (riw RBDImageWrapper) Open() error {
return riw.image.Open()
}
func (riw RBDImageWrapper) Close() error {
return riw.image.Close()
}
func (riw RBDImageWrapper) Stat() (*rbd.ImageInfo,error) {
return riw.image.Stat()
}
type iRBD interface { type iRBD interface {
getName() string GetName() string
getSize() int64 GetSize() int64
} }
type RBDUsage struct { type RBDUsage struct {
Images []RBD `json:"images"` Images []RBD
} }
type RBD struct { type RBD struct {
Name string `json:"name"` Name string
Id string `json:"id"` rbd.ImageInfo
RequestedSize uint64 `json:"provisioned_size"`
UsedSize int64 `json:"used_size"`
} }
func (r RBD) getName() string { func (r RBD) GetName() string {
return r.Name return r.Name
} }
func (r RBD) getSize() int64 { func (r RBD) GetSize() int64 {
return r.UsedSize return int64(r.Size)
} }
type Pool struct { type Pool struct {
Name string Name string
RBDlist []RBD HasRBD bool
// RBDlist []string RBDlist []iRBD
} }
type Metrics struct {
Total_rbd_requested_size_per_pool *prometheus.GaugeVec
Total_rbd_requested_size prometheus.Gauge
}
+3 -3
View File
@@ -5,7 +5,7 @@ COPY go.mod go.sum ./
RUN go mod download RUN go mod download
COPY . . COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o exporter . RUN CGO_ENABLED=0 GOOS=linux go build -o rbd-exporter .
FROM ubuntu:22.04 FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
@@ -14,7 +14,7 @@ RUN apt-get update && apt-get install -y \
WORKDIR /home/ WORKDIR /home/
COPY --from=builder /app/exporter . COPY --from=builder /app/rbd-exporter .
EXPOSE 9040 EXPOSE 9040
CMD [ "./exporter" ] CMD [ "./rbd-exporter" ]
+3
View File
@@ -0,0 +1,3 @@
module main
go 1.25.6
+40
View File
@@ -0,0 +1,40 @@
package main
import (
"fmt"
"sync"
)
type Res struct {
num int
name string
}
func Route(i int,k string, ch chan<- Res) {
ch <- Res{
num: i,
name: k,
}
}
func main() {
ch := make(chan Res,20)
var wg sync.WaitGroup
for i:= range 10 {
for _,k := range []string{"thr1","thr2"} {
wg.Add(1)
go func () {
defer wg.Done()
Route(i, k, ch)
}()
}
}
wg.Wait()
close(ch)
for i := range ch {
fmt.Println(i)
}
}
+3
View File
@@ -0,0 +1,3 @@
module mod_prj
go 1.25.5
+10
View File
@@ -0,0 +1,10 @@
package main
import "mod_prj/message"
func main() {
messages.Hello_en()
messages.Bye_en()
}
+11
View File
@@ -0,0 +1,11 @@
package messages
import "fmt"
func Hello_en() {
fmt.Println("Hello")
}
func Bye_en() {
fmt.Println("Good Bye")
}
+23
View File
@@ -0,0 +1,23 @@
# IntelliJ IDEA
.idea
*.iml
*.ipr
*.iws
# macOS
.DS_Store
# Golang
*.prof
*.exe
*.test
*.report
*.out
/bin/
/tmp/
/.build/
/vendor/
# test results
coverage.txt
coverage.xml
+45
View File
@@ -0,0 +1,45 @@
# IntelliJ IDEA
.idea/
*.iml
*.ipr
*.iws
dist/
# VSCode
.vscode/
# zed
.zed/
# macOS
.DS_Store
# Golang
*.prof
*.exe
*.test
*.report
*.out
/bin/
/tmp/
/.build/
/.devp/
/vendor/
go.work
go.work.sum
# test results
coverage.txt
coverage.xml
# Platform
scripts/golangci-merged.yml
graph.svg
devp_lint_report.json
.public
/dist/
# Other
*.tmp
.fuse_*
+72
View File
@@ -0,0 +1,72 @@
# Multi-stage build using official Go image
FROM harbor.mws-team.ru/docker.io/library/ubuntu:22.04 AS builder
# Add Ceph Reef repository
RUN apt-get update && apt-get install -y \
wget \
gnupg \
gcc \
pkg-config \
&& wget -q -O- 'https://download.ceph.com/keys/release.asc' | apt-key add - \
&& echo "deb https://download.ceph.com/debian-reef/ jammy main" > /etc/apt/sources.list.d/ceph.list
# Install Ceph Reef development libraries
RUN apt-get update && apt-get install -y \
librbd-dev \
librados-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Go
ENV GO_VERSION=1.25.5
RUN wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz \
&& tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz \
&& rm go${GO_VERSION}.linux-amd64.tar.gz
# Set Go environment variables
ENV PATH=$PATH:/usr/local/go/bin
ENV GOPATH=/go
ENV PATH=$PATH:$GOPATH/bin
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=1 GOOS=linux go build -o rbd-exporter .
# Final stage with Ceph Reef runtime libraries
FROM harbor.mws-team.ru/docker.io/library/ubuntu:22.04 AS final
# Add Ceph Reef repository to runtime stage too
RUN apt-get update && apt-get install -y \
wget \
gnupg \
lsb-release \
ca-certificates \
&& wget -q -O- 'https://download.ceph.com/keys/release.asc' | gpg --dearmor -o /etc/apt/trusted.gpg.d/ceph.gpg \
&& echo "deb https://download.ceph.com/debian-reef/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/ceph.list
# Install Ceph Reef runtime libraries
RUN apt-get update && apt-get install -y \
librbd-dev \
librados-dev \
ceph-common \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /rbd-exporter
# Copy the binary from builder stage
COPY --from=builder /app/rbd-exporter .
# Make binary executable
RUN chmod +x ./rbd-exporter
EXPOSE 9040
CMD ["./rbd-exporter"]
+25
View File
@@ -0,0 +1,25 @@
include scripts/devp/platform.mk
.PHONY: generate
generate: devp/generate-go
.PHONY: test
test: devp/test-go
.PHONY: lint
lint: devp/lint-go-default
.PHONY: format
format: devp/format-go
.PHONY: go-mod-tidy
go-mod-tidy: devp/go-mod-tidy
.PHONY: ci/check
ci/check: devp/ci/check
@# This target is called during Big Bang 'Validate' CI job
.PHONY: ci/test/unit
ci/test/unit: devp/coverage
@# This target is called during Big Bang 'Test' CI job
+39
View File
@@ -0,0 +1,39 @@
package connection
import (
"fmt"
"rbd_exporter/mytypes"
"github.com/ceph/go-ceph/rados"
)
func Connect(params mytypes.Params) (_ mytypes.CephConnection, err error) {
var cephConn = mytypes.CephConnection{}
defer func() {
if err != nil {
err = fmt.Errorf("error in func connect() %w", err)
}
}()
cephConn.Conn, err = rados.NewConnWithClusterAndUser("ceph", "client.admin")
if err != nil {
return mytypes.CephConnection{}, err
}
err = cephConn.Conn.ReadConfigFile(params.Config)
if err != nil {
return mytypes.CephConnection{}, err
}
err = cephConn.Conn.SetConfigOption("keyring", params.Keyring)
if err != nil {
return mytypes.CephConnection{}, err
}
err = cephConn.Conn.Connect()
if err != nil {
return mytypes.CephConnection{}, err
}
return cephConn, nil
}
+158
View File
@@ -0,0 +1,158 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: rbd-exporter
namespace: dev-unstable-rook-ceph-common
labels:
app: rbd-exporter
spec:
replicas: 1
selector:
matchLabels:
app: rbd-exporter
template:
metadata:
labels:
app: rbd-exporter
annotations:
obs.mws.ru/path: /metrics
obs.mws.ru/port: "9040"
obs.mws.ru/scheme: "http"
obs.mws.ru/scrape: "true"
obs.mws.ru/probe-scrape-interval: "1m"
spec:
imagePullSecrets:
- name: ceph-registry-secret
containers:
- name: rbd-exporter
image: harbor.mws-team.ru/mws-storage-junk/rbd-exporter:0.01
ports:
- containerPort: 9040
name: metrics
command:
- /bin/bash
- -c
- |
# Replicate the script from toolbox.sh inline so the ceph image
# can be run directly, instead of requiring the rook toolbox
CEPH_CONFIG="/etc/ceph/ceph.conf"
MON_CONFIG="/etc/rook/mon-endpoints"
KEYRING_FILE="/etc/ceph/keyring"
# create a ceph config file in its default location so ceph/rados tools can be used
# without specifying any arguments
write_endpoints() {
endpoints=$(cat ${MON_CONFIG})
# filter out the mon names
# external cluster can have numbers or hyphens in mon names, handling them in regex
# shellcheck disable=SC2001
mon_endpoints=$(echo "${endpoints}"| sed 's/[a-z0-9_-]\+=//g')
DATE=$(date)
echo "$DATE writing mon endpoints to ${CEPH_CONFIG}: ${endpoints}"
cat <<EOF > ${CEPH_CONFIG}
[global]
mon_host = ${mon_endpoints}
[client.admin]
keyring = ${KEYRING_FILE}
EOF
}
# watch the endpoints config file and update if the mon endpoints ever change
watch_endpoints() {
# get the timestamp for the target of the soft link
real_path=$(realpath ${MON_CONFIG})
initial_time=$(stat -c %Z "${real_path}")
while true; do
echo "I am watching for mon changes!!!"
real_path=$(realpath ${MON_CONFIG})
latest_time=$(stat -c %Z "${real_path}")
if [[ "${latest_time}" != "${initial_time}" ]]; then
write_endpoints
initial_time=${latest_time}
fi
sleep 10
done
}
# read the secret from an env var (for backward compatibility), or from the secret file
ceph_secret=${ROOK_CEPH_SECRET}
if [[ "$ceph_secret" == "" ]]; then
ceph_secret=$(cat /var/lib/rook-ceph-mon/secret.keyring)
fi
# create the keyring file
cat <<EOF > ${KEYRING_FILE}
[${ROOK_CEPH_USERNAME}]
key = ${ceph_secret}
EOF
# write the initial config file
write_endpoints
# run rbd-exporter
exec /rbd-exporter/rbd-exporter --keyring /etc/ceph/keyring &
# continuously update the mon endpoints if they fail over
watch_endpoints
imagePullPolicy: IfNotPresent
tty: true
securityContext:
runAsNonRoot: true
runAsUser: 2016
runAsGroup: 2016
capabilities:
drop: ["ALL"]
env:
- name: ROOK_CEPH_USERNAME
valueFrom:
secretKeyRef:
name: rook-ceph-mon
key: ceph-username
volumeMounts:
- mountPath: /etc/ceph
name: ceph-config
- name: mon-endpoint-volume
mountPath: /etc/rook
- name: ceph-admin-secret
mountPath: /var/lib/rook-ceph-mon
readOnly: true
volumes:
- name: ceph-admin-secret
secret:
secretName: rook-ceph-mon
optional: false
items:
- key: ceph-secret
path: secret.keyring
- name: mon-endpoint-volume
configMap:
name: rook-ceph-mon-endpoints
items:
- key: data
path: mon-endpoints
- name: ceph-config
emptyDir: {}
# ---
# apiVersion: v1
# kind: Service
# metadata:
# name: export-service
# namespace: rook-ceph
# labels:
# svc_app: export
# rook_cluster: rook-ceph
# spec:
# selector:
# app: export-box
# ports:
# - name: metrics
# port: 9040
# targetPort: 9040
# protocol: TCP
# type: ClusterIP
+32
View File
@@ -0,0 +1,32 @@
# DEVP это файл для описания того, что именно будет деплоится
version: 3
cloud:
service: "storage"
# Имя программы
rbd-exporter:
type: "cloud" #дефолтное значение, если обычный проект из гита
# Список стендов находится здесь https://git.mws-team.ru/mws/stands
# Список: dev, preprod, prod, stand24Q1(env STAGE), stress-common(env STRESS)
stands:
- "dev"
build:
template: "golang"
config:
# Версия билдера заполняется автоматически при обновлении (см. строчку ниже).
# Если нужно обновить руками - make devp/update
builder-version: 0.75.0
# По схемам смотреть тут https://confluence.mts.ru/pages/viewpage.action?pageId=1071927793#id-%D0%92%D0%B5%D1%80%D1%81%D0%B8%D0%BE%D0%BD%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D1%80%D0%B5%D0%BB%D0%B8%D0%B7%D0%BE%D0%B2-%D0%A1%D1%85%D0%B5%D0%BC%D0%B0app
version-schema: app
# Использовать харбор на harbor.mws-team.ru
use-new-harbor: true
pipeline:
# Путь в репе до главного файла проги
app-path: main.go
# Manual - запуск пайплайна вручную
mode: manual
docker:
# Про выбор канико читать тут https://mws.pages.mws-team.ru/devp/big-bang/generator/pages/reference/devp-yaml.html#infrastrukturnye-komponenty
kaniko:
# Выбирал самую свежую по отсутствию уязвимостей
builder-version: 2.3.1-1.23.2.1
context: .
+30
View File
@@ -0,0 +1,30 @@
module rbd_exporter
go 1.25.5
require (
github.com/prometheus/client_golang v1.23.2
go.uber.org/mock v0.6.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/ceph/go-ceph v0.37.0
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/stretchr/testify v1.11.1
go.uber.org/zap v1.27.1
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/sys v0.38.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
)
+55
View File
@@ -0,0 +1,55 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/ceph/go-ceph v0.37.0 h1:KXliBe3ZDr3/AtfY7n9d1MG7ippYNCVhMPcAgm05CFI=
github.com/ceph/go-ceph v0.37.0/go.mod h1:3y2tOlITlyuVFhy8v6PpCEfjMwKPfXJiH0/2hKZZQRE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
BIN
View File
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
package logger
import "go.uber.org/zap"
var Logger *zap.SugaredLogger
// SetLogger is called from init()
func SetLogger(l *zap.SugaredLogger) {
Logger = l
}
+98
View File
@@ -0,0 +1,98 @@
package main
import (
"errors"
"flag"
"fmt"
"net/http"
"rbd_exporter/connection"
"rbd_exporter/logger"
"rbd_exporter/metrics"
"rbd_exporter/mytypes"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Here I initialize logger
func loggerInit() *zap.SugaredLogger {
config := zap.NewDevelopmentConfig()
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, err := config.Build()
if err != nil {
panic(fmt.Sprintf("Logger set up failed: %v", err))
}
return logger.Sugar()
}
func globalInit() (mainlogger *zap.SugaredLogger,params mytypes.Params){
configFile := flag.String("config", "/etc/ceph/ceph.conf", "placement of ceph config file")
_keyring := flag.String("keyring", "/etc/ceph/ceph.client.admin.keyring", "placement of ceph keyring file")
flag.Parse()
params.Config = *configFile
params.Keyring = *_keyring
mainlogger = loggerInit()
logger.SetLogger(mainlogger)
mainlogger.Info("Setting up logger is complete successfully")
mainlogger.Info("Registering prom metrics")
return mainlogger,params
}
func main() {
mainlogger,params := globalInit()
defer func() {
err := logger.Logger.Sync()
if err != nil {
logger.Logger.Error("couldn't sync logger")
}
}()
cephConn, err := connection.Connect(params)
if err != nil {
logger.Logger.Error(err)
if wrapped := errors.Unwrap(err); wrapped != nil {
logger.Logger.Error(wrapped)
}
panic(err)
}
defer cephConn.Conn.Shutdown()
mainlogger.Info("Successfully connected to a cluster")
http.Handle("/metrics", promhttp.Handler())
server := http.Server{
Addr: ":9040",
ReadTimeout: 10 * time.Second,
Handler: http.DefaultServeMux,
}
// HTTP runs in separate thread cuz it blocks further execution of main
go func() {
fmt.Fprint()
mainlogger.Info("Starting http server")
// Here I check for errors if HTTP fails
if err := server.ListenAndServe(); err != nil {
mainlogger.Fatalf("HTTP server failed to start %v", err)
}
mainlogger.Info("HTTP server started")
}()
// if err := server.Shutdown(); err != nil {
// logger.Logger.Errorf("https server shutdown failed",err)
// }
go metrics.GetMetrics(cephConn)
select {}
}
+79
View File
@@ -0,0 +1,79 @@
package metrics
import (
"rbd_exporter/rbdfactory"
"rbd_exporter/mytypes"
"rbd_exporter/logger"
"time"
"github.com/prometheus/client_golang/prometheus"
)
func GetMetrics(cephConn mytypes.CephConnector) {
metrics := InitMetrics()
prometheus.MustRegister(
metrics.TotalRbdRequestedSizePerPool,
)
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
var result = []mytypes.Pool{}
poolList, err := cephConn.ListPools()
if err != nil {
logger.Logger.Error("Cannot get list of pools")
// do not exit but continue checking
continue
}
for _, v := range poolList {
x, _ := rbdfactory.PoolFactory(cephConn, v)
result = append(result, x)
}
metrics.TotalRbdRequestedSizePerPool.Reset()
for _,v := range result {
if !v.HasRBD {continue}
FillMetrics(v,metrics)
}
logger.Logger.Debug("====================")
// fmt.Println(result)
}
}
func InitMetrics() *mytypes.Metrics {
m := &mytypes.Metrics{
TotalRbdRequestedSizePerPool : prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "total_rbd_requested_size_per_pool",
Help: "total size of all requested RBDs in a specific pool",
},
[]string{"poolname"},
),
// total_rbd_requested_size: prometheus.NewGauge(
// prometheus.GaugeOpts{
// Name: "total_rbd_requested_size",
// Help: "total size of all RBDs in the cluster",
// },
// ),
}
return m
}
func FillMetrics(pool mytypes.Pool,metrics *mytypes.Metrics) {
var totalSizePerPool uint64 = 0
logger.Logger.Debugf("Processing pool %s",pool.Name)
for _,v := range pool.RBDlist {
totalSizePerPool += v.GetSize()
}
logger.Logger.Debugf("Total size of RBDs in pool %s is %d",pool.Name,totalSizePerPool)
metrics.TotalRbdRequestedSizePerPool.WithLabelValues(pool.Name,
).Set(
float64(totalSizePerPool))
}
@@ -0,0 +1,256 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: mytypes/types.go
//
// Generated by this command:
//
// mockgen -source=mytypes/types.go -destination=mytypes/types_mockgen_mock.go -package=mocks
//
// Package mocks is a generated GoMock package.
package mocks
import (
mytypes "rbd_exporter/mytypes"
reflect "reflect"
rbd "github.com/ceph/go-ceph/rbd"
gomock "go.uber.org/mock/gomock"
)
// MockCephConnector is a mock of CephConnector interface.
type MockCephConnector struct {
ctrl *gomock.Controller
recorder *MockCephConnectorMockRecorder
isgomock struct{}
}
// MockCephConnectorMockRecorder is the mock recorder for MockCephConnector.
type MockCephConnectorMockRecorder struct {
mock *MockCephConnector
}
// NewMockCephConnector creates a new mock instance.
func NewMockCephConnector(ctrl *gomock.Controller) *MockCephConnector {
mock := &MockCephConnector{ctrl: ctrl}
mock.recorder = &MockCephConnectorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCephConnector) EXPECT() *MockCephConnectorMockRecorder {
return m.recorder
}
// ListPools mocks base method.
func (m *MockCephConnector) ListPools() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListPools")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListPools indicates an expected call of ListPools.
func (mr *MockCephConnectorMockRecorder) ListPools() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPools", reflect.TypeOf((*MockCephConnector)(nil).ListPools))
}
// OpenIOContext mocks base method.
func (m *MockCephConnector) OpenIOContext(poolName string) (mytypes.IOContexter, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenIOContext", poolName)
ret0, _ := ret[0].(mytypes.IOContexter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenIOContext indicates an expected call of OpenIOContext.
func (mr *MockCephConnectorMockRecorder) OpenIOContext(poolName any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenIOContext", reflect.TypeOf((*MockCephConnector)(nil).OpenIOContext), poolName)
}
// MockIOContexter is a mock of IOContexter interface.
type MockIOContexter struct {
ctrl *gomock.Controller
recorder *MockIOContexterMockRecorder
isgomock struct{}
}
// MockIOContexterMockRecorder is the mock recorder for MockIOContexter.
type MockIOContexterMockRecorder struct {
mock *MockIOContexter
}
// NewMockIOContexter creates a new mock instance.
func NewMockIOContexter(ctrl *gomock.Controller) *MockIOContexter {
mock := &MockIOContexter{ctrl: ctrl}
mock.recorder = &MockIOContexterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIOContexter) EXPECT() *MockIOContexterMockRecorder {
return m.recorder
}
// Destroy mocks base method.
func (m *MockIOContexter) Destroy() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Destroy")
}
// Destroy indicates an expected call of Destroy.
func (mr *MockIOContexterMockRecorder) Destroy() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockIOContexter)(nil).Destroy))
}
// GetImage mocks base method.
func (m *MockIOContexter) GetImage(name string) mytypes.RBDImager {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetImage", name)
ret0, _ := ret[0].(mytypes.RBDImager)
return ret0
}
// GetImage indicates an expected call of GetImage.
func (mr *MockIOContexterMockRecorder) GetImage(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImage", reflect.TypeOf((*MockIOContexter)(nil).GetImage), name)
}
// GetImageNames mocks base method.
func (m *MockIOContexter) GetImageNames() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetImageNames")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetImageNames indicates an expected call of GetImageNames.
func (mr *MockIOContexterMockRecorder) GetImageNames() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImageNames", reflect.TypeOf((*MockIOContexter)(nil).GetImageNames))
}
// MockRBDImager is a mock of RBDImager interface.
type MockRBDImager struct {
ctrl *gomock.Controller
recorder *MockRBDImagerMockRecorder
isgomock struct{}
}
// MockRBDImagerMockRecorder is the mock recorder for MockRBDImager.
type MockRBDImagerMockRecorder struct {
mock *MockRBDImager
}
// NewMockRBDImager creates a new mock instance.
func NewMockRBDImager(ctrl *gomock.Controller) *MockRBDImager {
mock := &MockRBDImager{ctrl: ctrl}
mock.recorder = &MockRBDImagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRBDImager) EXPECT() *MockRBDImagerMockRecorder {
return m.recorder
}
// Close mocks base method.
func (m *MockRBDImager) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close.
func (mr *MockRBDImagerMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockRBDImager)(nil).Close))
}
// Open mocks base method.
func (m *MockRBDImager) Open() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Open")
ret0, _ := ret[0].(error)
return ret0
}
// Open indicates an expected call of Open.
func (mr *MockRBDImagerMockRecorder) Open() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockRBDImager)(nil).Open))
}
// Stat mocks base method.
func (m *MockRBDImager) Stat() (*rbd.ImageInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Stat")
ret0, _ := ret[0].(*rbd.ImageInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Stat indicates an expected call of Stat.
func (mr *MockRBDImagerMockRecorder) Stat() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockRBDImager)(nil).Stat))
}
// MockIRBD is a mock of IRBD interface.
type MockIRBD struct {
ctrl *gomock.Controller
recorder *MockIRBDMockRecorder
isgomock struct{}
}
// MockIRBDMockRecorder is the mock recorder for MockIRBD.
type MockIRBDMockRecorder struct {
mock *MockIRBD
}
// NewMockIRBD creates a new mock instance.
func NewMockIRBD(ctrl *gomock.Controller) *MockIRBD {
mock := &MockIRBD{ctrl: ctrl}
mock.recorder = &MockIRBDMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIRBD) EXPECT() *MockIRBDMockRecorder {
return m.recorder
}
// GetName mocks base method.
func (m *MockIRBD) GetName() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetName")
ret0, _ := ret[0].(string)
return ret0
}
// GetName indicates an expected call of GetName.
func (mr *MockIRBDMockRecorder) GetName() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockIRBD)(nil).GetName))
}
// GetSize mocks base method.
func (m *MockIRBD) GetSize() int64 {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSize")
ret0, _ := ret[0].(int64)
return ret0
}
// GetSize indicates an expected call of GetSize.
func (mr *MockIRBDMockRecorder) GetSize() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSize", reflect.TypeOf((*MockIRBD)(nil).GetSize))
}
+115
View File
@@ -0,0 +1,115 @@
package mytypes
import (
"github.com/ceph/go-ceph/rados"
"github.com/ceph/go-ceph/rbd"
"github.com/prometheus/client_golang/prometheus"
)
type Params struct {
Config string
Keyring string
}
type CephConnector interface {
ListPools() ([]string,error)
OpenIOContext(poolName string) (IOContexter, error)
}
type CephConnection struct {
Conn *rados.Conn
}
// Here I do implement CephConnector interface
func (cc CephConnection) ListPools() ([]string,error) {
return cc.Conn.ListPools()
}
// Here I do implement CephConnector interface
func (cc CephConnection) OpenIOContext(poolName string) (IOContexter,error) {
ioctx,err := cc.Conn.OpenIOContext(poolName)
if err != nil {
return nil,err
}
return IOContextWrapper{ioctx: ioctx},nil
}
type IOContexter interface {
Destroy()
GetImageNames() ([]string, error)
GetImage(name string) RBDImager
}
type IOContextWrapper struct {
ioctx *rados.IOContext
}
func (iocw IOContextWrapper) Destroy() {
iocw.ioctx.Destroy()
}
func (iocw IOContextWrapper) GetImageNames() ([]string,error){
return rbd.GetImageNames(iocw.ioctx)
}
func (iocw IOContextWrapper) GetImage(rbdName string) RBDImager {
img := rbd.GetImage(iocw.ioctx,rbdName)
return RBDImageWrapper{image: img}
}
type RBDImager interface {
Open() error
Close() error
Stat() (*rbd.ImageInfo,error)
}
type RBDImageWrapper struct {
image *rbd.Image
}
func (riw RBDImageWrapper) Open() error {
return riw.image.Open()
}
func (riw RBDImageWrapper) Close() error {
return riw.image.Close()
}
func (riw RBDImageWrapper) Stat() (*rbd.ImageInfo,error) {
return riw.image.Stat()
}
type IRBD interface {
GetName() string
GetSize() uint64
}
type RBDUsage struct {
Images []RBD
}
type RBD struct {
Name string
rbd.ImageInfo
}
func (r RBD) GetName() string {
return r.Name
}
func (r RBD) GetSize() uint64 {
return r.Size
}
type Pool struct {
Name string
HasRBD bool
RBDlist []IRBD
}
type Metrics struct {
TotalRbdRequestedSizePerPool *prometheus.GaugeVec
// Total_rbd_requested_size prometheus.Gauge
}
+57
View File
@@ -0,0 +1,57 @@
package rbdfactory
import (
"fmt"
"rbd_exporter/logger"
"rbd_exporter/mytypes"
)
func PoolFactory(cephConn mytypes.CephConnector, poolName string) (mytypes.Pool, error) {
var rbdlist = []mytypes.IRBD{}
ioctx, err := cephConn.OpenIOContext(poolName)
if err != nil {
return mytypes.Pool{}, fmt.Errorf("couldn't set context for pool %s %w", poolName, err)
}
defer ioctx.Destroy()
imageNames, err := ioctx.GetImageNames()
if err != nil {
return mytypes.Pool{}, fmt.Errorf("couldn't get list of rbds %w", err)
}
for _, rbdName := range imageNames {
stat, err := RBDFacroty(ioctx, rbdName)
if err != nil {
logger.Logger.Errorf("coundn't get stat from disk %s %w", rbdName, err)
}
rbdlist = append(rbdlist, stat)
}
return mytypes.Pool{
Name: poolName,
HasRBD: len(imageNames) != 0,
RBDlist: rbdlist,
}, nil
}
func RBDFacroty(ioctx mytypes.IOContexter, rbdName string) (mytypes.RBD, error) {
image := ioctx.GetImage(rbdName)
err := image.Open()
if err != nil {
logger.Logger.Errorf("Couldn't open RBD %s",rbdName)
// logger.Logger.Errorf("Shit happened in RBDFactory")
}
defer image.Close()
info,err := image.Stat()
if err != nil {
return mytypes.RBD{},fmt.Errorf("couldn't get stats for image %s %w",rbdName,err)
}
return mytypes.RBD{
Name: rbdName,
ImageInfo: *info,
},nil
}
+5
View File
@@ -0,0 +1,5 @@
BUILD_REL_PATH := .build
BUILD_PATH := ${PWD_PATH}/${BUILD_REL_PATH}
${BUILD_PATH}:
$(Q) mkdir -p $@
+37
View File
@@ -0,0 +1,37 @@
DEVP_CHANGIE_CONFIG_PATH := $(if $(DEVP_CHANGIE_CONFIG_PATH),$(DEVP_CHANGIE_CONFIG_PATH),"./.changes/.changie.yaml")
DEVP_CHANGIE_EDITOR := $(if $(DEVP_CHANGIE_EDITOR),$(DEVP_CHANGIE_EDITOR),$(EDITOR))
DEVP_CHANGIE_USE_EDITOR := $(if $(DEVP_CHANGIE_EDITOR),true,false)
DEVP_CHANGIE_CMD := CHANGIE_CONFIG_PATH=${DEVP_CHANGIE_CONFIG_PATH} EDITOR="${DEVP_CHANGIE_EDITOR}" ${DEVP_CHANGIE_EXE}
.PHONY: devp/changes/new
devp/changes/new: ${DEVP_CHANGIE_EXE}
@# Add changelog record
$(Q) ${DEVP_CHANGIE_CMD} new --editor=${DEVP_CHANGIE_USE_EDITOR} ${DEVP_CHANGIE_NEW_ARGS}
.PHONY: devp/changes/render
devp/changes/render: ${DEVP_CHANGIE_EXE}
@# Renders changelog. Debug target that allows developer to see how his changes will be displayed in a changelog
$(Q) ${DEVP_CHANGIE_CMD} batch patch --keep
.PHONY: devp/changes/release
devp/changes/release: ${DEVP_CHANGIE_EXE}
@# Renders changelog for release
$(Q) ${DEVP_CHANGIE_CMD} batch ${DEVP_CHANGIE_NEXT_VERSION}
.PHONY: devp/changes/merge
devp/changes/merge: ${DEVP_CHANGIE_EXE}
@# Merges last changelog to the main changelog file
$(Q) ${DEVP_CHANGIE_CMD} merge
.PHONY: devp/changes/latest
devp/changes/latest: ${DEVP_CHANGIE_EXE}
@# Get latest release version
$(Q) ${DEVP_CHANGIE_CMD} latest
.PHONY: devp/changes/get-text
devp/changes/get-text: ${DEVP_DEVPTOOL_EXE} ${DEVP_CHANGIE_EXE}
@# Generates latest release announce text and copies it to clipboard
$(Q) DEVP_CHANGIE_CONFIG_PATH=${DEVP_CHANGIE_CONFIG_PATH} \
DEVP_CHANGES_TITLE=${DEVP_CHANGES_TITLE} \
DEVP_CHANGES_FOOTER=${DEVP_CHANGES_FOOTER} \
${DEVP_DEVPTOOL_EXE} changes get-text
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
set -euo pipefail
COMMANDS=${1:-}
REPLACED_MESSAGE=${REPLACED_MESSAGE:-}
# Note that when running locally this will check for untracked files.
if [ -z "$(git status --porcelain)" ]; then
echo "OK, working tree is clean."
exit 0
fi
echo -e "\e[0Ksection_start:$(date +%s):git_status[collapsed=true]\r\e[0Kgit status"
git status
echo -e "\e[0Ksection_end:$(date +%s):git_status\r\e[0K"
echo -e "\e[0Ksection_start:$(date +%s):git_diff[collapsed=true]\r\e[0Kgit diff"
git diff
echo -e "\e[0Ksection_end:$(date +%s):git_diff\r\e[0K"
echo
if [ -n "$REPLACED_MESSAGE" ]; then
echo "❗$REPLACED_MESSAGE"
elif [ -n "$COMMANDS" ]; then
echo "❗Please run '$COMMANDS' locally and commit the changes."
else
echo "❗There are uncommitted changes. Check git diff/status above. Try running 'make devp/format-go devp/go-mod-tidy' locally and make sure everything is generated."
fi
exit 1
+27
View File
@@ -0,0 +1,27 @@
.PHONY: devp/check/git
devp/check/git:
@# Check that git tree is clean. You can pass $REPLACED_MESSAGE variable to replace message text when working tree is not clean
$(Q) echo "Checking that git tree is clean"
$(Q) ${DEVP_SCRIPTS}/check-clean-working-tree.sh
.PHONY: devp/ci/check
devp/ci/check: devp/ci/check/go-mod-tidy \
devp/ci/check/reinstall \
devp/ci/check/format-go \
devp/lint-go-default
@# Perform default platform CI checks
.PHONY: devp/ci/check/go-mod-tidy
devp/ci/check/go-mod-tidy: devp/go-mod-tidy
@# Check there are no changes after devp/go-mod-tidy
$(Q) ${DEVP_SCRIPTS}/check-clean-working-tree.sh "make devp/go-mod-tidy"
.PHONY: devp/ci/check/reinstall
devp/ci/check/reinstall: devp/reinstall
@# Check there are no changes in platform files
$(Q) REPLACED_MESSAGE="There are changes in platform files. Undo the changes in 'scripts/devp' folder." ${DEVP_SCRIPTS}/check-clean-working-tree.sh
.PHONY: devp/ci/check/format-go
devp/ci/check/format-go: devp/format-go
@# Check there is no changes after devp/format-go
$(Q) ${DEVP_SCRIPTS}/check-clean-working-tree.sh "make devp/format-go"
@@ -0,0 +1,24 @@
include scripts/devp/platform.mk
.PHONY: generate
generate: devp/generate-go
.PHONY: test
test: devp/test-go
.PHONY: lint
lint: devp/lint-go-default
.PHONY: format
format: devp/format-go
.PHONY: go-mod-tidy
go-mod-tidy: devp/go-mod-tidy
.PHONY: ci/check
ci/check: devp/ci/check
@# This target is called during Big Bang 'Validate' CI job
.PHONY: ci/test/unit
ci/test/unit: devp/coverage
@# This target is called during Big Bang 'Test' CI job
@@ -0,0 +1,12 @@
version: 3
cloud:
service: "my-service"
my-cloud-component:
type: "cloud"
stands:
- "dev"
build:
template: "golang"
config:
builder-version: 0.75.0
uses: []
@@ -0,0 +1,23 @@
# IntelliJ IDEA
.idea
*.iml
*.ipr
*.iws
# macOS
.DS_Store
# Golang
*.prof
*.exe
*.test
*.report
*.out
/bin/
/tmp/
/.build/
/vendor/
# test results
coverage.txt
coverage.xml
@@ -0,0 +1,44 @@
# IntelliJ IDEA
.idea/
*.iml
*.ipr
*.iws
dist/
# VSCode
.vscode/
# zed
.zed/
# macOS
.DS_Store
# Golang
*.prof
*.exe
*.test
*.report
*.out
/bin/
/tmp/
/.build/
/.devp/
/vendor/
go.work
go.work.sum
# test results
coverage.txt
coverage.xml
# Platform
scripts/golangci-merged.yml
graph.svg
devp_lint_report.json
.public
/dist/
# Other
*.tmp
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
set -euo pipefail
VERSION=0.75.0
SRC_DIR=$(realpath $(dirname -- "${BASH_SOURCE[0]}"))
# TODO(mitya): group for files created in docker container is still incorrect
mkdir -p ${HOME}/.devp/ci-builder-golang/${VERSION}/go
docker run -w /work \
-v $(pwd):/work \
-e HOST_USER=${USER} \
-e HOST_UID=$(id -u ${USER}) \
-e HOST_GID=$(id -g ${USER}) \
-v ${SRC_DIR}/dockerentry.sh:/dockerentry.sh:ro \
-v ${HOME}/.devp/ci-builder-golang/${VERSION}/go:/tmp/go:rw \
-e GOCACHE=/tmp/go/.cache \
-e GOMODCACHE=/tmp/go/.modcache \
-e GOLANGCI_LINT_CACHE=/tmp/go/.lintcache \
-e BIN_PATH=/work/bin/docker \
--rm -it harbor.mws-team.ru/mws-devp/infra/ci-builder-golang:${VERSION} \
/dockerentry.sh $@
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
set -euo pipefail
echo UID_MAX ${HOST_UID} >> /etc/login.defs
USER_HOME="/home/${HOST_USER}"
useradd -m -u ${HOST_UID} ${HOST_USER} -s /bin/bash
su ${HOST_USER} -m -c "HOME='$USER_HOME' PATH='$PATH' $*"
+24
View File
@@ -0,0 +1,24 @@
.PHONY: devp/format-go
devp/format-go: ${DEVP_FORMAT_EXE}
@# Run formatter for project
$(Q) ${DEVP_FORMAT_EXE} project ${VERBOSE_FLAG}
.PHONY: devp/format-go/vscode-enable
devp/format-go/vscode-enable: ${DEVP_DEVPTOOL_EXE} ${DEVP_FORMAT_EXE}
@# Enable platform formatting in VS Code
$(Q) ${DEVP_DEVPTOOL_EXE} golang configure-ide vscode formatter --tool-path=${DEVP_FORMAT_EXE}
.PHONY: devp/format-go/vscode-disable
devp/format-go/vscode-disable: ${DEVP_DEVPTOOL_EXE} ${DEVP_FORMAT_EXE}
@# Disable platform formatting in VS Code
$(Q) ${DEVP_DEVPTOOL_EXE} golang configure-ide vscode formatter --tool-path=${DEVP_FORMAT_EXE} --enable=false
.PHONY: devp/format-go/goland-enable
devp/format-go/goland-enable: ${DEVP_DEVPTOOL_EXE} ${DEVP_FORMAT_EXE}
@# Enable platform formatting in Goland
$(Q) ${DEVP_DEVPTOOL_EXE} golang configure-ide goland formatter --tool-path=${DEVP_FORMAT_EXE}
.PHONY: devp/format-go/goland-disable
devp/format-go/goland-disable: ${DEVP_DEVPTOOL_EXE} ${DEVP_FORMAT_EXE}
@# Disable platform formatting in Goland
$(Q) ${DEVP_DEVPTOOL_EXE} golang configure-ide goland formatter --tool-path=${DEVP_FORMAT_EXE} --enable=false
+4
View File
@@ -0,0 +1,4 @@
.PHONY: devp/generate-go
devp/generate-go:
@# Run 'go generate' for project
$(Q) go generate ./...
+9
View File
@@ -0,0 +1,9 @@
.PHONY: devp/godeviz/show
devp/godeviz/show: ${DEVP_GODEVIZ_EXE}
@# Show interactive dependency graph for the current project
$(Q) ${DEVP_GODEVIZ_EXE} show
.PHONY: devp/godeviz/svg
devp/godeviz/svg: ${DEVP_GODEVIZ_EXE}
@# Show dependency graph for the current project
$(Q) ${DEVP_GODEVIZ_EXE} svg
+4
View File
@@ -0,0 +1,4 @@
.PHONY: devp/go-mod-tidy
devp/go-mod-tidy:
@# Run 'go mod tidy' for project
$(Q) go mod tidy
+9
View File
@@ -0,0 +1,9 @@
.PHONY: devp/help
devp/help:
@# List all targets
$(Q) ${DEVP_SCRIPTS}/list-targets.sh $(MAKE) $(firstword $(MAKEFILE_LIST))
.PHONY: devp/help/short
devp/help/short:
@# List Makefile targets only
$(Q) ${DEVP_SCRIPTS}/list-targets.sh $(MAKE) $(firstword $(MAKEFILE_LIST)) true

Some files were not shown because too many files have changed in this diff Show More