Skip to content

Commit b5f4cf9

Browse files
Merge pull request #592 from element-hq/gaelg/matrix-tools-cmd-refactor
matrix-tools cmd refactor
2 parents aee2643 + 1d0ac86 commit b5f4cf9

21 files changed

Lines changed: 602 additions & 326 deletions

File tree

charts/matrix-stack/source/common/sub_schema_values.yaml.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
1313

1414
## The matrix-tools image, used in multiple components
1515
matrixTools:
16-
{{ image(registry="ghcr.io", repository="element-hq/ess-helm/matrix-tools", tag="0.5.3") | indent(2) }}
16+
{{ image(registry="ghcr.io", repository="element-hq/ess-helm/matrix-tools", tag="0.5.4") | indent(2) }}
1717

1818
## CertManager Issuer to configure by default automatically on all ingresses
1919
## If configured, the chart will automatically generate the tlsSecret name for all ingresses

charts/matrix-stack/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ matrixTools:
2020

2121
## The tag of the container image to use.
2222
## One of tag or digest must be provided.
23-
tag: "0.5.3"
23+
tag: "0.5.4"
2424

2525
## Container digest to use. Used to pull the image instead of the image tag if set
2626
## The tag will still be set as the app.kubernetes.io/version label

matrix-tools/cmd/main.go

Lines changed: 10 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,16 @@ package main
66

77
import (
88
"fmt"
9-
"io"
109
"os"
1110

12-
"flag"
13-
11+
deploymentmarkers "github.com/element-hq/ess-helm/matrix-tools/internal/cmd/deployment-markers"
12+
generatesecrets "github.com/element-hq/ess-helm/matrix-tools/internal/cmd/generate-secrets"
13+
renderconfig "github.com/element-hq/ess-helm/matrix-tools/internal/cmd/render-config"
14+
"github.com/element-hq/ess-helm/matrix-tools/internal/cmd/syn2mas"
15+
"github.com/element-hq/ess-helm/matrix-tools/internal/cmd/tcpwait"
1416
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/args"
15-
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/marker"
16-
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/renderer"
17-
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/secret"
18-
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/syn2mas"
19-
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/tcpwait"
20-
"github.com/pkg/errors"
21-
"gopkg.in/yaml.v3"
22-
"k8s.io/client-go/kubernetes"
23-
"k8s.io/client-go/rest"
2417
)
2518

26-
func getKubernetesClient() (kubernetes.Interface, error) {
27-
config, err := rest.InClusterConfig()
28-
if err != nil {
29-
return nil, err
30-
}
31-
clientset, err := kubernetes.NewForConfig(config)
32-
if err != nil {
33-
return nil, err
34-
}
35-
return clientset, nil
36-
}
37-
38-
func readFiles(paths []string) ([]io.Reader, []func() error, error) {
39-
files := make([]io.Reader, 0)
40-
closeFiles := make([]func() error, 0)
41-
for _, path := range paths {
42-
fileReader, err := os.Open(path)
43-
if err != nil {
44-
return files, closeFiles, fmt.Errorf("failed to open file: %w", err)
45-
}
46-
files = append(files, fileReader)
47-
closeFiles = append(closeFiles, fileReader.Close)
48-
}
49-
return files, closeFiles, nil
50-
}
5119

5220
func main() {
5321
options, err := args.ParseArgs(os.Args)
@@ -58,97 +26,15 @@ func main() {
5826

5927
switch options.Command {
6028
case args.RenderConfig:
61-
fileReaders, closeFiles, err := readFiles(options.Files)
62-
defer func() {
63-
for _, closeFn := range closeFiles {
64-
err := closeFn()
65-
if err != nil {
66-
fmt.Println("Error closing file : ", err)
67-
}
68-
}
69-
}()
70-
if err != nil {
71-
fmt.Println(err)
72-
os.Exit(1)
73-
}
74-
result, err := renderer.RenderConfig(fileReaders)
75-
if err != nil {
76-
if err == flag.ErrHelp {
77-
flag.CommandLine.Usage()
78-
} else {
79-
fmt.Println("Error:", err)
80-
}
81-
os.Exit(1)
82-
}
83-
var outputYAML []byte
84-
if outputYAML, err = yaml.Marshal(result); err != nil {
85-
fmt.Println("Error marshalling merged config to YAML:", err)
86-
os.Exit(1)
87-
}
88-
89-
fmt.Printf("Rendering config to file: %v\n", options.Output)
90-
if os.Getenv("DEBUG_RENDERING") == "1" {
91-
fmt.Println(string(outputYAML))
92-
}
93-
err = os.WriteFile(options.Output, outputYAML, 0440)
94-
if err != nil {
95-
fmt.Println("Error writing to file:", err)
96-
os.Exit(1)
97-
}
29+
renderconfig.Run(options.RenderConfig)
9830
case args.TCPWait:
99-
tcpwait.WaitForTCP(options.Address)
31+
tcpwait.Run(options.TcpWait)
10032
case args.Syn2Mas:
101-
clientset, err := getKubernetesClient()
102-
if err != nil {
103-
fmt.Println("Error getting Kubernetes client: ", err)
104-
os.Exit(1)
105-
}
106-
namespace := os.Getenv("NAMESPACE")
107-
if namespace == "" {
108-
fmt.Println("Error, $NAMESPACE is not defined")
109-
os.Exit(1)
110-
}
111-
syn2mas.RunSyn2MAS(clientset, namespace, options.SynapseConfig, options.MASConfig)
33+
syn2mas.Run(options.Syn2Mas)
11234
case args.GenerateSecrets:
113-
clientset, err := getKubernetesClient()
114-
if err != nil {
115-
fmt.Println("Error getting Kubernetes client: ", err)
116-
os.Exit(1)
117-
}
118-
namespace := os.Getenv("NAMESPACE")
119-
if namespace == "" {
120-
fmt.Println("Error, $NAMESPACE is not defined")
121-
os.Exit(1)
122-
}
123-
124-
for _, generatedSecret := range options.GeneratedSecrets {
125-
err := secret.GenerateSecret(clientset, options.Labels, namespace, generatedSecret.Name, generatedSecret.Key, generatedSecret.Type)
126-
if err != nil {
127-
wrappedErr := errors.Wrapf(err, "error generating secret: %s", generatedSecret.ArgValue)
128-
fmt.Println("Error:", wrappedErr)
129-
os.Exit(1)
130-
}
131-
}
35+
generatesecrets.Run(options.GenerateSecrets)
13236
case args.DeploymentMarkers:
133-
clientset, err := getKubernetesClient()
134-
if err != nil {
135-
fmt.Println("Error getting Kubernetes client: ", err)
136-
os.Exit(1)
137-
}
138-
namespace := os.Getenv("NAMESPACE")
139-
if namespace == "" {
140-
fmt.Println("Error, $NAMESPACE is not defined")
141-
os.Exit(1)
142-
}
143-
144-
for _, depMarker := range options.DeploymentMarkers {
145-
err := marker.GenerateConfigMap(clientset, options.Labels, namespace, depMarker.Name, depMarker.Key, depMarker.Step, depMarker.NewValue, depMarker.AllowedValues)
146-
if err != nil {
147-
wrappedErr := errors.Wrapf(err, "error generating configmap: %v", depMarker)
148-
fmt.Println("Error:", wrappedErr)
149-
os.Exit(1)
150-
}
151-
}
37+
deploymentmarkers.Run(options.DeploymentMarkers)
15238
default:
15339
fmt.Printf("Unknown command")
15440
os.Exit(1)

matrix-tools/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require gopkg.in/yaml.v3 v3.0.1
1010

1111
require (
1212
github.com/pkg/errors v0.9.1
13+
github.com/stretchr/testify v1.10.0
1314
k8s.io/api v0.33.2
1415
k8s.io/apimachinery v0.33.2
1516
k8s.io/client-go v0.33.2
@@ -33,6 +34,7 @@ require (
3334
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
3435
github.com/modern-go/reflect2 v1.0.2 // indirect
3536
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
37+
github.com/pmezard/go-difflib v1.0.0 // indirect
3638
github.com/x448/float16 v0.8.4 // indirect
3739
golang.org/x/net v0.38.0 // indirect
3840
golang.org/x/oauth2 v0.27.0 // indirect
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2025 New Vector Ltd
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-only
4+
5+
package deploymentmarkers
6+
7+
import (
8+
"flag"
9+
"fmt"
10+
"strings"
11+
)
12+
13+
const (
14+
FlagSetName = "deployment-markers"
15+
)
16+
17+
type DeploymentMarkersOptions struct {
18+
Labels map[string]string
19+
DeploymentMarkers []DeploymentMarker
20+
}
21+
22+
23+
type DeploymentMarker struct {
24+
Name string
25+
Key string
26+
Step string
27+
NewValue string
28+
AllowedValues []string
29+
}
30+
31+
func ParseArgs(args []string) (*DeploymentMarkersOptions, error) {
32+
options := &DeploymentMarkersOptions{}
33+
34+
deploymentMarkersSet := flag.NewFlagSet(FlagSetName, flag.ExitOnError)
35+
deploymentMarkers := deploymentMarkersSet.String("markers", "", "Comma-separated list of deployment markers, with Semi-colon separated list of previous allowed values in the format of `name:step:newValue:[allowedValues;..]`")
36+
labels := deploymentMarkersSet.String("labels", "", "Comma-separated list of labels for generated secrets, in the format of `key=value`")
37+
step := deploymentMarkersSet.String("step", "", "One of `pre` or `post`")
38+
39+
err := deploymentMarkersSet.Parse(args)
40+
if err != nil {
41+
return nil, err
42+
}
43+
for _, deploymentMarkerArg := range strings.Split(*deploymentMarkers, ",") {
44+
parsedValue := strings.Split(deploymentMarkerArg, ":")
45+
if len(parsedValue) < 3 {
46+
return nil, fmt.Errorf("invalid deployment marker format, expect <name:key:newValue:[allowedValues;..]>: %s", deploymentMarkerArg)
47+
}
48+
parsedAllowedValues := strings.Split(parsedValue[3], ";")
49+
deploymentMarker := DeploymentMarker{Name: parsedValue[0], Key: parsedValue[1], Step: *step, NewValue: parsedValue[2], AllowedValues: parsedAllowedValues}
50+
options.DeploymentMarkers = append(options.DeploymentMarkers, deploymentMarker)
51+
}
52+
options.Labels = make(map[string]string)
53+
if *labels != "" {
54+
for _, label := range strings.Split(*labels, ",") {
55+
parsedLabelValue := strings.Split(label, "=")
56+
options.Labels[parsedLabelValue[0]] = parsedLabelValue[1]
57+
}
58+
}
59+
options.Labels["app.kubernetes.io/managed-by"] = "matrix-tools-deployment-markers"
60+
return options, nil
61+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2025 New Vector Ltd
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-only
4+
5+
package deploymentmarkers
6+
7+
import (
8+
"fmt"
9+
"os"
10+
11+
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/marker"
12+
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/util"
13+
"github.com/pkg/errors"
14+
)
15+
16+
func Run(options *DeploymentMarkersOptions) {
17+
clientset, err := util.GetKubernetesClient()
18+
if err != nil {
19+
fmt.Println("Error getting Kubernetes client: ", err)
20+
os.Exit(1)
21+
}
22+
namespace := os.Getenv("NAMESPACE")
23+
if namespace == "" {
24+
fmt.Println("Error, $NAMESPACE is not defined")
25+
os.Exit(1)
26+
}
27+
28+
for _, depMarker := range options.DeploymentMarkers {
29+
err := marker.GenerateConfigMap(clientset, options.Labels, namespace, depMarker.Name, depMarker.Key, depMarker.Step, depMarker.NewValue, depMarker.AllowedValues)
30+
if err != nil {
31+
wrappedErr := errors.Wrapf(err, "error generating configmap: %v", depMarker)
32+
fmt.Println("Error:", wrappedErr)
33+
os.Exit(1)
34+
}
35+
}
36+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2025 New Vector Ltd
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-only
4+
5+
package generatesecrets
6+
7+
import (
8+
"flag"
9+
"fmt"
10+
"strings"
11+
12+
"github.com/element-hq/ess-helm/matrix-tools/internal/pkg/secret"
13+
)
14+
15+
const (
16+
FlagSetName = "generate-secrets"
17+
)
18+
19+
type GenerateSecretsOptions struct {
20+
GeneratedSecrets []GeneratedSecret
21+
Labels map[string]string
22+
}
23+
24+
type GeneratedSecret struct {
25+
ArgValue string
26+
Name string
27+
Key string
28+
Type secret.SecretType
29+
}
30+
31+
func parseSecretType(value string) (secret.SecretType, error) {
32+
switch value {
33+
case "rand32":
34+
return secret.Rand32, nil
35+
case "signingkey":
36+
return secret.SigningKey, nil
37+
case "hex32":
38+
return secret.Hex32, nil
39+
case "rsa":
40+
return secret.RSA, nil
41+
case "ecdsaprime256v1":
42+
return secret.EcdsaPrime256v1, nil
43+
case "ecdsasecp256k1":
44+
return secret.EcdsaSecp256k1, nil
45+
default:
46+
return secret.UnknownSecretType, fmt.Errorf("unknown secret type: %s", value)
47+
}
48+
}
49+
50+
51+
func ParseArgs(args []string) (*GenerateSecretsOptions, error) {
52+
var options GenerateSecretsOptions
53+
54+
generateSecretsSet := flag.NewFlagSet("generate-secrets", flag.ExitOnError)
55+
secrets := generateSecretsSet.String("secrets", "", "Comma-separated list of secrets to generate, in the format of `name:key:type`, where `type` is one of: rand32")
56+
secretsLabels := generateSecretsSet.String("labels", "", "Comma-separated list of labels for generated secrets, in the format of `key=value`")
57+
58+
err := generateSecretsSet.Parse(args)
59+
if err != nil {
60+
return nil, err
61+
}
62+
for _, generatedSecretArg := range strings.Split(*secrets, ",") {
63+
parsedValue := strings.Split(generatedSecretArg, ":")
64+
if len(parsedValue) < 3 {
65+
return nil, fmt.Errorf("invalid generated secret format, expect <name:key:type:...>: %s", generatedSecretArg)
66+
}
67+
var parsedSecretType secret.SecretType
68+
if parsedSecretType, err = parseSecretType(parsedValue[2]); err != nil {
69+
return nil, fmt.Errorf("invalid secret type in %s : %v", generatedSecretArg, err)
70+
}
71+
72+
generatedSecret := GeneratedSecret{ArgValue: generatedSecretArg, Name: parsedValue[0], Key: parsedValue[1], Type: parsedSecretType}
73+
options.GeneratedSecrets = append(options.GeneratedSecrets, generatedSecret)
74+
}
75+
options.Labels = make(map[string]string)
76+
if *secretsLabels != "" {
77+
for _, label := range strings.Split(*secretsLabels, ",") {
78+
parsedLabelValue := strings.Split(label, "=")
79+
options.Labels[parsedLabelValue[0]] = parsedLabelValue[1]
80+
}
81+
}
82+
options.Labels["app.kubernetes.io/managed-by"] = "matrix-tools-init-secrets"
83+
return &options, nil
84+
}

0 commit comments

Comments
 (0)