diff --git a/.gitignore b/.gitignore index 0bb65833..3136a424 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,22 @@ -log-cache-cli -cf-lc-plugin -*.test -main +# Builds +bin + +# IntelliJ +.idea/ + +# macOS +.DS_Store + +# Vim files +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] +Session.vim +Sessionx.vim +.netrwhist +*~ +tags +[._]*.un~ diff --git a/README.md b/README.md index 8cb2bc42..17573329 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ NAME: tail - Output logs for a source-id/app USAGE: - tail [options] + tail [options] SOURCE_ID ENVIRONMENT VARIABLES: LOG_CACHE_ADDR Overrides the default location of log-cache. @@ -94,14 +94,14 @@ NAME: query - Issues a PromQL query against Log Cache USAGE: - query [options] + query PROMQL_QUERY [options] ENVIRONMENT VARIABLES: LOG_CACHE_ADDR Overrides the default location of log-cache. LOG_CACHE_SKIP_AUTH Set to 'true' to disable CF authentication. OPTIONS: - --end End time for a range query. Cannont be used with --time. Can be a unix timestamp or RFC3339. + --end End time for a range query. Cannot be used with --time. Can be a unix timestamp or RFC3339. --start Start time for a range query. Cannont be used with --time. Can be a unix timestamp or RFC3339. --step Step interval for a range query. Cannot be used with --time. --time Effective time for query execution of an instant query. Cannont be used with --start, --end, or --step. Can be a unix timestamp or RFC3339. diff --git a/cmd/cf-lc-plugin/.gitignore b/cmd/cf-lc-plugin/.gitignore deleted file mode 100644 index f7f7c15b..00000000 --- a/cmd/cf-lc-plugin/.gitignore +++ /dev/null @@ -1 +0,0 @@ -cf-lc-plugin diff --git a/cmd/cf-lc-plugin/cf_log_cache_plugin_suite_test.go b/cmd/cf-lc-plugin/cf_log_cache_plugin_suite_test.go deleted file mode 100644 index 3d50f2c3..00000000 --- a/cmd/cf-lc-plugin/cf_log_cache_plugin_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package main_test - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "testing" -) - -func TestLogCacheCli(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "CF Log Cache Plugin Suite") -} diff --git a/internal/command/command.go b/internal/command/command.go new file mode 100644 index 00000000..e7bc3012 --- /dev/null +++ b/internal/command/command.go @@ -0,0 +1,65 @@ +package command + +import ( + "net/http" + "os" + "strings" + + "code.cloudfoundry.org/cli/plugin" + logcache "code.cloudfoundry.org/go-log-cache" +) + +// Logger is used for outputting log-cache results and errors +type Logger interface { + Fatalf(format string, args ...interface{}) + Printf(format string, args ...interface{}) +} + +// HTTPClient is the client used for HTTP requests +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +type tokenHTTPClient struct { + c HTTPClient + tokenFunc func() string +} + +func (c *tokenHTTPClient) Do(req *http.Request) (*http.Response, error) { + accessToken := c.tokenFunc() + if len(accessToken) > 0 { + req.Header.Set("Authorization", accessToken) + } + + return c.c.Do(req) + +} + +func newLogCacheClient(c HTTPClient, log Logger, cli plugin.CliConnection) *logcache.Client { + addr := os.Getenv("LOG_CACHE_ADDR") + if addr == "" { + addrAPI, err := cli.ApiEndpoint() + if err != nil { + log.Fatalf("Could not determine Log Cache endpoint: %s", err) + } + addr = strings.Replace(addrAPI, "api", "log-cache", 1) + } + + if strings.ToLower(os.Getenv("LOG_CACHE_SKIP_AUTH")) != "true" { + c = &tokenHTTPClient{ + c: c, + tokenFunc: func() string { + token, err := cli.AccessToken() + if err != nil { + log.Fatalf("Unable to get Access Token: %s", err) + } + return token + }, + } + } + + return logcache.NewClient( + addr, + logcache.WithHTTPClient(c), + ) +} diff --git a/pkg/command/cf/command_suite_test.go b/internal/command/command_suite_test.go similarity index 95% rename from pkg/command/cf/command_suite_test.go rename to internal/command/command_suite_test.go index 891e7caa..f68f890f 100644 --- a/pkg/command/cf/command_suite_test.go +++ b/internal/command/command_suite_test.go @@ -1,4 +1,4 @@ -package cf_test +package command_test import ( "errors" @@ -9,7 +9,7 @@ import ( "sync" "code.cloudfoundry.org/cli/plugin" - "code.cloudfoundry.org/cli/plugin/models" + plugin_models "code.cloudfoundry.org/cli/plugin/models" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -190,10 +190,3 @@ func (s *stubCliConnection) AccessToken() (string, error) { s.accessTokenCount++ return s.accessToken, s.accessTokenErr } - -func (s *stubCliConnection) getAccessTokenCount() int { - s.Lock() - defer s.Unlock() - - return s.accessTokenCount -} diff --git a/pkg/command/cf/formatter.go b/internal/command/formatter.go similarity index 97% rename from pkg/command/cf/formatter.go rename to internal/command/formatter.go index 6761d964..b85def69 100644 --- a/pkg/command/cf/formatter.go +++ b/internal/command/formatter.go @@ -1,4 +1,4 @@ -package cf +package command import ( "bytes" @@ -122,7 +122,7 @@ func (f prettyFormatter) sourceHeader(sourceID, _, _, user string) (string, bool } func (f prettyFormatter) formatEnvelope(e *loggregator_v2.Envelope) (string, bool) { - return fmt.Sprintf("%s", envelopeWrapper{sourceID: f.sourceID, Envelope: e, newLine: f.newLine}), true + return envelopeWrapper{sourceID: f.sourceID, Envelope: e, newLine: f.newLine}.String(), true } type jsonFormatter struct { @@ -251,7 +251,7 @@ func (e envelopeWrapper) String() string { values = append(values, fmt.Sprintf("%s:%f %s", k, v.Value, v.Unit)) } - sort.Sort(sort.StringSlice(values)) + sort.Strings(values) return fmt.Sprintf("%sGAUGE %s", e.header(ts), diff --git a/pkg/command/cf/meta.go b/internal/command/meta.go similarity index 88% rename from pkg/command/cf/meta.go rename to internal/command/meta.go index beac722d..32250c06 100644 --- a/pkg/command/cf/meta.go +++ b/internal/command/meta.go @@ -1,20 +1,17 @@ -package cf +package command import ( "context" "encoding/json" "fmt" "io" - "os" "regexp" "sort" - "strconv" "strings" "text/tabwriter" "time" "code.cloudfoundry.org/cli/plugin" - logcache "code.cloudfoundry.org/go-log-cache" logcache_v1 "code.cloudfoundry.org/go-log-cache/rpc/logcache_v1" flags "github.com/jessevdk/go-flags" ) @@ -106,7 +103,6 @@ func WithMetaNoiseSleepDuration(d time.Duration) MetaOption { // Meta returns the metadata from Log Cache func Meta( - ctx context.Context, cli plugin.CliConnection, args []string, c HTTPClient, @@ -115,7 +111,7 @@ func Meta( mopts ...MetaOption, ) { opts := getOptions(args, log, mopts...) - client := createLogCacheClient(c, log, cli) + client := newLogCacheClient(c, log, cli) tw := tabwriter.NewWriter(tableWriter, 0, 2, 2, ' ', 0) username, err := cli.Username() if err != nil { @@ -125,7 +121,7 @@ func Meta( var originalMeta map[string]*logcache_v1.MetaInfo var currentMeta map[string]*logcache_v1.MetaInfo writeRetrievingMetaHeader(opts, tw, username) - currentMeta, err = client.Meta(ctx) + currentMeta, err = client.Meta(context.Background()) if err != nil { log.Fatalf("Failed to read Meta information: %s", err) } @@ -135,7 +131,7 @@ func Meta( writeWaiting(opts, tw, username) time.Sleep(opts.metaNoiseSleepDuration) writeRetrievingMetaHeader(opts, tw, username) - currentMeta, err = client.Meta(ctx) + currentMeta, err = client.Meta(context.Background()) if err != nil { log.Fatalf("Failed to read Meta information: %s", err) } @@ -227,31 +223,6 @@ type displayRow struct { Delta int64 } -func createLogCacheClient(c HTTPClient, log Logger, cli plugin.CliConnection) *logcache.Client { - logCacheEndpoint, err := logCacheEndpoint(cli) - if err != nil { - log.Fatalf("Could not determine Log Cache endpoint: %s", err) - } - - if strings.ToLower(os.Getenv("LOG_CACHE_SKIP_AUTH")) != "true" { - c = &tokenHTTPClient{ - c: c, - tokenFunc: func() string { - token, err := cli.AccessToken() - if err != nil { - log.Fatalf("Unable to get Access Token: %s", err) - } - return token - }, - } - } - - return logcache.NewClient( - logCacheEndpoint, - logcache.WithHTTPClient(c), - ) -} - func tableFormat(opts optionsFlags, row displayRow) (string, []interface{}) { tableFormat := "%d\t%d\t%s\n" items := []interface{}{interface{}(row.Count), interface{}(row.Expired), interface{}(row.CacheDuration)} @@ -274,19 +245,21 @@ func tableFormat(opts optionsFlags, row displayRow) (string, []interface{}) { func writeRetrievingMetaHeader(opts optionsFlags, tableWriter io.Writer, username string) { if opts.withHeaders { - fmt.Fprintf(tableWriter, fmt.Sprintf( + fmt.Fprintf( + tableWriter, "Retrieving log cache metadata as %s...\n\n", username, - )) + ) } } func writeAppsAndServicesHeader(opts optionsFlags, tableWriter io.Writer, username string) { if opts.withHeaders { - fmt.Fprintf(tableWriter, fmt.Sprintf( + fmt.Fprintf( + tableWriter, "Retrieving app and service names as %s...\n\n", username, - )) + ) } } @@ -375,18 +348,6 @@ func getOptions(args []string, log Logger, mopts ...MetaOption) optionsFlags { return opts } -func displayRate(rate int) string { - var output string - - if rate >= MaximumBatchSize { - output = fmt.Sprintf(">%d", MaximumBatchSize-1) - } else { - output = strconv.Itoa(rate) - } - - return output -} - func sortRows(opts optionsFlags, rows []displayRow) { switch opts.SortBy { case string(sortBySourceID): @@ -530,32 +491,6 @@ func maxDuration(a, b time.Duration) time.Duration { return a } -func truncate(count int, entries map[string]*logcache_v1.MetaInfo) map[string]*logcache_v1.MetaInfo { - truncated := make(map[string]*logcache_v1.MetaInfo) - for k, v := range entries { - if len(truncated) >= count { - break - } - truncated[k] = v - } - return truncated -} - -func logCacheEndpoint(cli plugin.CliConnection) (string, error) { - logCacheAddr := os.Getenv("LOG_CACHE_ADDR") - - if logCacheAddr != "" { - return logCacheAddr, nil - } - - apiEndpoint, err := cli.ApiEndpoint() - if err != nil { - return "", err - } - - return strings.Replace(apiEndpoint, "api", "log-cache", 1), nil -} - func invalidSourceType(st string) bool { validSourceTypes := []sourceType{ sourceTypePlatform, diff --git a/pkg/command/cf/meta_test.go b/internal/command/meta_test.go similarity index 96% rename from pkg/command/cf/meta_test.go rename to internal/command/meta_test.go index 3367198f..13a6d5e9 100644 --- a/pkg/command/cf/meta_test.go +++ b/internal/command/meta_test.go @@ -1,15 +1,14 @@ -package cf_test +package command_test import ( "bytes" - "context" "errors" "fmt" "net/url" "os" "strings" - "code.cloudfoundry.org/log-cache-cli/v4/pkg/command/cf" + cf "code.cloudfoundry.org/log-cache-cli/v4/internal/command" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -57,7 +56,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, []string{"--noise", "--sort-by", "rate"}, httpClient, @@ -115,7 +113,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, []string{"--sort-by", "source-type"}, httpClient, @@ -162,7 +159,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, []string{"--sort-by", "count"}, httpClient, @@ -210,7 +206,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, []string{"--sort-by", "expired"}, httpClient, @@ -258,7 +253,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, []string{"--sort-by", "cache-duration"}, httpClient, @@ -291,7 +285,6 @@ var _ = Describe("Meta", func() { It("fatally logs when --sort-by is not valid", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, []string{"--sort-by", "invalid"}, httpClient, @@ -306,7 +299,6 @@ var _ = Describe("Meta", func() { It("fatally logs when --source-type other than 'platform' is used with --guid", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, []string{"--guid", "--source-type", "not-platform"}, httpClient, @@ -321,7 +313,6 @@ var _ = Describe("Meta", func() { It("fatally logs when --sort-by source is used with --guid", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, []string{"--guid", "--sort-by", "source"}, httpClient, @@ -336,7 +327,6 @@ var _ = Describe("Meta", func() { It("fatally logs when --sort-by source-type is used with --guid", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, []string{"--guid", "--sort-by", "source-type"}, httpClient, @@ -351,7 +341,6 @@ var _ = Describe("Meta", func() { It("fatally logs when --sort-by source-type is used with --guid", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, []string{"--guid", "--sort-by", "source-type"}, httpClient, @@ -366,7 +355,6 @@ var _ = Describe("Meta", func() { It("fatally logs when --sort-by rate is used without --noise", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, []string{"--sort-by", "rate"}, httpClient, @@ -395,7 +383,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, []string{"--guid"}, httpClient, @@ -436,7 +423,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, []string{"--guid"}, httpClient, @@ -465,7 +451,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -522,7 +507,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, []string{"--noise"}, httpClient, @@ -575,7 +559,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -641,7 +624,6 @@ var _ = Describe("Meta", func() { args := []string{"--source-type", "application"} cf.Meta( - context.Background(), cliConn, args, httpClient, @@ -687,7 +669,6 @@ var _ = Describe("Meta", func() { args := []string{"--source-type", "service"} cf.Meta( - context.Background(), cliConn, args, httpClient, @@ -733,7 +714,6 @@ var _ = Describe("Meta", func() { args := []string{"--source-type", "PLATFORM"} cf.Meta( - context.Background(), cliConn, args, httpClient, @@ -778,7 +758,6 @@ var _ = Describe("Meta", func() { args := []string{"--source-type", "all"} cf.Meta( - context.Background(), cliConn, args, httpClient, @@ -824,7 +803,6 @@ var _ = Describe("Meta", func() { args := []string{"--source-type", "unknown"} cf.Meta( - context.Background(), cliConn, args, httpClient, @@ -868,7 +846,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -913,7 +890,6 @@ var _ = Describe("Meta", func() { args := []string{"--guid"} cf.Meta( - context.Background(), cliConn, args, httpClient, @@ -954,7 +930,6 @@ var _ = Describe("Meta", func() { args := []string{"--source-type", "PLATFORM", "--guid"} cf.Meta( - context.Background(), cliConn, args, httpClient, @@ -1001,7 +976,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -1060,7 +1034,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -1091,7 +1064,6 @@ var _ = Describe("Meta", func() { cliConn.cliCommandErr = nil cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -1105,7 +1077,6 @@ var _ = Describe("Meta", func() { It("fatally logs when it receives too many arguments", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, []string{"extra-arg"}, httpClient, @@ -1121,7 +1092,6 @@ var _ = Describe("Meta", func() { args := []string{"--source-type", "invalid"} Expect(func() { cf.Meta( - context.Background(), cliConn, args, httpClient, @@ -1138,7 +1108,6 @@ var _ = Describe("Meta", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -1160,7 +1129,6 @@ var _ = Describe("Meta", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -1188,7 +1156,6 @@ var _ = Describe("Meta", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -1210,7 +1177,6 @@ var _ = Describe("Meta", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -1227,7 +1193,6 @@ var _ = Describe("Meta", func() { Expect(func() { cf.Meta( - context.Background(), cliConn, nil, httpClient, @@ -1240,13 +1205,6 @@ var _ = Describe("Meta", func() { }) }) -func generateBatch(count int) []string { - x := strings.Repeat("{},", count-1) - x += "{}" - - return []string{fmt.Sprintf(`{"batch": [%s]}`, x)} -} - func metaResponseInfo(sourceIDs ...string) string { var metaInfos []string metaInfos = append(metaInfos, fmt.Sprintf(`"%s": { diff --git a/pkg/command/cf/query.go b/internal/command/query.go similarity index 80% rename from pkg/command/cf/query.go rename to internal/command/query.go index 93cccb6d..95d0313e 100644 --- a/pkg/command/cf/query.go +++ b/internal/command/query.go @@ -1,4 +1,4 @@ -package cf +package command import ( "context" @@ -6,9 +6,7 @@ import ( "errors" "fmt" "io" - "os" "strconv" - "strings" "time" "code.cloudfoundry.org/cli/plugin" @@ -16,20 +14,17 @@ import ( flags "github.com/jessevdk/go-flags" ) -type QueryOption func(*queryOptions) - func Query( - ctx context.Context, cli plugin.CliConnection, args []string, c HTTPClient, log Logger, w io.Writer, - opts ...QueryOption, ) { if len(args) < 1 { - log.Fatalf("Must specify a PromQL query") + log.Fatalf("Incorrect Usage: the required argument `PROMQL_QUERY` was not provided") } + query := args[0] queryOptions, err := newQueryOptions(cli, args, log) @@ -37,45 +32,9 @@ func Query( log.Fatalf("%s", err) } - for _, opt := range opts { - opt(&queryOptions) - } - lw := lineWriter{w: w} - if strings.ToLower(os.Getenv("LOG_CACHE_SKIP_AUTH")) != "true" { - c = &tokenHTTPClient{ - c: c, - tokenFunc: func() string { - token, err := cli.AccessToken() - if err != nil { - log.Fatalf("Unable to get Access Token: %s", err) - } - return token - }, - } - } - - logCacheAddr := os.Getenv("LOG_CACHE_ADDR") - if logCacheAddr == "" { - hasAPI, err := cli.HasAPIEndpoint() - if err != nil { - log.Fatalf("%s", err) - } - - if !hasAPI { - log.Fatalf("No API endpoint targeted.") - } - - tokenURL, err := cli.ApiEndpoint() - if err != nil { - log.Fatalf("%s", err) - } - - logCacheAddr = strings.Replace(tokenURL, "api", "log-cache", 1) - } - - client := logcache.NewClient(logCacheAddr, logcache.WithHTTPClient(c)) + client := newLogCacheClient(c, log, cli) var res *logcache.PromQLQueryResult diff --git a/pkg/command/cf/query_test.go b/internal/command/query_test.go similarity index 97% rename from pkg/command/cf/query_test.go rename to internal/command/query_test.go index ce030798..7f40666b 100644 --- a/pkg/command/cf/query_test.go +++ b/internal/command/query_test.go @@ -1,17 +1,16 @@ -package cf_test +package command_test import ( - "context" "fmt" "net/url" - "code.cloudfoundry.org/log-cache-cli/v4/pkg/command/cf" + cf "code.cloudfoundry.org/log-cache-cli/v4/internal/command" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" ) -var _ = Describe("LogCache", func() { +var _ = Describe("Query", func() { Describe("error handling for queries", func() { It("reports an error for a failed request", func() { tc := setup("", 503) @@ -61,7 +60,7 @@ var _ = Describe("LogCache", func() { tc.query() }).To(Panic()) - Expect(tc.logger.fatalfMessage).To(HavePrefix(`Must specify a PromQL query`)) + Expect(tc.logger.fatalfMessage).To(HavePrefix("Incorrect Usage: the required argument `PROMQL_QUERY` was not provided")) Expect(tc.httpClient.requestURLs).To(HaveLen(0)) }) }) @@ -267,7 +266,6 @@ func setup(responseBody string, responseCode int) *testContext { func (tc *testContext) query(args ...string) { cf.Query( - context.Background(), tc.cliConnection, args, tc.httpClient, diff --git a/pkg/command/cf/tail.go b/internal/command/tail.go similarity index 79% rename from pkg/command/cf/tail.go rename to internal/command/tail.go index 212845a6..e56ad2b3 100644 --- a/pkg/command/cf/tail.go +++ b/internal/command/tail.go @@ -1,12 +1,10 @@ -package cf +package command import ( "context" "errors" "fmt" "io" - "net/http" - "os" "regexp" "strings" "text/template" @@ -25,20 +23,6 @@ const ( timeFormat = "2006-01-02T15:04:05.00-0700" ) -// Command is the interface to implement plugin commands -type Command func(ctx context.Context, cli plugin.CliConnection, args []string, c HTTPClient, log Logger, w io.Writer) - -// Logger is used for outputting log-cache results and errors -type Logger interface { - Fatalf(format string, args ...interface{}) - Printf(format string, args ...interface{}) -} - -// HTTPClient is the client used for HTTP requests -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - type TailOption func(*tailOptions) func WithTailNoHeaders() TailOption { @@ -77,54 +61,37 @@ func Tail( } }() - logCacheAddr := os.Getenv("LOG_CACHE_ADDR") - if logCacheAddr == "" { - hasAPI, err := cli.HasAPIEndpoint() - if err != nil { - log.Fatalf("%s", err) - } - - if !hasAPI { - log.Fatalf("No API endpoint targeted.") - } - - tokenURL, err := cli.ApiEndpoint() - if err != nil { - log.Fatalf("%s", err) - } - - user, err := cli.Username() - if err != nil { - log.Fatalf("%s", err) - } + client := newLogCacheClient(c, log, cli) - org, err := cli.GetCurrentOrg() - if err != nil { - log.Fatalf("%s", err) - } + user, err := cli.Username() + if err != nil { + log.Fatalf("%s", err) + } - space, err := cli.GetCurrentSpace() - if err != nil { - log.Fatalf("%s", err) - } + org, err := cli.GetCurrentOrg() + if err != nil { + log.Fatalf("%s", err) + } - logCacheAddr = strings.Replace(tokenURL, "api", "log-cache", 1) + space, err := cli.GetCurrentSpace() + if err != nil { + log.Fatalf("%s", err) + } - headerPrinter := formatter.appHeader - if o.isService { - headerPrinter = formatter.serviceHeader - } - if sourceID == "" { - // not an app or service, use generic header - headerPrinter = formatter.sourceHeader - } + headerPrinter := formatter.appHeader + if o.isService { + headerPrinter = formatter.serviceHeader + } + if sourceID == "" { + // not an app or service, use generic header + headerPrinter = formatter.sourceHeader + } - if !o.noHeaders { - header, ok := headerPrinter(o.providedName, org.Name, space.Name, user) - if ok { - lw.Write(header) - lw.Write("") - } + if !o.noHeaders { + header, ok := headerPrinter(o.providedName, org.Name, space.Name, user) + if ok { + lw.Write(header) + lw.Write("") } } @@ -136,23 +103,6 @@ func Tail( return formatter.formatEnvelope(e) } - tokenClient := &tokenHTTPClient{ - c: c, - tokenFunc: func() string { return "" }, - } - - if strings.ToLower(os.Getenv("LOG_CACHE_SKIP_AUTH")) != "true" { - tokenClient.tokenFunc = func() string { - token, err := cli.AccessToken() - if err != nil { - log.Fatalf("Unable to get Access Token: %s", err) - } - return token - } - } - - client := logcache.NewClient(logCacheAddr, logcache.WithHTTPClient(tokenClient)) - checkFeatureVersioning(client, ctx, log, o.nameFilter) if sourceID == "" { @@ -487,33 +437,3 @@ func checkFeatureVersioning(client *logcache.Client, ctx context.Context, log Lo } } } - -type backoff struct { - logcache.AlwaysDoneBackoff - - logger Logger -} - -func newBackoff(log Logger) backoff { - return backoff{logger: log} -} - -func (b backoff) OnErr(err error) bool { - b.logger.Fatalf("%s", err) - return b.AlwaysDoneBackoff.OnErr(err) -} - -type tokenHTTPClient struct { - c HTTPClient - tokenFunc func() string -} - -func (c *tokenHTTPClient) Do(req *http.Request) (*http.Response, error) { - accessToken := c.tokenFunc() - if len(accessToken) > 0 { - req.Header.Set("Authorization", accessToken) - } - - return c.c.Do(req) - -} diff --git a/pkg/command/cf/tail_test.go b/internal/command/tail_test.go similarity index 98% rename from pkg/command/cf/tail_test.go rename to internal/command/tail_test.go index 3d664f47..03cc786b 100644 --- a/pkg/command/cf/tail_test.go +++ b/internal/command/tail_test.go @@ -1,4 +1,4 @@ -package cf_test +package command_test import ( "context" @@ -10,12 +10,12 @@ import ( "strconv" "time" - "code.cloudfoundry.org/log-cache-cli/v4/pkg/command/cf" + cf "code.cloudfoundry.org/log-cache-cli/v4/internal/command" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) -var _ = Describe("LogCache", func() { +var _ = Describe("Tail", func() { var ( logger *stubLogger writer *stubWriter @@ -78,6 +78,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).To(HaveLen(1)) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) end, err := strconv.ParseInt(requestURL.Query().Get("end_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(end).To(BeNumerically("~", time.Now().UnixNano(), 10000000)) @@ -113,6 +114,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).To(HaveLen(1)) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) end, err := strconv.ParseInt(requestURL.Query().Get("end_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(end).To(BeNumerically("~", time.Now().UnixNano(), 10000000)) @@ -147,6 +149,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).To(HaveLen(1)) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) end, err := strconv.ParseInt(requestURL.Query().Get("end_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(end).To(BeNumerically("~", time.Now().UnixNano(), 10000000)) @@ -179,6 +182,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).To(HaveLen(1)) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) end, err := strconv.ParseInt(requestURL.Query().Get("end_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(end).To(BeNumerically("~", time.Now().UnixNano(), 10000000)) @@ -214,6 +218,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).To(HaveLen(1)) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) end, err := strconv.ParseInt(requestURL.Query().Get("end_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(end).To(BeNumerically("~", time.Now().UnixNano(), 10000000)) @@ -390,6 +395,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).ToNot(BeEmpty()) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) start, err := strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) @@ -403,6 +409,7 @@ var _ = Describe("LogCache", func() { Expect(envelopeType).To(Equal("ANY")) requestURL, err = url.Parse(httpClient.requestURLs[1]) + Expect(err).ToNot(HaveOccurred()) start, err = strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(start).To(Equal(startTime.Add(-28*time.Second).UnixNano() + 1)) @@ -452,6 +459,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).ToNot(BeEmpty()) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) start, err := strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) @@ -465,6 +473,7 @@ var _ = Describe("LogCache", func() { Expect(envelopeType).To(Equal("ANY")) requestURL, err = url.Parse(httpClient.requestURLs[1]) + Expect(err).ToNot(HaveOccurred()) start, err = strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(start).To(Equal(startTime.Add(-28*time.Second).UnixNano() + 1)) @@ -514,6 +523,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).ToNot(BeEmpty()) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) start, err := strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) @@ -527,6 +537,7 @@ var _ = Describe("LogCache", func() { Expect(envelopeType).To(Equal("ANY")) requestURL, err = url.Parse(httpClient.requestURLs[1]) + Expect(err).ToNot(HaveOccurred()) start, err = strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(start).To(Equal(startTime.Add(-28*time.Second).UnixNano() + 1)) @@ -602,6 +613,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).ToNot(BeEmpty()) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) start, err := strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) @@ -615,6 +627,7 @@ var _ = Describe("LogCache", func() { Expect(envelopeType).To(Equal("ANY")) requestURL, err = url.Parse(httpClient.requestURLs[1]) + Expect(err).ToNot(HaveOccurred()) start, err = strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(start).To(Equal(startTime.Add(-28*time.Second).UnixNano() + 1)) @@ -673,6 +686,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).ToNot(BeEmpty()) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) start, err := strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) @@ -686,6 +700,7 @@ var _ = Describe("LogCache", func() { Expect(envelopeType).To(Equal("ANY")) requestURL, err = url.Parse(httpClient.requestURLs[1]) + Expect(err).ToNot(HaveOccurred()) start, err = strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(start).To(Equal(startTime.Add(-28*time.Second).UnixNano() + 1)) @@ -744,6 +759,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).ToNot(BeEmpty()) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) start, err := strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) @@ -757,6 +773,7 @@ var _ = Describe("LogCache", func() { Expect(envelopeType).To(Equal("ANY")) requestURL, err = url.Parse(httpClient.requestURLs[1]) + Expect(err).ToNot(HaveOccurred()) start, err = strconv.ParseInt(requestURL.Query().Get("start_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(start).To(Equal(startTime.Add(-28*time.Second).UnixNano() + 1)) @@ -890,6 +907,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).To(HaveLen(1)) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) end, err := strconv.ParseInt(requestURL.Query().Get("end_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(end).To(BeNumerically("~", time.Now().UnixNano(), 10000000)) @@ -1390,42 +1408,7 @@ var _ = Describe("LogCache", func() { ) }).To(Panic()) - Expect(logger.fatalfMessage).To(Equal("some-error")) - }) - - It("fatally logs if there is no API endpoint", func() { - cliConn.hasAPIEndpoint = false - - Expect(func() { - cf.Tail( - context.Background(), - cliConn, - []string{"app-name"}, - httpClient, - logger, - writer, - ) - }).To(Panic()) - - Expect(logger.fatalfMessage).To(Equal("No API endpoint targeted.")) - }) - - It("fatally logs if there is an error while checking for API endpoint", func() { - cliConn.hasAPIEndpoint = true - cliConn.hasAPIEndpointErr = errors.New("some-error") - - Expect(func() { - cf.Tail( - context.Background(), - cliConn, - []string{"app-name"}, - httpClient, - logger, - writer, - ) - }).To(Panic()) - - Expect(logger.fatalfMessage).To(Equal("some-error")) + Expect(logger.fatalfMessage).To(Equal("Could not determine Log Cache endpoint: some-error")) }) It("fatally logs if the request returns an error", func() { @@ -1582,6 +1565,7 @@ var _ = Describe("LogCache", func() { Expect(httpClient.requestURLs).To(HaveLen(1)) requestURL, err := url.Parse(httpClient.requestURLs[0]) + Expect(err).ToNot(HaveOccurred()) end, err := strconv.ParseInt(requestURL.Query().Get("end_time"), 10, 64) Expect(err).ToNot(HaveOccurred()) Expect(end).To(BeNumerically("~", time.Now().UnixNano(), 10000000)) diff --git a/cmd/cf-lc-plugin/main.go b/main.go similarity index 69% rename from cmd/cf-lc-plugin/main.go rename to main.go index c575dea5..294c8596 100644 --- a/cmd/cf-lc-plugin/main.go +++ b/main.go @@ -4,13 +4,12 @@ import ( "context" "crypto/tls" "encoding/json" - "io" "log" "net/http" "os" "code.cloudfoundry.org/cli/plugin" - "code.cloudfoundry.org/log-cache-cli/v4/pkg/command/cf" + "code.cloudfoundry.org/log-cache-cli/v4/internal/command" "golang.org/x/crypto/ssh/terminal" ) @@ -21,62 +20,35 @@ var version string type LogCacheCLI struct{} -var commands = make(map[string]cf.Command) - func (c *LogCacheCLI) Run(conn plugin.CliConnection, args []string) { - if len(args) == 1 && args[0] == "CLI-MESSAGE-UNINSTALL" { - // someone's uninstalling the plugin, but we don't need to clean up - return - } - - if len(args) < 1 { - log.Fatalf("Expected at least 1 argument, but got %d.", len(args)) - } - isTerminal := terminal.IsTerminal(int(os.Stdout.Fd())) - commands["query"] = func(ctx context.Context, cli plugin.CliConnection, args []string, c cf.HTTPClient, log cf.Logger, tableWriter io.Writer) { - var opts []cf.QueryOption - cf.Query(ctx, cli, args, c, log, tableWriter, opts...) - } - - commands["tail"] = func(ctx context.Context, cli plugin.CliConnection, args []string, c cf.HTTPClient, log cf.Logger, tableWriter io.Writer) { - var opts []cf.TailOption - if !isTerminal { - opts = append(opts, cf.WithTailNoHeaders()) - } - cf.Tail(ctx, cli, args, c, log, tableWriter, opts...) - } - - commands["log-meta"] = func(ctx context.Context, cli plugin.CliConnection, args []string, c cf.HTTPClient, log cf.Logger, tableWriter io.Writer) { - var opts []cf.MetaOption - if !isTerminal { - opts = append(opts, cf.WithMetaNoHeaders()) - } - cf.Meta( - ctx, - cli, - args, - c, - log, - tableWriter, - opts..., - ) - } - skipSSL, err := conn.IsSSLDisabled() if err != nil { - log.Fatalf("%s", err) + log.Fatal(err) } http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ InsecureSkipVerify: skipSSL, } - op, ok := commands[args[0]] - if !ok { - log.Fatalf("Unknown Log Cache command: %s", args[0]) + l := log.New(os.Stderr, "", 0) + + switch args[0] { + case "query": + command.Query(conn, args[1:], http.DefaultClient, l, os.Stdout) + case "tail": + var opts []command.TailOption + if !isTerminal { + opts = append(opts, command.WithTailNoHeaders()) + } + command.Tail(context.Background(), conn, args[1:], http.DefaultClient, l, os.Stdout, opts...) + case "log-meta": + var opts []command.MetaOption + if !isTerminal { + opts = append(opts, command.WithMetaNoHeaders()) + } + command.Meta(conn, args[1:], http.DefaultClient, l, os.Stdout, opts...) } - op(context.Background(), conn, args[1:], http.DefaultClient, log.New(os.Stderr, "", 0), os.Stdout) } func (c *LogCacheCLI) GetMetadata() plugin.PluginMetadata { @@ -93,7 +65,7 @@ func (c *LogCacheCLI) GetMetadata() plugin.PluginMetadata { Name: "tail", HelpText: "Output logs for a source-id/app", UsageDetails: plugin.Usage{ - Usage: `tail [options] + Usage: `tail [options] SOURCE_ID ENVIRONMENT VARIABLES: LOG_CACHE_ADDR Overrides the default location of log-cache. @@ -132,13 +104,13 @@ ENVIRONMENT VARIABLES: Name: "query", HelpText: "Issues a PromQL query against Log Cache", UsageDetails: plugin.Usage{ - Usage: `query [options] + Usage: `query PROMQL_QUERY [options] ENVIRONMENT VARIABLES: LOG_CACHE_ADDR Overrides the default location of log-cache. LOG_CACHE_SKIP_AUTH Set to 'true' to disable CF authentication.`, Options: map[string]string{ - "-time": "Effective time for query execution of an instant query. Cannont be used with --start, --end, or --step. Can be a unix timestamp or RFC3339.", + "-time": "Effective time for query execution of an instant query. Cannot be used with --start, --end, or --step. Can be a unix timestamp or RFC3339.", "-start": "Start time for a range query. Cannont be used with --time. Can be a unix timestamp or RFC3339.", "-end": "End time for a range query. Cannont be used with --time. Can be a unix timestamp or RFC3339.", "-step": "Step interval for a range query. Cannot be used with --time.", @@ -152,12 +124,3 @@ ENVIRONMENT VARIABLES: func main() { plugin.Start(&LogCacheCLI{}) } - -type linesWriter struct { - lines []string -} - -func (w *linesWriter) Write(data []byte) (int, error) { - w.lines = append(w.lines, string(data)) - return len(data), nil -} diff --git a/scripts/build.sh b/scripts/build.sh index 7489fc2f..de4a94ab 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + set -e version="{\"Major\":0,\"Minor\":0,\"Build\":\"0+dev.0\"}" @@ -8,8 +9,5 @@ WORKSPACE="$SCRIPTS_PATH/.." pushd $WORKSPACE go get ./... -popd - -pushd "$WORKSPACE/cmd/cf-lc-plugin" - go build -ldflags "-X main.version=$version" -o $WORKSPACE/build_artifacts/log-cache-cf-plugin-dev + go build -ldflags "-X main.version=$version" -o $WORKSPACE/bin/log-cache-cf-plugin-dev popd diff --git a/scripts/install.sh b/scripts/install.sh index 4d02f4c5..b9914bad 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -8,6 +8,6 @@ WORKSPACE="$SCRIPTS_PATH/.." $SCRIPTS_PATH/build.sh # Install the log-cache plugin to the CF CLI and force overwrite -cf install-plugin $WORKSPACE/build_artifacts/log-cache-cf-plugin-dev -f +cf install-plugin $WORKSPACE/bin/log-cache-cf-plugin-dev -f -rm -rf $WORKSPACE/build_artifacts +rm -rf $WORKSPACE/bin diff --git a/scripts/integration-tests.sh b/scripts/integration-tests.sh index 975e420e..db1a9f54 100755 --- a/scripts/integration-tests.sh +++ b/scripts/integration-tests.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -euo pipefail @@ -26,7 +26,7 @@ function setup { cd $(dirname $0) - NO_STANDALONE_LC=true ./install.sh + ./install.sh } function cleanup_test_app { diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 56338b5c..76376434 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash read -r -p "Are you sure you want to remove the log-cache CLI? [y/N]: " remove_cli if [[ ! "$remove_cli" =~ ^[Yy]$ ]]; then