Skip to content

Commit 8174b29

Browse files
authored
Merge Workbench (#551)
* Import dbus and systemd (#541) * Import and merge dbus usage form workbench * Import and merge systemd package from workbench * Update NewSystemD signature to get logger as option * Import utils code from workbench (#542) * Add retry package as support code * Improve logging utils with workbench code * Update command executor to add CombinedOutput * Import cluster core (#544) * Replace cluster systemctl by existing systemd code * Import workbench cluster pkg as cmdclient * Add dosctring to IsActive and IsEnabled * Import and merge sapcontrol code from workbench (#546) * Import and merger saptune pkg code from workbench (#547) * Import and merger saptune pkg code from workbench * Use a custom type as saptune arg in gatherer * Import and copy operators code from workbench (#548) * Import and copy operators code from workbench * Ignore dupl linter in operator tests * Add new operation new cmd command (#549) * Move workbench docs (#550) * Move workbench docs * Use imagesdir to set images folder
1 parent 92b108f commit 8174b29

File tree

140 files changed

+17808
-1772
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

140 files changed

+17808
-1772
lines changed

.golangci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ linters:
4040
rules:
4141
- linters:
4242
- lll
43+
# ignore dupl in tests, as we prefer copy-paste over abstraction there
44+
- dupl
4345
path: _test\.go
4446
- linters:
4547
- revive

.mockery.yaml

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,38 @@ packages:
4444
dir: "pkg/utils/mocks"
4545
interfaces:
4646
CommandExecutor:
47-
github.com/trento-project/agent/internal/core/hosts/systemd:
47+
github.com/trento-project/agent/internal/core/systemd:
4848
config:
4949
outpkg: "mocks"
50-
dir: "internal/core/hosts/systemd/mocks"
50+
dir: "internal/core/systemd/mocks"
5151
interfaces:
52-
DbusConnector:
52+
Systemd:
53+
Loader:
54+
github.com/trento-project/agent/internal/core/dbus:
55+
config:
56+
outpkg: "mocks"
57+
dir: "internal/core/dbus/mocks"
58+
interfaces:
59+
Connector:
60+
github.com/trento-project/agent/internal/core/cluster:
61+
config:
62+
outpkg: "mocks"
63+
dir: "internal/core/cluster/mocks"
64+
interfaces:
65+
CmdClient:
66+
github.com/trento-project/agent/internal/core/saptune:
67+
config:
68+
outpkg: "mocks"
69+
dir: "internal/core/saptune/mocks"
70+
interfaces:
71+
Saptune:
72+
github.com/trento-project/agent/internal/operations/operator:
73+
config:
74+
outpkg: "operator"
75+
dir: "internal/operations/operator"
76+
interfaces:
77+
phaser:
78+
Operator:
79+
config:
80+
outpkg: "mocks"
81+
dir: "internal/operations/operator/mocks"

cmd/operator.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"log/slog"
8+
"os"
9+
"os/signal"
10+
"syscall"
11+
12+
"github.com/spf13/cobra"
13+
"github.com/spf13/pflag"
14+
"github.com/spf13/viper"
15+
16+
"github.com/trento-project/agent/internal/agent"
17+
"github.com/trento-project/agent/internal/operations/operator"
18+
"github.com/trento-project/agent/pkg/utils"
19+
)
20+
21+
func NewOperatorCmd() *cobra.Command {
22+
operatorCmd := &cobra.Command{ //nolint
23+
Use: "operator",
24+
Short: "Run operator related commands",
25+
Hidden: true,
26+
}
27+
28+
operatorCmd.AddCommand(NewOperatorRunCmd())
29+
operatorCmd.AddCommand(NewOperatorListCmd())
30+
31+
return operatorCmd
32+
}
33+
34+
func NewOperatorRunCmd() *cobra.Command {
35+
runCmd := &cobra.Command{ //nolint
36+
Use: "run",
37+
Short: "Run operator",
38+
Run: runOperator,
39+
PersistentPreRunE: func(agentCmd *cobra.Command, _ []string) error {
40+
agentCmd.Flags().VisitAll(func(f *pflag.Flag) {
41+
err := viper.BindPFlag(f.Name, f)
42+
if err != nil {
43+
panic(fmt.Errorf("error during cli init: %w", err))
44+
}
45+
})
46+
47+
return agent.InitConfig("agent")
48+
},
49+
}
50+
51+
runCmd.Flags().StringP("operator", "o", "", "The operator to use")
52+
runCmd.Flags().StringP("arguments", "a", "", "The used operator arguments")
53+
err := runCmd.MarkFlagRequired("operator")
54+
if err != nil {
55+
panic(err)
56+
}
57+
58+
return runCmd
59+
}
60+
61+
func NewOperatorListCmd() *cobra.Command {
62+
listCmd := &cobra.Command{ //nolint
63+
Use: "list",
64+
Short: "List the available operators",
65+
Run: listOperators,
66+
PersistentPreRunE: func(agentCmd *cobra.Command, _ []string) error {
67+
agentCmd.Flags().VisitAll(func(f *pflag.Flag) {
68+
err := viper.BindPFlag(f.Name, f)
69+
if err != nil {
70+
panic(fmt.Errorf("error during cli init: %w", err))
71+
}
72+
})
73+
74+
return agent.InitConfig("agent")
75+
},
76+
}
77+
78+
return listCmd
79+
}
80+
81+
func runOperator(cmd *cobra.Command, _ []string) {
82+
var operatorName = viper.GetString("operator")
83+
var arguments = viper.GetString("arguments")
84+
var logger = utils.NewDefaultLogger(
85+
viper.GetString("log-level"),
86+
)
87+
88+
slog.SetDefault(logger)
89+
slog.Info("Operation", "operator", operatorName, "arguments", arguments)
90+
91+
opArgs := make(operator.Arguments)
92+
err := json.Unmarshal([]byte(arguments), &opArgs)
93+
if err != nil {
94+
logger.Error("error unmarshalling arguments", "err", err)
95+
os.Exit(1)
96+
}
97+
98+
registry := operator.StandardRegistry()
99+
operatorBuilder, err := registry.GetOperatorBuilder(operatorName)
100+
if err != nil {
101+
logger.Error("error building operator", "err", err)
102+
os.Exit(1)
103+
}
104+
105+
ctx, cancel := context.WithCancel(cmd.Context())
106+
107+
signals := make(chan os.Signal, 1)
108+
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
109+
cancelled := false
110+
go func() {
111+
<-signals
112+
slog.Info("Caught signal!")
113+
cancelled = true
114+
cancel()
115+
116+
}()
117+
118+
op := operatorBuilder("", opArgs)
119+
report := op.Run(ctx)
120+
121+
if cancelled {
122+
slog.Info("Operation cancelled")
123+
return
124+
}
125+
126+
if report.Error != nil {
127+
logger.Error(report.Error.Error())
128+
os.Exit(0)
129+
}
130+
131+
diff, err := json.Marshal(report.Success.Diff)
132+
if err != nil {
133+
logger.Error("error marshalling diff output", "err", err)
134+
os.Exit(1)
135+
}
136+
137+
logger.Info("Operation succeeded",
138+
"phase", report.Success.LastPhase,
139+
"diff", string(diff),
140+
)
141+
}
142+
143+
func listOperators(*cobra.Command, []string) {
144+
var logger = utils.NewDefaultLogger(
145+
viper.GetString("log-level"),
146+
)
147+
148+
slog.SetDefault(logger)
149+
150+
registry := operator.StandardRegistry()
151+
operators := registry.AvailableOperators()
152+
153+
slog.Info("Available operators:")
154+
155+
for _, o := range operators {
156+
slog.Info(o)
157+
}
158+
}

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ that can help you deploy, provision and operate infrastructure for SAP Applicati
4545
rootCmd.AddCommand(NewFactsCmd())
4646
rootCmd.AddCommand(NewVersionCmd())
4747
rootCmd.AddCommand(NewGenerateCmd())
48+
rootCmd.AddCommand(NewOperatorCmd())
4849

4950
return rootCmd
5051
}

docs/images/flow_chart.png

25 KB
Loading

docs/operators.adoc

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
ifndef::imagesdir[:imagesdir: ./images]
2+
3+
= Trento Agent Operators
4+
5+
An `+operator+` is a unit of code that can perform write operations on target machines.
6+
7+
Each operator knows how to execute actions and how to roll them back in
8+
case of failure.
9+
10+
== Operator
11+
12+
image::flow_chart.png[flowchart]
13+
14+
An operator is a unit of code that can perform write operations on
15+
target machines. +
16+
A write operation can either fail or succeed. If it succeeds, the
17+
operation displays a diff highlighting the changes before and after the
18+
commit phase.
19+
20+
The operator accepts arguments to specify how to perform the operations.
21+
The arguments are in the form of a `+map[string]any+`, and each operator
22+
knows how to extract and validate them. +
23+
It follows a transactional approach, where each operation has distinct
24+
stages:
25+
26+
* PLAN +
27+
* COMMIT +
28+
* VERIFY +
29+
* ROLLBACK
30+
31+
The documentation for each of the operators can be found in the
32+
operators go source.
33+
34+
=== PLAN
35+
36+
The goal of the PLAN stage is to collect information about the
37+
operations and verify prerequisites. +
38+
This is also the phase where information for diffing is gathered by
39+
collecting the `+before+` state.
40+
41+
Additionally, during this phase, it is important to ensure that any
42+
resources modified during the COMMIT phase are backed up. This allows
43+
restoration during the ROLLBACK phase or manual recovery by system
44+
administrators if the rollback fails.
45+
46+
If an error occurs during the PLAN phase, no rollback is needed; the
47+
operation is simply aborted with the plan error.
48+
49+
=== COMMIT
50+
51+
The COMMIT phase executes the actual write operations on the system,
52+
utilizing the information collected during the PLAN phase. +
53+
If an error occurs during this phase, a rollback is triggered.
54+
55+
The COMMIT phase should be idempotent. If a requested change has already
56+
been applied, the commit operation is simply skipped without returning
57+
an error. +
58+
Idempotency should be implemented appropriately based on the type of
59+
operations to be performed.
60+
61+
=== VERIFY
62+
63+
The VERIFY phase ensures that the actions applied during the COMMIT
64+
phase have produced the expected results on the system. +
65+
If an error occurs during this phase, the rollback process is initiated.
66+
67+
This phase is also when the `+after+` state is recorded for the diff,
68+
highlighting changes made during the commit phase.
69+
70+
=== ROLLBACK
71+
72+
The ROLLBACK phase must implement mechanisms to revert any changes made
73+
during the COMMIT phase. +
74+
It can use information collected during the PLAN phase to restore the
75+
system to its previous state. +
76+
The rollback implementation may vary depending on the type of operation
77+
performed during the COMMIT phase. +
78+
Be sure to provide clear error messages and log actions appropriately.
79+
80+
If the rollback fails, the error is returned without further action.
81+
82+
== Executor
83+
84+
The Executor is a wrapper around an operator. The operator implements
85+
the phase interface and must be wrapped in an Executor.
86+
87+
The Executor manages operations transactionally. +
88+
For library users, the Executor is transparent—using an Operator means
89+
it is already wrapped within an Executor.
90+
91+
== Registry
92+
93+
The Registry holds all available operators. Each operator has a version.
94+
By default, if no version is specified when requesting an operator, the
95+
latest version is fetched from the Registry.
96+
97+
To use an operator, it must be fetched using the `+GetOperatorBuilder+`
98+
function, providing the operator name as an argument.
99+
100+
[source,go]
101+
----
102+
builder, err := registry.GetOperatorBuilder(operatorName)
103+
op := builder("test-cli", opArgs)
104+
report := op.Run(ctx)
105+
// the report contains the success or the error of the execution
106+
----
107+
108+
The operator name follows this format: `+<operatorname>@<version>+`.
109+
110+
The Registry returns an Operator Builder:
111+
112+
[source,go]
113+
----
114+
type OperatorBuilder func(operationID string, arguments OperatorArguments) Operator
115+
----
116+
117+
The `+operationID+` is a unique identifier for the operation, and
118+
`+arguments+` are modeled as `+map[string]any+`.
119+
120+
== CLI
121+
122+
The operators execution is also exposed as a `+CLI+`. The functionality is
123+
hidden as it is intended only for testing and development purposes.
124+
125+
=== Usage
126+
127+
....
128+
Run operator
129+
130+
Usage:
131+
trento-agent operator run [flags]
132+
133+
Flags:
134+
-a, --arguments string The used operator arguments
135+
-h, --help help for run
136+
-o, --operator string The operator to use
137+
138+
....
139+
140+
The CLI accepts the name of an operator as an argument, following the
141+
same convention `+<operatorname>@<version>+`, and requires the `+-a+`
142+
option to pass the arguments.
143+
144+
The arguments must be provided as a JSON string.
145+
146+
=== Example
147+
148+
[source,bash]
149+
----
150+
sudo ./trento-agent operator run -o saptuneapplysolution -a '{"solution": "HANA"}'
151+
----
152+
153+
Using `+sudo+` may be necessary depending on the type of operator being
154+
executed. +
155+
In this example, the `+saptuneapplysolution+` operator is called with
156+
the argument `+solution+` set to `+HANA+`.
157+
158+
The CLI will perform the operations, log any errors, and finally display
159+
the diff when the execution succeeds.

0 commit comments

Comments
 (0)