summary history files

internal/cli/metric.go
package cli

import (
	"context"
	"ct/internal/store"
	"database/sql"
	"fmt"
	"os"
	"time"

	_ "github.com/mattn/go-sqlite3" //nolint
	"github.com/olekukonko/tablewriter"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

func metricCmd(cli *cli) *cobra.Command {
	var cmd = &cobra.Command{
		Use: "metric",
	}

	cmd.AddCommand(configMetricCmd(cli))
	cmd.AddCommand(deleteMetricCmd(cli))
	cmd.AddCommand(listMetricCmd(cli))
	cmd.AddCommand(createMetricCmd(cli))

	return cmd
}

func configMetricCmd(cli *cli) *cobra.Command {
	var flags struct {
		DataType   string
		ValueText  string
		MetricType string
	}

	var cmd = &cobra.Command{
		Use: "configure",
		PreRun: func(cmd *cobra.Command, args []string) {
			_ = viper.BindPFlag("data-type", cmd.Flags().Lookup("data-type"))
			_ = viper.BindPFlag("value-text", cmd.Flags().Lookup("value-text"))
			_ = viper.BindPFlag("metric-type", cmd.Flags().Lookup("metric-type"))
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) != 1 {
				return fmt.Errorf("Missing metric")
			}
			m := args[0]

			db, err := sql.Open("sqlite3", cli.config.DBFile)
			if err != nil {
				return err
			}
			defer db.Close()

			s := store.NewStore(db)

			ctx := context.Background()
			metric, err := s.Metric.SelectOne(ctx, m)
			if err != nil && err != store.ErrNotFound {
				return err
			}
			if err != nil && err == store.ErrNotFound {
				return fmt.Errorf("Metric not found: %s", m)
			}

			if flags.ValueText != "" {
				config := store.NewConfig(metric.MetricID, "value_text", flags.ValueText)
				if err := s.Config.Upsert(ctx, config); err != nil {
					return err
				}
			}

			if flags.DataType != "" {
				config := store.NewConfig(metric.MetricID, "data_type", flags.DataType)
				if ok := config.IsDataTypeSupported(); !ok {
					return fmt.Errorf("Data type not supported")
				}
				if err := s.Config.Upsert(ctx, config); err != nil {
					return err
				}
			}

			if flags.MetricType != "" {
				config := store.NewConfig(metric.MetricID, "metric_type", flags.MetricType)
				if ok := config.IsMetricTypeSupported(); !ok {
					return fmt.Errorf("Metric type not supported")
				}
				if err := s.Config.Upsert(ctx, config); err != nil {
					return err
				}
			}

			return nil
		},
	}

	cmd.Flags().StringVar(&flags.DataType, "data-type", "", "Metric data type")
	cmd.Flags().StringVar(&flags.ValueText, "value-text", "", "Metric value text")
	cmd.Flags().StringVar(&flags.MetricType, "metric-type", "", "Metric type")

	return cmd
}

func deleteMetricCmd(cli *cli) *cobra.Command {
	var cmd = &cobra.Command{
		Use: "delete",
		PreRun: func(cmd *cobra.Command, args []string) {
			_ = viper.BindPFlag("metric-name", cmd.Flags().Lookup("metric-name"))
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) != 1 {
				return fmt.Errorf("Missing metric")
			}
			m := args[0]

			db, err := sql.Open("sqlite3", cli.config.DBFile)
			if err != nil {
				return err
			}
			defer db.Close()

			s := store.NewStore(db)

			ctx := context.Background()
			metric, err := s.Metric.SelectOne(ctx, m)
			if err != nil {
				return err
			}

			return s.Metric.Delete(ctx, metric.MetricID)
		},
	}
	return cmd
}

func listMetricCmd(cli *cli) *cobra.Command {
	var cmd = &cobra.Command{
		Use: "list",
		RunE: func(cmd *cobra.Command, args []string) error {
			db, err := sql.Open("sqlite3", cli.config.DBFile)
			if err != nil {
				return err
			}
			defer db.Close()

			s := store.NewStore(db)
			ctx := context.Background()
			metrics, err := s.Metric.SelectLimit(ctx, 0)
			if err != nil {
				return err
			}

			table := tablewriter.NewWriter(os.Stdout)
			table.SetHeader([]string{"Name", "Config", "Count", "Last"})

			for _, metric := range metrics {
				configDataType, err := s.Config.SelectOne(ctx, metric.MetricID, "data_type")
				if err != nil && err != store.ErrNotFound {
					return fmt.Errorf("Failed finding data_type config: %s", err)
				}
				configValueText, err := s.Config.SelectOne(ctx, metric.MetricID, "value_text")
				if err != nil && err != store.ErrNotFound {
					return fmt.Errorf("Failed finding value_text config: %s", err)
				}
				configMetricType, err := s.Config.SelectOne(ctx, metric.MetricID, "metric_type")
				if err != nil && err != store.ErrNotFound {
					return fmt.Errorf("Failed finding metric_type config: %s", err)
				}

				last30Days, err := s.Log.SelectWithTimestamp(ctx, metric.MetricID, time.Now().AddDate(0, 0, -30))
				if err != nil {
					return fmt.Errorf("Failed finding last 30 days of logs: %s", err)
				}

				lastLog, err := s.Log.SelectLast(ctx, metric.MetricID)
				if err != nil && err != store.ErrNotFound {
					return fmt.Errorf("Failed finding last log entry: %s", err)
				}
				lastLogFriendlyTimestamp := "None"
				if lastLog != nil {
					lastLogFriendlyTimestamp = lastLog.Timestamp.Format("2006-01-02")
				}

				configText := fmt.Sprintf("%s;%s;%s", configDataType, configMetricType, configValueText)
				table.Append([]string{metric.Name, configText, fmt.Sprintf("%d", len(last30Days)), lastLogFriendlyTimestamp})
			}

			table.Render()

			return nil
		},
	}
	return cmd
}

func createMetricCmd(cli *cli) *cobra.Command {
	var flags struct {
		DataType   string
		ValueText  string
		MetricType string
	}

	var cmd = &cobra.Command{
		Use: "create",
		PreRun: func(cmd *cobra.Command, args []string) {
			_ = viper.BindPFlag("data-type", cmd.Flags().Lookup("data-type"))
			_ = viper.BindPFlag("value-text", cmd.Flags().Lookup("value-text"))
			_ = viper.BindPFlag("metric-type", cmd.Flags().Lookup("metric-type"))
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			if len(args) != 1 {
				return fmt.Errorf("Missing metric")
			}
			m := args[0]

			db, err := sql.Open("sqlite3", cli.config.DBFile)
			if err != nil {
				return err
			}
			defer db.Close()

			s := store.NewStore(db)
			ctx := context.Background()

			metric, err := s.Metric.Create(ctx, m)
			if err != nil {
				return err
			}

			config := store.NewConfig(metric.MetricID, "data_type", flags.DataType)
			if ok := config.IsDataTypeSupported(); !ok {
				return fmt.Errorf("Data type not supported: %s", config.Val)
			}
			if err := s.Config.Upsert(ctx, config); err != nil {
				return err
			}

			config = store.NewConfig(metric.MetricID, "metric_type", flags.MetricType)
			if ok := config.IsMetricTypeSupported(); !ok {
				return fmt.Errorf("Metric type not supported: %s", config.Val)
			}
			if err := s.Config.Upsert(ctx, config); err != nil {
				return err
			}

			if flags.ValueText != "" {
				config = store.NewConfig(metric.MetricID, "value_text", flags.ValueText)
				if err := s.Config.Upsert(ctx, config); err != nil {
					return err
				}
			}

			return nil
		},
	}

	cmd.Flags().StringVar(&flags.DataType, "data-type", "float", "Metric data type")
	cmd.Flags().StringVar(&flags.MetricType, "metric-type", "gauge", "Metric type")
	cmd.Flags().StringVar(&flags.ValueText, "value-text", "", "Metric value text")

	return cmd
}