Skip to content

Refactor session #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions 2do.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,15 @@
- [ ] `dockeroller/docker/docker.go:29` We should get Docker ENVs from user in case of not default
- [ ] Add linter (golangci-linter to actions)
- [ ] Pass ID to session like the way dongi works, or maybe better!
- [ ] What happens if we have a job that permanently pull images list and containers list from docker and we use that instead of using the one we already have?
- [ ] When there are multiple tags for an image, there should be a possibility to remove only one of them (like docker desktop), not all at once

-----
In Telegram, we have a few UI components,
for example, Lists and Forms
Also we have some events that we can have actions on,
+ OnCallback, OnPhoto, OnQuery, etc.
+ Simple texts (handle through scene&question management)
+ ReplyMarkup keyboards text

We also need a session to store user state
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ sample-docker: # Run docker with sample start command

gen:
go generate ./...

start:
go run main.go start

11 changes: 8 additions & 3 deletions cmd/start.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package cmd

import (
"net/http"
"os"
"time"

"github.com/arshamalh/dockeroller/docker"
"github.com/arshamalh/dockeroller/entities"
"github.com/arshamalh/dockeroller/log"
"github.com/arshamalh/dockeroller/session"
tpkg "github.com/arshamalh/dockeroller/telegram"
"github.com/arshamalh/dockeroller/telegram/handlers"
"github.com/arshamalh/dockeroller/telegram/middlewares"
"github.com/joho/godotenv"
"github.com/spf13/cobra"
"gopkg.in/telebot.v3"
Expand Down Expand Up @@ -54,8 +57,10 @@ func start(token string, whitelistedIDs []int64) {

func startTelegram(docker docker.Docker, token string, whitelistedIDs []int64) {
bot, err := telebot.NewBot(telebot.Settings{
Token: token,
Poller: &telebot.LongPoller{Timeout: 10 * time.Second},
Token: token,
Poller: &telebot.LongPoller{Timeout: 10 * time.Second},
ParseMode: telebot.ModeMarkdownV2,
Client: &http.Client{Timeout: entities.CLIENT_TIMEOUT},
})
if err != nil {
log.Gl.Error(err.Error())
Expand All @@ -65,7 +70,7 @@ func startTelegram(docker docker.Docker, token string, whitelistedIDs []int64) {
// Middlewares
bot.Use(middleware.Whitelist(whitelistedIDs...))
// TODO: Disabled logger middleware for now.
// bot.Use(LoggerMiddleware(log.Gl))
bot.Use(middlewares.LoggerMiddleware(log.Gl))
if err := bot.SetCommands(tpkg.Commands); err != nil {
log.Gl.Error(err.Error())
}
Expand Down
43 changes: 24 additions & 19 deletions docker/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@ import (
"io"

"github.com/arshamalh/dockeroller/entities"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
)

func (d *docker) ContainersList(ctx context.Context, filters filters.Args) (containers []*entities.Container) {
raw_containers, _ := d.cli.ContainerList(ctx,
types.ContainerListOptions{
func (d *docker) ContainersList(ctx context.Context, filters filters.Args) ([]*entities.Container, error) {
raw_containers, err := d.cli.ContainerList(ctx,
container.ListOptions{
All: true,
Filters: filters,
},
)
if err != nil {
return nil, err
}

containers := make([]*entities.Container, 0)
for _, raw_cont := range raw_containers {
containers = append(containers, &entities.Container{
ID: raw_cont.ID,
Expand All @@ -25,11 +30,11 @@ func (d *docker) ContainersList(ctx context.Context, filters filters.Args) (cont
State: entities.ContainerState(raw_cont.State),
})
}
return
return containers, nil
}

func (d *docker) GetContainer(containerID string) (*entities.Container, error) {
container, err := d.cli.ContainerInspect(context.TODO(), containerID)
func (d *docker) GetContainer(ctx context.Context, containerID string) (*entities.Container, error) {
container, err := d.cli.ContainerInspect(ctx, containerID)
if err != nil {
return nil, err
}
Expand All @@ -42,36 +47,36 @@ func (d *docker) GetContainer(containerID string) (*entities.Container, error) {
}, nil
}

func (d *docker) ContainerStats(containerID string) (io.ReadCloser, error) {
stats, err := d.cli.ContainerStats(context.TODO(), containerID, true)
func (d *docker) ContainerStats(ctx context.Context, containerID string) (io.ReadCloser, error) {
stats, err := d.cli.ContainerStats(ctx, containerID, true)
return stats.Body, err
}

func (d *docker) ContainerLogs(containerID string) (io.ReadCloser, error) {
func (d *docker) ContainerLogs(ctx context.Context, containerID string) (io.ReadCloser, error) {
// TODO: Interesting options about logs are available, you can get them from user settings
return d.cli.ContainerLogs(context.TODO(), containerID, types.ContainerLogsOptions{
return d.cli.ContainerLogs(ctx, containerID, container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
Details: false,
})
}

func (d *docker) ContainerStart(containerID string) error {
return d.cli.ContainerStart(context.TODO(), containerID, types.ContainerStartOptions{})
func (d *docker) ContainerStart(ctx context.Context, containerID string) error {
return d.cli.ContainerStart(ctx, containerID, container.StartOptions{})
}

func (d *docker) ContainerStop(containerID string) error {
return d.cli.ContainerStop(context.TODO(), containerID, nil)
func (d *docker) ContainerStop(ctx context.Context, containerID string) error {
return d.cli.ContainerStop(ctx, containerID, container.StopOptions{})
}

func (d *docker) ContainerRemove(containerID string, removeForm *entities.ContainerRemoveForm) error {
return d.cli.ContainerRemove(context.TODO(), containerID, types.ContainerRemoveOptions{
func (d *docker) ContainerRemove(ctx context.Context, containerID string, removeForm *entities.ContainerRemoveForm) error {
return d.cli.ContainerRemove(ctx, containerID, container.RemoveOptions{
RemoveVolumes: removeForm.RemoveVolumes,
Force: removeForm.Force,
})
}

func (d *docker) ContainerRename(containerID, newName string) error {
return d.cli.ContainerRename(context.TODO(), containerID, newName)
func (d *docker) ContainerRename(ctx context.Context, containerID, newName string) error {
return d.cli.ContainerRename(ctx, containerID, newName)
}
25 changes: 15 additions & 10 deletions docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,27 @@ import (
)

type Docker interface {
GetContainer(containerID string) (*entities.Container, error)
ContainersList(ctx context.Context, filters filters.Args) (containers []*entities.Container)
ContainerLogs(containerID string) (io.ReadCloser, error)
ContainerStats(containerID string) (io.ReadCloser, error)
ContainerStart(containerID string) error
ContainerStop(containerID string) error
ContainerRemove(containerID string, removeForm *entities.ContainerRemoveForm) error
ContainerRename(containerID, newName string) error
// TODO: Add context to these functions, as they all accept context in their lower level
GetContainer(ctx context.Context, containerID string) (*entities.Container, error)
ContainersList(ctx context.Context, filters filters.Args) ([]*entities.Container, error)
// TODO: context for these one is so important as we might be able to remove quite channel by its help
ContainerLogs(ctx context.Context, containerID string) (io.ReadCloser, error)
ContainerStats(ctx context.Context, containerID string) (io.ReadCloser, error)
ContainerStart(ctx context.Context, containerID string) error
ContainerStop(ctx context.Context, containerID string) error
ContainerRemove(ctx context.Context, containerID string, removeForm *entities.ContainerRemoveForm) error
ContainerRename(ctx context.Context, containerID, newName string) error

ImagesList() []*entities.Image
ImagesList(ctx context.Context, filters filters.Args) ([]*entities.Image, error)
ImageTag(ctx context.Context, imageID, newTag string) error
ImageRemove(ctx context.Context, imageID string, force, pruneChildren bool) error
}

func New() *docker {
cli, err := client.NewClientWithOpts(client.FromEnv)
cli, err := client.NewClientWithOpts(
client.FromEnv,
client.WithAPIVersionNegotiation(),
)
if err != nil {
log.Gl.Error(err.Error())
}
Expand Down
40 changes: 33 additions & 7 deletions docker/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import (
"time"

"github.com/arshamalh/dockeroller/entities"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
)

func (d *docker) ImagesList() (images []*entities.Image) {
rawImages, _ := d.cli.ImageList(context.TODO(), types.ImageListOptions{All: true})
func (d *docker) ImagesList(ctx context.Context, filters filters.Args) ([]*entities.Image, error) {
rawImages, err := d.cli.ImageList(context.TODO(), image.ListOptions{All: true})
if err != nil {
return nil, err
}

images := make([]*entities.Image, 0)
for _, rawImg := range rawImages {
status, containers := d.getImageStatus(context.TODO(), rawImg)
images = append(images, &entities.Image{
Expand All @@ -23,7 +28,25 @@ func (d *docker) ImagesList() (images []*entities.Image) {
CreatedAt: fmt.Sprint(time.Unix(rawImg.Created, 0).Format("2006-01-02 15:04:05")),
})
}
return
return images, nil
}

func (d *docker) GetImage(ctx context.Context, imageID string) (*entities.Image, error) {
image, _, err := d.cli.ImageInspectWithRaw(ctx, imageID)
if err != nil {
return nil, err
}

return &entities.Image{
ID: image.ID,
Size: image.Size,
CreatedAt: image.Created,
Tags: image.RepoTags,
// TODO: not sure what to do with status and usedBy as they are not working the same as ImagesList is.
// Status: entities.ImageStatus(image),
// UsedBy: ,
}, nil

}

func (d *docker) ImageTag(ctx context.Context, imageID, newTag string) error {
Expand All @@ -32,7 +55,7 @@ func (d *docker) ImageTag(ctx context.Context, imageID, newTag string) error {

func (d *docker) ImageRemove(ctx context.Context, imageID string, force, pruneChildren bool) error {
_, err := d.cli.ImageRemove(ctx, imageID,
types.ImageRemoveOptions{
image.RemoveOptions{
Force: force,
PruneChildren: pruneChildren,
},
Expand All @@ -42,7 +65,7 @@ func (d *docker) ImageRemove(ctx context.Context, imageID string, force, pruneCh

// Returns whether an image is dangling or used by containers,
// returns a list of ContainerIDs as the second argument, or nil if the Image is dangling or unused
func (d *docker) getImageStatus(ctx context.Context, image types.ImageSummary) (entities.ImageStatus, []*entities.Container) {
func (d *docker) getImageStatus(ctx context.Context, image image.Summary) (entities.ImageStatus, []*entities.Container) {
if len(image.RepoTags) == 0 {
return entities.ImageStatusUnUsedDangling, nil
}
Expand All @@ -52,7 +75,10 @@ func (d *docker) getImageStatus(ctx context.Context, image types.ImageSummary) (
return entities.ImageStatusUnUsedDangling, nil
}

containers := d.ContainersList(ctx, filters.NewArgs(filters.Arg("ancestor", image.ID)))
containers, err := d.ContainersList(ctx, filters.NewArgs(filters.Arg("ancestor", image.ID)))
if err != nil {
// TODO: Handle the error, maybe we should not have this call here
}
if len(containers) == 0 {
return entities.ImageStatusUnUsed, nil
}
Expand Down
13 changes: 13 additions & 0 deletions entities/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package entities

import "time"

const (
LEN_IMG_TRIM int = 12
LEN_CONT_TRIM int = 12
LOGS_QUEUE_LEN int = 10
STATES_PULL_INTERVAL time.Duration = time.Second
// Sleeping time, not too much, not so little (under 500 millisecond would be annoying)
LOGS_PULL_INTERVAL time.Duration = 500 * time.Millisecond
CLIENT_TIMEOUT time.Duration = 30 * time.Second
)
23 changes: 18 additions & 5 deletions entities/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ const (
)

type Container struct {
ID string
Name string
Image string
Status string
State ContainerState
ID string
Name string
Image string
Status string
State ContainerState
RemoveForm *ContainerRemoveForm
}

func (c Container) String() string {
Expand All @@ -29,3 +30,15 @@ func (c Container) String() string {
func (c Container) IsOn() bool {
return c.State == ContainerStateRunning
}

func (c *Container) On() {
c.State = ContainerStateRunning
}

func (c *Container) Off() {
c.State = ContainerStateExited
}

func (c *Container) ShortID() string {
return c.ID[:LEN_CONT_TRIM]
}
18 changes: 17 additions & 1 deletion entities/image.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package entities

import (
"fmt"

"github.com/arshamalh/dockeroller/tools"
)

type ImageStatus string

const (
Expand All @@ -15,5 +21,15 @@ type Image struct {
Status ImageStatus
CreatedAt string
// A list of Containers using this image
UsedBy []*Container
UsedBy []*Container
RemoveForm *ImageRemoveForm
}

func (img Image) String() string {
size := tools.SizeToHumanReadable(img.Size)
return fmt.Sprintf("%s - %s - %s - created at: %s", img.ID, size, img.Status, img.CreatedAt)
}

func (img *Image) ShortID() string {
return img.ID[:LEN_IMG_TRIM]
}
14 changes: 0 additions & 14 deletions entities/scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,3 @@ const (
SceneRenameContainer Scene = iota + 1
SceneRenameImage
)

type Question int

const (
QNewContainerName Question = iota + 1
)

const (
QNewImageName Question = iota + 1
)

func (q Question) NextQuestion() Question {
return q + 1
}
Loading
Loading