Skip to content

Commit

Permalink
Feature additions (#122)
Browse files Browse the repository at this point in the history
* update go modules
* refactor and implement features
* fix yaml tag
* add autofit tests
  • Loading branch information
briangann authored May 18, 2024
1 parent fc339eb commit 3be3d4c
Show file tree
Hide file tree
Showing 18 changed files with 316 additions and 97 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/ilyakaznacheev/cleanenv v1.5.0
github.com/magefile/mage v1.15.0
github.com/smartystreets/goconvey v1.8.1
google.golang.org/api v0.168.0
google.golang.org/api v0.169.0
)

require (
Expand All @@ -25,7 +25,7 @@ require (
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect
google.golang.org/grpc v1.62.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,17 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.168.0 h1:MBRe+Ki4mMN93jhDDbpuRLjRddooArz4FeSObvUMmjY=
google.golang.org/api v0.168.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY=
google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 h1:em/y72n4XlYRtayY/cVj6pnVzHa//BDA1BdoO+z9mdE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
Expand Down
24 changes: 12 additions & 12 deletions pkg/cmd/grafana-kiosk/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Args struct {
UseMFA bool
Audience string
KeyFile string
Apikey string
APIKey string
LXDEHome string
ConfigPath string
Mode string
Expand Down Expand Up @@ -68,7 +68,7 @@ func ProcessArgs(cfg interface{}) Args {
flagSettings.StringVar(&processedArgs.PasswordField, "field-password", "password", "Fieldname for the password")
flagSettings.StringVar(&processedArgs.Audience, "audience", "", "idtoken audience")
flagSettings.StringVar(&processedArgs.KeyFile, "keyfile", "key.json", "idtoken json credentials")
flagSettings.StringVar(&processedArgs.Apikey, "apikey", "", "apikey")
flagSettings.StringVar(&processedArgs.APIKey, "apikey", "", "apikey")

fu := flagSettings.Usage
flagSettings.Usage = func() {
Expand Down Expand Up @@ -134,9 +134,9 @@ func summary(cfg *kiosk.Config) {
log.Println("IsPlayList:", cfg.Target.IsPlayList)
log.Println("UseMFA:", cfg.Target.UseMFA)
// goauth
log.Println("Fieldname AutoLogin:", cfg.GOAUTH.AutoLogin)
log.Println("Fieldname Username:", cfg.GOAUTH.UsernameField)
log.Println("Fieldname Password:", cfg.GOAUTH.PasswordField)
log.Println("Fieldname AutoLogin:", cfg.GoAuth.AutoLogin)
log.Println("Fieldname Username:", cfg.GoAuth.UsernameField)
log.Println("Fieldname Password:", cfg.GoAuth.PasswordField)
}

func main() {
Expand Down Expand Up @@ -186,14 +186,14 @@ func main() {
cfg.General.WindowPosition = args.WindowPosition
cfg.General.WindowSize = args.WindowSize
//
cfg.GOAUTH.AutoLogin = args.OauthAutoLogin
cfg.GOAUTH.UsernameField = args.UsernameField
cfg.GOAUTH.PasswordField = args.PasswordField
cfg.GoAuth.AutoLogin = args.OauthAutoLogin
cfg.GoAuth.UsernameField = args.UsernameField
cfg.GoAuth.PasswordField = args.PasswordField

cfg.IDTOKEN.Audience = args.Audience
cfg.IDTOKEN.KeyFile = args.KeyFile
cfg.IDToken.Audience = args.Audience
cfg.IDToken.KeyFile = args.KeyFile

cfg.APIKEY.Apikey = args.Apikey
cfg.APIKey.APIKey = args.APIKey
}

// make sure the url has content
Expand Down Expand Up @@ -232,7 +232,7 @@ func main() {
kiosk.GrafanaKioskIDToken(&cfg, messages)
case "apikey":
log.Printf("Launching apikey kiosk")
kiosk.GrafanaKioskApikey(&cfg, messages)
kiosk.GrafanaKioskAPIKey(&cfg, messages)
case "aws":
log.Printf("Launching AWS SSO kiosk")
kiosk.GrafanaKioskAWSLogin(&cfg, messages)
Expand Down
93 changes: 89 additions & 4 deletions pkg/cmd/grafana-kiosk/main_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,101 @@
package main

import (
"log"
"os"
"testing"

"github.com/grafana/grafana-kiosk/pkg/kiosk"
"github.com/ilyakaznacheev/cleanenv"
. "github.com/smartystreets/goconvey/convey"
)

// TestKiosk checks kiosk command.
func TestKioskCommand(t *testing.T) {
Convey("Given Kiosk CMD", t, func() {
Convey("cmd should...", func() {
So(true, ShouldBeTrue)
func TestMain(t *testing.T) {
Convey("Given Default Configuration", t, func() {
cfg := kiosk.Config{
BuildInfo: kiosk.BuildInfo{
Version: "1.0.0",
},
General: kiosk.General{
AutoFit: true,
LXDEEnabled: true,
LXDEHome: "/home/pi",
Mode: "full",
WindowPosition: "0,0",
WindowSize: "1920,1080",
ScaleFactor: "1.0",
},
Target: kiosk.Target{
IgnoreCertificateErrors: false,
IsPlayList: false,
UseMFA: false,
LoginMethod: "local",
Password: "admin",
URL: "http://localhost:3000",
Username: "admin",
},
GoAuth: kiosk.GoAuth{
AutoLogin: false,
UsernameField: "user",
PasswordField: "password",
},
IDToken: kiosk.IDToken{
KeyFile: "/tmp/key.json",
Audience: "clientid",
},
APIKey: kiosk.APIKey{
APIKey: "abc",
},
}
Convey("General Options", func() {
Convey("Parameter - autofit", func() {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"grafana-kiosk", ""}
// starts out default true
result := ProcessArgs(cfg)
So(result.AutoFit, ShouldBeTrue)
// flag to set it false
os.Args = []string{
"grafana-kiosk",
"--autofit=false",
}
result = ProcessArgs(cfg)
So(result.AutoFit, ShouldBeFalse)
})

Convey("Environment - autofit", func() {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"grafana-kiosk", ""}
os.Setenv("KIOSK_AUTOFIT", "false")
cfg := kiosk.Config{}
if err := cleanenv.ReadEnv(&cfg); err != nil {
log.Println("Error reading config from environment", err)
}
So(cfg.General.AutoFit, ShouldBeFalse)
})
})
// end of general options

Convey("Anonymous Login", func() {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"grafana-kiosk", ""}
result := ProcessArgs(cfg)
So(result.LoginMethod, ShouldEqual, "anon")
So(result.URL, ShouldEqual, "https://play.grafana.org")
So(result.AutoFit, ShouldBeTrue)
})
Convey("Local Login", func() {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"grafana-kiosk", "-login-method", "local"}
result := ProcessArgs(cfg)
So(result.LoginMethod, ShouldEqual, "local")
So(result.URL, ShouldEqual, "https://play.grafana.org")
So(result.AutoFit, ShouldBeTrue)
})
})
}
7 changes: 4 additions & 3 deletions pkg/kiosk/anonymous_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ func GrafanaKioskAnonymous(cfg *Config, messages chan string) {
taskCtx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))
defer cancel()

listenChromeEvents(taskCtx, consoleAPICall|targetCrashed)
listenChromeEvents(taskCtx, cfg, consoleAPICall|targetCrashed)

// ensure that the browser process is started
if err := chromedp.Run(taskCtx); err != nil {
panic(err)
}

// Give browser time to load next page (this can be prone to failure, explore different options vs sleeping)
time.Sleep(2000 * time.Millisecond)
// Give browser time to load
log.Printf("Sleeping %d MS before navigating to url", cfg.General.PageLoadDelayMS)
time.Sleep(time.Duration(cfg.General.PageLoadDelayMS) * time.Millisecond)

var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList)

Expand Down
13 changes: 7 additions & 6 deletions pkg/kiosk/apikey_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"github.com/chromedp/chromedp"
)

// GrafanaKioskApikey creates a chrome-based kiosk using a grafana api key.
func GrafanaKioskApikey(cfg *Config, messages chan string) {
// GrafanaKioskAPIKey creates a chrome-based kiosk using a grafana api key.
func GrafanaKioskAPIKey(cfg *Config, messages chan string) {
dir, err := os.MkdirTemp(os.TempDir(), "chromedp-kiosk")
if err != nil {
panic(err)
Expand All @@ -29,15 +29,16 @@ func GrafanaKioskApikey(cfg *Config, messages chan string) {
taskCtx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))
defer cancel()

listenChromeEvents(taskCtx, consoleAPICall|targetCrashed)
listenChromeEvents(taskCtx, cfg, consoleAPICall|targetCrashed)

// ensure that the browser process is started
if err := chromedp.Run(taskCtx); err != nil {
panic(err)
}

// Give browser time to load next page (this can be prone to failure, explore different options vs sleeping)
time.Sleep(2000 * time.Millisecond)
// Give browser time to load
log.Printf("Sleeping %d MS before navigating to url", cfg.General.PageLoadDelayMS)
time.Sleep(time.Duration(cfg.General.PageLoadDelayMS) * time.Millisecond)

var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList)

Expand All @@ -46,7 +47,7 @@ func GrafanaKioskApikey(cfg *Config, messages chan string) {
Launch chrome and look for main-view element
*/
headers := map[string]interface{}{
"Authorization": "Bearer " + cfg.APIKEY.Apikey,
"Authorization": "Bearer " + cfg.APIKey.APIKey,
}
if err := chromedp.Run(taskCtx,
network.Enable(),
Expand Down
5 changes: 4 additions & 1 deletion pkg/kiosk/aws_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ func GrafanaKioskAWSLogin(cfg *Config, messages chan string) {
taskCtx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))
defer cancel()

listenChromeEvents(taskCtx, targetCrashed)
listenChromeEvents(taskCtx, cfg, targetCrashed)
// Give browser time to load
log.Printf("Sleeping %d MS before navigating to url", cfg.General.PageLoadDelayMS)
time.Sleep(time.Duration(cfg.General.PageLoadDelayMS) * time.Millisecond)

// ensure that the browser process is started
if err := chromedp.Run(taskCtx); err != nil {
Expand Down
88 changes: 55 additions & 33 deletions pkg/kiosk/config.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,60 @@
package kiosk

// BuildInfo contains the build version
type BuildInfo struct {
Version string `yaml:"version,omitempty"`
}

// General non-site specific configuations
type General struct {
AutoFit bool `yaml:"autofit" env:"KIOSK_AUTOFIT" env-default:"true" env-description:"fit panels to screen"`
DebugEnabled bool `yaml:"debug" env:"KIOSK_DEBUG" env-default:"false" env-description:"enables debug output"`
GPUEnabled bool `yaml:"gpu-enabled" env:"KIOSK_GPU_ENABLED" env-default:"false" env-description:"disable GPU support"`
LXDEEnabled bool `yaml:"lxde" env:"KIOSK_LXDE_ENABLED" env-default:"false" env-description:"initialize LXDE for kiosk mode"`
LXDEHome string `yaml:"lxde-home" env:"KIOSK_LXDE_HOME" env-default:"/home/pi" env-description:"path to home directory of LXDE user running X Server"`
Mode string `yaml:"kiosk-mode" env:"KIOSK_MODE" env-default:"full" env-description:"[full|tv|disabled]"`
OzonePlatform string `yaml:"ozone-platform" env:"KIOSK_OZONE_PLATFORM" env-default:"" env-description:"Set ozone-platform option (wayland|cast|drm|wayland|x11)"`
PageLoadDelayMS int64 `yaml:"page-load-delay-ms" env:"KIOSK_PAGE_LOAD_DELAY_MS" env-default:"2000" env-description:"milliseconds to wait before expecting page load"`
ScaleFactor string `yaml:"scale-factor" env:"KIOSK_SCALE_FACTOR" env-default:"1.0" env-description:"Scale factor, like zoom"`
WindowPosition string `yaml:"window-position" env:"KIOSK_WINDOW_POSITION" env-default:"0,0" env-description:"Top Left Position of Kiosk"`
WindowSize string `yaml:"window-size" env:"KIOSK_WINDOW_SIZE" env-default:"" env-description:"Size of Kiosk in pixels (width,height)"`
}

// Target the dashboard/playlist details
type Target struct {
IgnoreCertificateErrors bool `yaml:"ignore-certificate-errors" env:"KIOSK_IGNORE_CERTIFICATE_ERRORS" env-description:"ignore SSL/TLS certificate errors" env-default:"false"`
IsPlayList bool `yaml:"playlist" env:"KIOSK_IS_PLAYLIST" env-default:"false" env-description:"URL is a playlist"`
LoginMethod string `yaml:"login-method" env:"KIOSK_LOGIN_METHOD" env-default:"anon" env-description:"[anon|local|gcom|goauth|idtoken|apikey]"`
Password string `yaml:"password" env:"KIOSK_LOGIN_PASSWORD" env-default:"guest" env-description:"password"`
URL string `yaml:"URL" env:"KIOSK_URL" env-default:"https://play.grafana.org" env-description:"URL to Grafana server"`
Username string `yaml:"username" env:"KIOSK_LOGIN_USER" env-default:"guest" env-description:"username"`
UseMFA bool `yaml:"use-mfa" env:"KIOSK_USE_MFA" env-default:"false" env-description:"MFA is enabled for given account"`
}

// GoAuth OAuth
type GoAuth struct {
AutoLogin bool `yaml:"auto-login" env:"KIOSK_GOAUTH_AUTO_LOGIN" env-description:"[false|true]"`
UsernameField string `yaml:"fieldname-username" env:"KIOSK_GOAUTH_FIELD_USER" env-description:"Username html input name value"`
PasswordField string `yaml:"fieldname-password" env:"KIOSK_GOAUTH_FIELD_PASSWORD" env-description:"Password html input name value"`
}

// IDToken token based login
type IDToken struct {
KeyFile string `yaml:"idtoken-keyfile" env:"KIOSK_IDTOKEN_KEYFILE" env-default:"key.json" env-description:"JSON Credentials for idtoken"`
Audience string `yaml:"idtoken-audience" env:"KIOSK_IDTOKEN_AUDIENCE" env-description:"Audience for idtoken, tpyically your oauth client id"`
}

// APIKey APIKey for login
type APIKey struct {
APIKey string `yaml:"apikey" env:"KIOSK_APIKEY_APIKEY" env-description:"APIKEY"`
}

// Config configuration for backend.
type Config struct {
BuildInfo struct {
Version string `yaml:"version,omitempty"`
}
General struct {
AutoFit bool `yaml:"autofit" env:"KIOSK_AUTOFIT" env-default:"true" env-description:"fit panels to screen"`
LXDEEnabled bool `yaml:"lxde" env:"KIOSK_LXDE_ENABLED" env-default:"false" env-description:"initialize LXDE for kiosk mode"`
LXDEHome string `yaml:"lxde-home" env:"KIOSK_LXDE_HOME" env-default:"/home/pi" env-description:"path to home directory of LXDE user running X Server"`
Mode string `yaml:"kiosk-mode" env:"KIOSK_MODE" env-default:"full" env-description:"[full|tv|disabled]"`
WindowPosition string `yaml:"window-position" env:"KIOSK_WINDOW_POSITION" env-default:"0,0" env-description:"Top Left Position of Kiosk"`
WindowSize string `yaml:"window-size" env:"KIOSK_WINDOW_SIZE" env-default:"" env-description:"Size of Kiosk in pixels (width,height)"`
ScaleFactor string `yaml:"scale-factor" env:"KIOSK_SCALE_FACTOR" env-default:"1.0" env-description:"Scale factor, like zoom"`
} `yaml:"general"`
Target struct {
IgnoreCertificateErrors bool `yaml:"ignore-certificate-errors" env:"KIOSK_IGNORE_CERTIFICATE_ERRORS" env-description:"ignore SSL/TLS certificate errors" env-default:"false"`
IsPlayList bool `yaml:"playlist" env:"KIOSK_IS_PLAYLIST" env-default:"false" env-description:"URL is a playlist"`
UseMFA bool `yaml:"use-mfa" env:"KIOSK_USE_MFA" env-default:"false" env-description:"MFA is enabled for given account"`
LoginMethod string `yaml:"login-method" env:"KIOSK_LOGIN_METHOD" env-default:"anon" env-description:"[anon|local|gcom|goauth|idtoken|apikey]"`
Password string `yaml:"password" env:"KIOSK_LOGIN_PASSWORD" env-default:"guest" env-description:"password"`
URL string `yaml:"URL" env:"KIOSK_URL" env-default:"https://play.grafana.org" env-description:"URL to Grafana server"`
Username string `yaml:"username" env:"KIOSK_LOGIN_USER" env-default:"guest" env-description:"username"`
} `yaml:"target"`
GOAUTH struct {
AutoLogin bool `yaml:"auto-login" env:"KIOSK_GOAUTH_AUTO_LOGIN" env-description:"[false|true]"`
UsernameField string `yaml:"fieldname-username" env:"KIOSK_GOAUTH_FIELD_USER" env-description:"Username html input name value"`
PasswordField string `yaml:"fieldname-password" env:"KIOSK_GOAUTH_FIELD_PASSWORD" env-description:"Password html input name value"`
} `yaml:"goauth"`
IDTOKEN struct {
KeyFile string `yaml:"idtoken-keyfile" env:"KIOSK_IDTOKEN_KEYFILE" env-default:"key.json" env-description:"JSON Credentials for idtoken"`
Audience string `yaml:"idtoken-audience" env:"KIOSK_IDTOKEN_AUDIENCE" env-description:"Audience for idtoken, tpyically your oauth client id"`
} `yaml:"idtoken"`
APIKEY struct {
Apikey string `yaml:"apikey" env:"KIOSK_APIKEY_APIKEY" env-description:"APIKEY"`
} `yaml:"apikey"`
BuildInfo BuildInfo `yaml:"buildinfo"`
General General `yaml:"general"`
Target Target `yaml:"target"`
GoAuth GoAuth `yaml:"goauth"`
IDToken IDToken `yaml:"idtoken"`
APIKey APIKey `yaml:"apikey"`
}
5 changes: 4 additions & 1 deletion pkg/kiosk/grafana_com_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ func GrafanaKioskGCOM(cfg *Config, messages chan string) {
taskCtx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))
defer cancel()

listenChromeEvents(taskCtx, targetCrashed)
listenChromeEvents(taskCtx, cfg, targetCrashed)

// ensure that the browser process is started
if err := chromedp.Run(taskCtx); err != nil {
panic(err)
}
// Give browser time to load
log.Printf("Sleeping %d MS before navigating to url", cfg.General.PageLoadDelayMS)
time.Sleep(time.Duration(cfg.General.PageLoadDelayMS) * time.Millisecond)

var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList)

Expand Down
Loading

0 comments on commit 3be3d4c

Please sign in to comment.