Skip to content

Commit

Permalink
Add Golang CLI
Browse files Browse the repository at this point in the history
Adds a Golang CLI similar to the previous Bash scripts.
This is more portable, faster, and provide more features.

Additional features:
- Availabilities are show in list command
- Filter list by planCode and datacenters
- Show options descriptions
- Show datacenters full names with Cities
  • Loading branch information
TheoBrigitte committed Dec 1, 2024
1 parent 355d9d8 commit 7776c04
Show file tree
Hide file tree
Showing 43 changed files with 3,971 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
GROUP := github.com/TheoBrigitte
NAME := kimsufi-notifier

PKG := ${GROUP}/${NAME}

BIN := ${NAME}

PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/)

VERSION := $(shell git describe --always --long --dirty || date)
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/)

all: build

build:
GOOS=${OSTYPE} go build -v -o ${BIN} -ldflags=" \
-s -w \
-X github.com/prometheus/common/version.Version=$(shell git describe --tags) \
-X github.com/prometheus/common/version.Revision=$(shell git rev-parse HEAD) \
-X github.com/prometheus/common/version.Branch=$(shell git rev-parse --abbrev-ref HEAD) \
-X github.com/prometheus/common/version.BuildUser=$(shell whoami)@$(shell hostname) \
-X github.com/prometheus/common/version.BuildDate=$(shell date --utc +%FT%T)" \
${PKG}

install:
GOOS=${OSTYPE} go install -v -ldflags="-s -w -X main.Version=${VERSION}" ${PKG}

arm:
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ go build -v -o ${BIN} -ldflags=" \
-extldflags=-static -s -w \
-X github.com/prometheus/common/version.Version=$(shell git describe --tags) \
-X github.com/prometheus/common/version.Revision=$(shell git rev-parse HEAD) \
-X github.com/prometheus/common/version.Branch=$(shell git rev-parse --abbrev-ref HEAD) \
-X github.com/prometheus/common/version.BuildUser=$(shell whoami)@$(shell hostname) \
-X github.com/prometheus/common/version.BuildDate=$(shell date --utc +%FT%T)" \
${PKG}

test:
@go test -v ${PKG_LIST}

vet:
@go vet ${PKG_LIST}

lint:
@for file in ${GO_FILES} ; do \
golint $$file ; \
done

clean:
-@rm ${BIN}

.PHONY: build install test vet lint clean
147 changes: 147 additions & 0 deletions src/cmd/check/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package check

import (
"fmt"
"os"
"strings"
"text/tabwriter"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/TheoBrigitte/kimsufi-notifier/cmd/flag"
"github.com/TheoBrigitte/kimsufi-notifier/pkg/kimsufi"
kimsufiavailability "github.com/TheoBrigitte/kimsufi-notifier/pkg/kimsufi/availability"
kimsuficatalog "github.com/TheoBrigitte/kimsufi-notifier/pkg/kimsufi/catalog"
)

var (
Cmd = &cobra.Command{
Use: "check",
Short: "Check server availability",
Long: "Check OVH Eco (including Kimsufi) server availability\n\ndatacenters are the available datacenters for this plan",
RunE: runner,
}

// Flags variables
datacenters []string
options map[string]string
planCode string
humanLevel int
)

// init registers all flags
func init() {
flag.BindPlanCodeFlag(Cmd, &planCode)
flag.BindDatacentersFlag(Cmd, &datacenters)
flag.BindOptionsFlag(Cmd, &options)

Cmd.PersistentFlags().CountVarP(&humanLevel, "human", "h", "Human output, more h makes it better (e.g. -h, -hh)")
// Redefine help flag to only be a long --help flag, to avoid conflict with human flag
Cmd.Flags().Bool("help", false, "help for "+Cmd.Name())
}

// runner is the main function for the check command
func runner(cmd *cobra.Command, args []string) error {
// Initialize kimsufi service
endpoint := cmd.Flag(flag.OVHAPIEndpointFlagName).Value.String()
k, err := kimsufi.NewService(endpoint, log.StandardLogger(), nil)
if err != nil {
return fmt.Errorf("error: %w", err)
}

// Flag validation
if planCode == "" {
return fmt.Errorf("--%s is required", flag.PlanCodeFlagName)
}

var catalog *kimsuficatalog.Catalog
if humanLevel > 0 {
// Get the catalog to display human readable information.
catalog, err = k.ListServers(cmd.Flag(flag.CountryFlagName).Value.String())
if err != nil {
return fmt.Errorf("failed to list servers: %w", err)
}
}

// Check availability
availabilities, err := k.GetAvailabilities(datacenters, planCode, options)
if err != nil {
if kimsufi.IsNotAvailableError(err) {
message := datacenterAvailableMessageFormatter(datacenters)
log.Printf("%s is not available in %s\n", planCode, message)
return nil
}

return fmt.Errorf("error: %w", err)
}

// Display the server availabilities for each options.
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(w, "planCode\tmemory\tstorage\tstatus\tdatacenters")
fmt.Fprintln(w, "--------\t------\t-------\t------\t-----------")

nothingAvailable := true
for _, v := range *availabilities {
var (
name = v.PlanCode
memory = v.Memory
storage = v.Storage
datacenters = v.GetAvailableDatacenters()
)

if humanLevel > 0 {
plan := catalog.GetPlan(v.PlanCode)
if plan != nil {
names := strings.Split(plan.InvoiceName, " | ")
name = names[0]
}

memoryProduct := catalog.GetProduct(memory)
if memoryProduct != nil {
memory = memoryProduct.Description
}

storageProduct := catalog.GetProduct(storage)
if storageProduct != nil {
storage = storageProduct.Description
}
}

var datacenterNames []string
if humanLevel > 1 {
datacenterNames = datacenters.ToFullNamesOrCodes()
} else {
datacenterNames = datacenters.Codes()
}

var status = datacenters.Status()
if status == kimsufiavailability.StatusAvailable {
nothingAvailable = false
}

fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", name, memory, storage, status, strings.Join(datacenterNames, ", "))
}
w.Flush()

if nothingAvailable {
os.Exit(1)
}

return nil
}

func datacenterAvailableMessageFormatter(datacenters []string) string {
var message string

switch len(datacenters) {
case 0:
message = "any datacenter"
case 1:
message = datacenters[0] + " datacenter"
default:
message = strings.Join(datacenters, ", ") + " datacenters"
}

return message
}
60 changes: 60 additions & 0 deletions src/cmd/flag/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package flag

import (
"fmt"
"slices"
"strings"

"github.com/spf13/cobra"

"github.com/TheoBrigitte/kimsufi-notifier/pkg/category"
kimsufiavailability "github.com/TheoBrigitte/kimsufi-notifier/pkg/kimsufi/availability"
)

const (
CategoryFlagName = "category"

DatacentersFlagName = "datacenters"
DatacentersFlagShortName = "d"

OptionsFlagName = "options"
OptionsFlagShortName = "o"

HumanFlagName = "human"
HumanFlagShortName = "h"

PlanCodeFlagName = "plan-code"
PlanCodeFlagShortName = "p"
PlanCodeExample = "24ska01"
)

// BindCountryFlag binds the country flag to the provided cmd and value.
func BindCategoryFlag(cmd *cobra.Command, value *string) {
categories := slices.DeleteFunc(category.Names(), func(s string) bool {
return s == ""
})
allowedValues := strings.Join(categories, ", ")
cmd.PersistentFlags().StringVar(value, CategoryFlagName, "", fmt.Sprintf("category to filter on (allowed values: %s)", allowedValues))
}

// BindDatacentersFlag binds the datacenters flag to the provided cmd and value.
func BindDatacentersFlag(cmd *cobra.Command, value *[]string) {
cmd.PersistentFlags().StringSliceVarP(value, DatacentersFlagName, DatacentersFlagShortName, nil, fmt.Sprintf("datacenter(s) to filter on, comma separated list (known values: %s)", strings.Join(kimsufiavailability.GetDatacentersKnownCodes(), ", ")))
}

// BindOptionsFlag binds the datacenters flag to the provided cmd and value.
func BindOptionsFlag(cmd *cobra.Command, value *map[string]string) {
cmd.PersistentFlags().StringToStringVarP(value, OptionsFlagName, OptionsFlagShortName, nil, "options in key=value format, comma separated list, keys are column names (e.g. memory=ram-64g-noecc-2133)")
}

// BindHumanFlag binds the verbose flag to the provided cmd and value.
// Warning: this redefine the help flag to only be a long --help flag.
func BindHumanFlag(cmd *cobra.Command, value *int) {
cmd.PersistentFlags().CountVarP(value, HumanFlagName, HumanFlagShortName, "human output, more h makes it better (e.g. -h, -hh)")
cmd.Flags().Bool("help", false, "help for "+cmd.Name())
}

// BindPlanCodeFlag binds the plan code flag to the provided cmd and value.
func BindPlanCodeFlag(cmd *cobra.Command, value *string) {
cmd.PersistentFlags().StringVarP(value, PlanCodeFlagName, PlanCodeFlagShortName, "", fmt.Sprintf("plan code name (e.g. %s)", PlanCodeExample))
}
51 changes: 51 additions & 0 deletions src/cmd/flag/global.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package flag

import (
"bytes"
"fmt"
"strings"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/TheoBrigitte/kimsufi-notifier/pkg/kimsufi"
kimsufiregion "github.com/TheoBrigitte/kimsufi-notifier/pkg/kimsufi/region"
"github.com/TheoBrigitte/kimsufi-notifier/pkg/logger"
)

const (
LogLevelFlagName = "log-level"
LogLevelFlagShortName = "l"

OVHAPIEndpointFlagName = "endpoint"
OVHAPIEndpointFlagShortName = "e"
OVHAPIEndpointDefault = "ovh-eu"

CountryFlagName = "country"
CountryFlagShortName = "c"
CountryDefault = "FR"
)

// Bind binds the global flags to the provided cmd.
func Bind(cmd *cobra.Command) {
// Log level
cmd.PersistentFlags().StringP(LogLevelFlagName, LogLevelFlagShortName, log.ErrorLevel.String(), fmt.Sprintf("log level (allowed values: %s)", strings.Join(logger.AllLevelsString(), ", ")))

// OVH API Endpoint
cmd.PersistentFlags().StringP(OVHAPIEndpointFlagName, OVHAPIEndpointFlagShortName, OVHAPIEndpointDefault, fmt.Sprintf("OVH API Endpoint (allowed values: %s)", strings.Join(kimsufi.GetOVHEndpoints(), ", ")))

// Country
// Display all countries per endpoint
var output = &bytes.Buffer{}
for _, region := range kimsufiregion.AllowedRegions {
fmt.Fprintf(output, " %s: ", region.Endpoint)

countries := []string{}
for _, c := range region.Countries {
countries = append(countries, c.Code)
}
fmt.Fprintf(output, "%s\n", strings.Join(countries, ", "))
}

cmd.PersistentFlags().StringP(CountryFlagName, CountryFlagShortName, CountryDefault, fmt.Sprintf("country code, known values per endpoints:\n%s", output.String()))
}
Loading

0 comments on commit 7776c04

Please sign in to comment.