system/json.go
package system
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
)
var schemaDir = "file:///usr/share/saptune/schemas/1.0/"
var supportedRAC = map[string]bool{"daemon start": false, "daemon status": true, "daemon stop": false, "service apply": false, "service start": false, "service status": true, "service stop": false, "service restart": false, "service revert": false, "service reload": false, "service takeover": false, "service enable": false, "service disable": false, "service enablestart": false, "service disablestop": false, "note list": true, "note revertall": false, "note enabled": true, "note applied": true, "note apply": false, "note simulate": false, "note customise": false, "note create": false, "note edit": false, "note revert": false, "note show": false, "note delete": false, "note verify": true, "note rename": false, "solution list": true, "solution verify": true, "solution enabled": true, "solution applied": true, "solution apply": false, "solution change": false, "solution simulate": false, "solution customise": false, "solution create": false, "solution edit": false, "solution revert": false, "solution show": false, "solution delete": false, "solution rename": false, "staging status": false, "staging enable": false, "staging disable": false, "staging is-enabled": false, "staging list": false, "staging diff": false, "staging analysis": false, "staging release": false, "revert all": false, "lock remove": false, "check": false, "status": true, "version": true, "help": false}
// jentry is the json entry to display
var jentry JEntry
// JMsg is a single log message and it's severity/priority
type JMsg struct {
// Priority of the log messages as defined AT
// https://confluence.suse.com/display/SAP/Logging+Guide"
// "CRITICAL", "ERROR" ,"WARNING" ,"NOTICE" ,"INFO", "DEBUG"
Prio string `json:"priority"`
// The log message itself
Txt string `json:"message"`
}
// JMessages contains all log messages normally printed on the screen in the
// order they were created
type JMessages []JMsg
// JResult is the result (output) of the command.
type JResult interface{}
// JObj is a string, a string pointer or a bool for some parts in the result
// of the command.
type JObj interface{}
// JEntry defines the global structure of our json object
type JEntry struct {
// URI to the schema definition
Schema string `json:"$schema"`
// saptune timestamp of the time this JSON object was created
Created string `json:"publish time"`
// The entire saptune command as it was called
CmdLine string `json:"argv"`
// PID of the saptune process creating this object
Pid int `json:"pid"`
// The saptune command (classifier), which was execute
// realm + command, no options, no parameter
Cmd string `json:"command"`
// The return code the saptune command terminated with
CmdRet int `json:"exit code"`
// The result (output) of the command.
CmdResult JResult `json:"result"`
//Contains all log messages normally printed on the screen in the
// order they were created
CmdMsg JMessages `json:"messages"`
}
// emptyResult defines an empty result
type emptyResult struct {
}
// notSupportedResult is the result type definition for commands not yet
// supported for json output
type notSupportedResult struct {
Implemented bool `json:"implemented"`
}
// versResults is the result type definition for 'saptune version'
type versResults struct {
ConfVers string `json:"configured version"`
}
// configuredSol is for 'saptune solution enabled'
type configuredSol struct {
ConfiguredSol []string `json:"Solution enabled"`
}
// JAppliedSol is for 'Solution applied' in
// 'saptune status' and 'saptune solution applied'
type JAppliedSol struct {
SolName string `json:"Solution ID,omitempty"`
Partial *bool `json:"applied partially,omitempty"`
}
// appliedSol is for 'saptune solution applied'
type appliedSol struct {
AppliedSol []JAppliedSol `json:"Solution applied"`
}
// appliedNotes is for 'saptune note applied'
type appliedNotes struct {
AppliedNotes []string `json:"Notes applied"`
}
// notesOrder is for 'saptune note enabled'
type notesOrder struct {
NotesOrder []string `json:"Notes enabled"`
}
// JNoteListEntry is one line of 'saptune note list'
type JNoteListEntry struct {
NoteID string `json:"Note ID"`
NoteDesc string `json:"Note description"`
NoteRef JObj `json:"Note reference"`
NoteVers string `json:"Note version"`
NoteRdate string `json:"Note release date"`
ManEnabled bool `json:"Note enabled manually"`
SolEnabled bool `json:"Note enabled by Solution"`
ManReverted bool `json:"Note reverted manually"`
NoteOverride bool `json:"Note override exists"`
CustomNote bool `json:"custom Note"`
}
// JNoteList is the whole 'saptune note list'
type JNoteList struct {
NotesList []JNoteListEntry `json:"Notes available"`
NotesOrder []string `json:"Notes enabled"`
Msg string `json:"remember message"`
}
// JPNotesLine one row of 'saptune note verify|simulate'
// from PrintNoteFields
type JPNotesLine struct {
NoteID string `json:"Note ID,omitempty"`
NoteVers string `json:"Note version,omitempty"`
Parameter string `json:"parameter"`
Compliant *bool `json:"compliant,omitempty"`
ExpValue string `json:"expected value,omitempty"`
OverValue string `json:"override value,omitempty"`
ActValue *string `json:"actual value,omitempty"`
Comment string `json:"comment,omitempty"`
Footnotes []JFootNotes `json:"amendments,omitempty"`
}
// JFootNotes collects the footnotes per parameter
type JFootNotes struct {
FNoteNumber int `json:"index,omitempty"`
FNoteTxt string `json:"amendment,omitempty"`
}
// JPNotesRemind is the reminder section
type JPNotesRemind struct {
NoteID string `json:"Note ID,omitempty"`
NoteReminder string `json:"attention,omitempty"`
}
// JPNotes is the whole 'PrintNoteFields' function
// if we need to differ between 'verify' and 'simulate' this
// can be done in PrintNoteFields' or in jcollect.
type JPNotes struct {
Verifications []JPNotesLine `json:"verifications"`
Simulations []JPNotesLine `json:"simulations,omitempty"`
Attentions []JPNotesRemind `json:"attentions"`
NotesOrder []string `json:"Notes enabled"`
SysCompliance *bool `json:"system compliance"`
}
// JSol - Solution name and related Note list
type JSol struct {
SolName string `json:"Solution ID"`
NotesList []string `json:"Note list"`
}
// JStatus is the whole 'saptune status'
type JStatus struct {
Services JStatusServs `json:"services"`
SystemdSysState string `json:"systemd system state"`
TuningState string `json:"tuning state"`
VirtEnv string `json:"virtualization"`
SaptuneVersion string `json:"configured version"`
RPMVersion string `json:"package version"`
ConfiguredSol []string `json:"Solution enabled"`
ConfSolNotes []JSol `json:"Notes enabled by Solution"`
AppliedSol []JAppliedSol `json:"Solution applied"`
AppliedSolNotes []JSol `json:"Notes applied by Solution"`
ConfiguredNotes []string `json:"Notes enabled additionally"`
EnabledNotes []string `json:"Notes enabled"`
AppliedNotes []string `json:"Notes applied"`
Staging JStatusStaging `json:"staging"`
Msg string `json:"remember message"`
}
// JStatusStaging contains the staging infos for 'saptune status'
type JStatusStaging struct {
StagingEnabled bool `json:"staging enabled"`
StagedNotes []string `json:"Notes staged"`
StagedSols []string `json:"Solutions staged"`
}
// JStatusServs are the mentioned systemd services in 'saptune status'
type JStatusServs struct {
SaptuneService JObj `json:"saptune"`
SapconfService JObj `json:"sapconf"`
TunedService JObj `json:"tuned"`
TunedProfile *string `json:"tuned profile,omitempty"`
}
// JSolListEntry is one line of 'saptune solution list'
type JSolListEntry struct {
SolName string `json:"Solution ID"`
NotesList []string `json:"Note list"`
SolEnabled bool `json:"Solution enabled"`
SolOverride bool `json:"Solution override exists"`
CustomSol bool `json:"custom Solution"`
DepSol bool `json:"Solution deprecated"`
}
// JSolList is the whole 'saptune solution list'
type JSolList struct {
SolsList []JSolListEntry `json:"Solutions available"`
Msg string `json:"remember message"`
}
// jInit creates an initial json entry
// used in system/InitOut
func jInit() {
created := time.Now().Format("2006-01-02 15:04:05.000")
rac := realmAndCmd()
jentry = JEntry{
Schema: schemaName(rac),
Created: created,
CmdLine: strings.Join(os.Args, " "),
Pid: os.Getpid(),
Cmd: rac,
CmdResult: emptyResult{},
CmdMsg: []JMsg{},
}
}
// jWriteMsg appends messages from logging to the json entry
// instead of writing to stdout/stderr
// used in system/logging
func jWriteMsg(prio, msg string) {
var jmsg JMsg
if GetFlagVal("format") != "json" {
return
}
jmsg.Prio = prio
jmsg.Txt = msg
jentry.CmdMsg = append(jentry.CmdMsg, jmsg)
}
// jOut writes the json output to stdout
// used in function system/ErrorExit
func jOut(exit int) error {
var err error
if GetFlagVal("format") != "json" {
return err
}
// reset stdout to original setting
os.Stdout = stdOutOrg
jentry.CmdRet = exit
data, err := json.Marshal(jentry)
if err == nil {
fmt.Println(string(data))
}
return err
}
// JInvalid is the answer of an invalid saptune call
// used in function action/PrintHelpAndExit
func JInvalid(exitStatus int) {
if exitStatus != 0 {
jentry.Schema = schemaName("invalid")
jentry.Cmd = "invalid"
}
ErrorExit("", exitStatus)
}
// JnotSupportedYet is the answer of a command without json support yet
// used in function action/SelectAction
func JnotSupportedYet() {
rac := realmAndCmd()
if GetFlagVal("format") != "json" || racIsSupported(rac) {
return
}
jentry.CmdResult = notSupportedResult{Implemented: false}
ErrorExit("", 1)
}
// Jcollect collects the result data
func Jcollect(data interface{}) {
rac := realmAndCmd()
if GetFlagVal("format") != "json" || !racIsSupported(rac) {
return
}
switch res := data.(type) {
case string:
if rac == "version" {
jentry.CmdResult = versResults{ConfVers: res}
}
case []string:
if len(res) == 1 && res[0] == "" {
// replace empty string by empty slice
res = make([]string, 0)
}
if rac == "note enabled" {
jentry.CmdResult = notesOrder{NotesOrder: res}
}
if rac == "note applied" {
jentry.CmdResult = appliedNotes{AppliedNotes: res}
}
if rac == "solution enabled" {
jentry.CmdResult = configuredSol{ConfiguredSol: res}
}
//if rac == "solution applied" {
// jentry.CmdResult = appliedSol{AppliedSol: res}
//}
case JAppliedSol:
// "saptune solution applied"
var appSol appliedSol
if res.SolName != "" {
appSol.AppliedSol = append(appSol.AppliedSol, res)
} else {
appSol.AppliedSol = make([]JAppliedSol, 0)
}
jentry.CmdResult = appSol
case JSolList, JNoteList, JStatus, JPNotes:
//"solution list", "note list", "status", "daemon status", "service status", "note verify", "solution verify", "note simulate", "solution simulate":
jentry.CmdResult = res
default:
WarningLog("Unknown data type '%T' for command '%s' in Jcollect, skipping", data, rac)
}
}
// schemaName returns the schema string
func schemaName(name string) string {
return fmt.Sprintf("%ssaptune_%s.schema.json", schemaDir, strings.Replace(name, " ", "_", -1))
}
// realmAndCmd returns the realms name and the command name, if available
func realmAndCmd() string {
rac := CliArg(1)
if CliArg(2) != "" {
rac = rac + " " + CliArg(2)
}
if rac == "" {
// check for alias
if IsFlagSet("version") {
rac = "version"
}
if IsFlagSet("help") {
rac = "help"
}
}
return rac
}
// racIsSupported checks, if the combination 'realm command' has json support
func racIsSupported(rac string) bool {
if _, ok := supportedRAC[rac]; !ok {
// rac not a valid combination
// return true to let PrintHelpAndExit later do it's job
return true
}
return supportedRAC[rac]
}
// JNoteListEntryInit initialises a JNoteListEntry variable
// used in NoteActionList
func JNoteListEntryInit() JNoteListEntry {
newListEntry := JNoteListEntry{
NoteID: "",
NoteDesc: "",
NoteRef: make([]string, 0),
NoteVers: "",
NoteRdate: "",
ManEnabled: false,
SolEnabled: false,
ManReverted: false,
NoteOverride: false,
CustomNote: false,
}
return newListEntry
}