Skip to content

Commit

Permalink
Feature: Job logs and list (#50)
Browse files Browse the repository at this point in the history
* Define stdout and stderr per job

* Add streaming api response for mittnitectl logs

* use url instead of string

* add unfinished follow function for job logs

* always follow logs

* fix log error

* mittnitectl breaking: change job syntax

+ add logs and list subcommand

* add --tail arg for job logs

* fix formatting and add help for `mittnitectl job`

* update to go 1.19

* add docs for stdout/err options and fix typo

* make build methods more readable

* set default value for `--tail` to -1

+ make it possible to set `--tail` to "0" for only new log outputs
  • Loading branch information
jkmw authored Jan 10, 2023
1 parent 58b2e24 commit 7d7eea8
Show file tree
Hide file tree
Showing 19 changed files with 671 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.19

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.19

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ job "foo" {
}
```
To redirect the output of a job to a separate file, `stdout` and/or `stderr` can be specified:
```hcl
job "foo" {
command = "/usr/local/bin/foo"
args = ["bar"]
stdout = "/tmp/foo.log"
stderr = "/tmp/foo-errors.log"
}
```
You can configure a Job to watch files and to send a signal to the managed process if that file changes. This can be used, for example, to send a `SIGHUP` to a process to reload its configuration file when it changes.
```hcl
Expand Down Expand Up @@ -401,6 +412,30 @@ Flags:
-h, --help help for mittnitectl
Use "mittnitectl [command] --help" for more information about a command.
```
```shell
$ mittnitectl job --help
This command can be used to control a managed job.
Usage:
mittnitectl job [command]
Available Commands:
list List jobs
logs Get logs from job
restart Restart a job
start Start a job
status Show job status
stop Stop a job
Flags:
-h, --help help for job
Global Flags:
--api-address string write mittnites process id to this file (default "unix:///var/run/mittnite.sock")
Use "mittnitectl job [command] --help" for more information about a command.
```
### job
Expand Down
35 changes: 3 additions & 32 deletions cmd/job.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,15 @@
package cmd

import (
"github.com/mittwald/mittnite/pkg/cli"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func init() {
ctlCommand.AddCommand(jobCommand)
jobCommand.SetHelpTemplate(`{{.Long}}
Usage:
{{.UseLine}}
Arguments:
name: the name of the job
action: possible values are "start", "restart", "stop" and "status"
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`)
}

var jobCommand = &cobra.Command{
Use: "job <name> <action>",
Args: cobra.ExactArgs(2),
ArgAliases: []string{"name", "action"},
Short: "Control a job via command line",
Long: "This command can be used to control a job managed by mittnite.",
Run: func(cmd *cobra.Command, args []string) {
job := args[0]
action := args[1]
apiClient := cli.NewApiClient(apiAddress)

resp := apiClient.CallAction(job, action)
if err := resp.Print(); err != nil {
log.Errorf("failed to print output: %s", err.Error())
}
},
Use: "job",
Short: "Control a job via command line",
Long: "This command can be used to control a managed job.",
}
34 changes: 34 additions & 0 deletions cmd/job_actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cmd

import (
"fmt"
"github.com/mittwald/mittnite/pkg/cli"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func init() {
jobCommand.AddCommand(buildJobActionCommand("start", "Start a job", "This command can be used to start a managed job."))
jobCommand.AddCommand(buildJobActionCommand("restart", "Restart a job", "This command can be used to restart a managed job."))
jobCommand.AddCommand(buildJobActionCommand("stop", "Stop a job", "This command can be used to stop a managed job."))
jobCommand.AddCommand(buildJobActionCommand("status", "Show job status", "This command can be used to show the status of a managed job."))
}

func buildJobActionCommand(action string, shortDesc, longDesc string) *cobra.Command {
return &cobra.Command{
Use: fmt.Sprintf("%s <job>", action),
Args: cobra.ExactArgs(1),
ArgAliases: []string{"job"},
Short: shortDesc,
Long: longDesc,
Run: func(cmd *cobra.Command, args []string) {
job := args[0]
apiClient := cli.NewApiClient(apiAddress)

resp := apiClient.CallAction(job, action)
if err := resp.Print(); err != nil {
log.Errorf("failed to print output: %s", err.Error())
}
},
}
}
25 changes: 25 additions & 0 deletions cmd/job_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cmd

import (
"github.com/mittwald/mittnite/pkg/cli"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func init() {
jobCommand.AddCommand(jobListCommand)
}

var jobListCommand = &cobra.Command{
Use: "list",
Short: "List jobs",
Long: "This command can be used to list all managed jobs.",
Run: func(cmd *cobra.Command, args []string) {
apiClient := cli.NewApiClient(apiAddress)

resp := apiClient.JobList()
if err := resp.Print(); err != nil {
log.Errorf("failed to print output: %s", err.Error())
}
},
}
36 changes: 36 additions & 0 deletions cmd/job_logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cmd

import (
"github.com/mittwald/mittnite/pkg/cli"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var (
follow bool
tailLen int
)

func init() {
jobLogsCommand.PersistentFlags().BoolVarP(&follow, "follow", "f", false, "output appended data as the file grows")
jobLogsCommand.PersistentFlags().IntVarP(&tailLen, "tail", "", -1, "output last n lines")
jobCommand.AddCommand(jobLogsCommand)
}

var jobLogsCommand = &cobra.Command{
Use: "logs <job>",
Short: "Get logs from job",
Long: "This command can be used to get the logs of a managed job.",
Run: func(cmd *cobra.Command, args []string) {
job := args[0]
apiClient := cli.NewApiClient(apiAddress)

if tailLen < -1 {
tailLen = -1
}
resp := apiClient.JobLogs(job, follow, tailLen)
if err := resp.Print(); err != nil {
log.Errorf("failed to print output: %s", err.Error())
}
},
}
40 changes: 36 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
module github.com/mittwald/mittnite

go 1.19

require (
github.com/Masterminds/sprig/v3 v3.2.2
github.com/go-redis/redis v6.15.6+incompatible
github.com/go-sql-driver/mysql v1.5.0
github.com/google/go-cmp v0.5.4 // indirect
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/hcl v1.0.0
github.com/onsi/ginkgo v1.15.2 // indirect
github.com/onsi/gomega v1.11.0 // indirect
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.1.3
Expand All @@ -18,4 +18,36 @@ require (
go.mongodb.org/mongo-driver v1.5.1
)

go 1.16
require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/aws/aws-sdk-go v1.34.28 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/huandu/xstrings v1.3.1 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.9.5 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/onsi/ginkgo v1.15.2 // indirect
github.com/onsi/gomega v1.11.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.0.2 // indirect
github.com/xdg-go/stringprep v1.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
11 changes: 8 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
Expand Down Expand Up @@ -344,8 +346,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -382,14 +385,16 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
2 changes: 2 additions & 0 deletions internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ type BaseJobConfig struct {
CanFail bool `hcl:"canFail"`
Controllable bool `hcl:"controllable" json:"controllable"`
WorkingDirectory string `hcl:"workingDirectory" json:"workingDirectory,omitempty"`
Stdout string `hcl:"stdout" json:"stdout,omitempty"`
Stderr string `hcl:"stderr" json:"stderr,omitempty"`
}

type Laziness struct {
Expand Down
52 changes: 52 additions & 0 deletions pkg/cli/api_address_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cli

import (
"context"
"github.com/gorilla/websocket"
"net"
"net/http"
"net/url"
)

func (api *ApiClient) buildHTTPClientAndURL() (*http.Client, *url.URL, error) {
u, err := url.Parse(api.apiAddress)
if err != nil {
return nil, nil, err
}
if u.Scheme != "unix" {
return &http.Client{}, u, nil
}

socketPath := u.Path
u.Scheme = "http"
u.Host = "unix"
return &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
}, u, nil
}

func (api *ApiClient) buildWebsocketURL() (*websocket.Dialer, *url.URL, error) {
u, err := url.Parse(api.apiAddress)
if err != nil {
return nil, nil, err
}
if u.Scheme != "unix" {
u.Scheme = "ws"
return websocket.DefaultDialer, u, nil
}
socketPath := u.Path

dialer := &websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
}

u.Scheme = "ws"
u.Host = "unix"
return dialer, u, nil
}
Loading

0 comments on commit 7d7eea8

Please sign in to comment.