Skip to content

Commit c52fec4

Browse files
authored
feat: utils package for better DRY (#3)
1 parent 0c223a4 commit c52fec4

File tree

11 files changed

+387
-377
lines changed

11 files changed

+387
-377
lines changed

pkg/builder/builder.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ package builder
44
import (
55
"encoding/json"
66
"fmt"
7+
"strconv"
78

9+
"github.com/init4tech/signet-infra-components/pkg/utils"
810
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam"
911
crd "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions"
1012
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
@@ -13,6 +15,18 @@ import (
1315
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
1416
)
1517

18+
// parseBuilderPort converts a port string to an integer with a default fallback
19+
func parseBuilderPort(portStr pulumi.StringInput) pulumi.IntOutput {
20+
return pulumi.All(portStr).ApplyT(func(inputs []interface{}) int {
21+
portStr := inputs[0].(string)
22+
if port, err := strconv.Atoi(portStr); err == nil {
23+
return port
24+
}
25+
// Default to 8080 if there's an error parsing the port
26+
return 8080
27+
}).(pulumi.IntOutput)
28+
}
29+
1630
// NewBuilder creates a new builder component with the given configuration.
1731
func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.ResourceOption) (*BuilderComponent, error) {
1832
if err := args.Validate(); err != nil {
@@ -99,6 +113,23 @@ func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.R
99113
return nil, fmt.Errorf("failed to attach policy to role: %w", err)
100114
}
101115

116+
// Create ConfigMap for environment variables
117+
configMap, err := utils.CreateConfigMap(
118+
ctx,
119+
fmt.Sprintf("%s-configmap", args.Name),
120+
pulumi.String(args.Namespace),
121+
pulumi.StringMap{
122+
"app": pulumi.String(args.Name),
123+
"app.kubernetes.io/name": pulumi.String(args.Name),
124+
"app.kubernetes.io/part-of": pulumi.String(args.Name),
125+
},
126+
args.BuilderEnv,
127+
)
128+
if err != nil {
129+
return nil, fmt.Errorf("failed to create environment ConfigMap: %w", err)
130+
}
131+
component.ConfigMap = configMap
132+
102133
// Create deployment
103134
deployment, err := appsv1.NewDeployment(ctx, fmt.Sprintf("%s-deployment", args.Name), &appsv1.DeploymentArgs{
104135
Metadata: &metav1.ObjectMetaArgs{
@@ -120,10 +151,16 @@ func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.R
120151
&corev1.ContainerArgs{
121152
Name: pulumi.String(args.Name),
122153
Image: pulumi.String(args.Image),
123-
Env: createEnvVars(args.BuilderEnv),
154+
EnvFrom: corev1.EnvFromSourceArray{
155+
&corev1.EnvFromSourceArgs{
156+
ConfigMapRef: &corev1.ConfigMapEnvSourceArgs{
157+
Name: component.ConfigMap.Metadata.Name(),
158+
},
159+
},
160+
},
124161
Ports: corev1.ContainerPortArray{
125162
&corev1.ContainerPortArgs{
126-
ContainerPort: args.BuilderEnv.BuilderPort,
163+
ContainerPort: parseBuilderPort(args.BuilderEnv.BuilderPort),
127164
},
128165
&corev1.ContainerPortArgs{
129166
ContainerPort: pulumi.Int(MetricsPort),
@@ -142,7 +179,7 @@ func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.R
142179
LivenessProbe: &corev1.ProbeArgs{
143180
HttpGet: &corev1.HTTPGetActionArgs{
144181
Path: pulumi.String("/healthcheck"),
145-
Port: args.BuilderEnv.BuilderPort,
182+
Port: parseBuilderPort(args.BuilderEnv.BuilderPort),
146183
},
147184
InitialDelaySeconds: pulumi.Int(5),
148185
PeriodSeconds: pulumi.Int(1),
@@ -152,7 +189,7 @@ func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.R
152189
ReadinessProbe: &corev1.ProbeArgs{
153190
HttpGet: &corev1.HTTPGetActionArgs{
154191
Path: pulumi.String("/healthcheck"),
155-
Port: args.BuilderEnv.BuilderPort,
192+
Port: parseBuilderPort(args.BuilderEnv.BuilderPort),
156193
},
157194
InitialDelaySeconds: pulumi.Int(5),
158195
PeriodSeconds: pulumi.Int(10),
@@ -183,8 +220,8 @@ func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.R
183220
Selector: args.AppLabels.Labels,
184221
Ports: corev1.ServicePortArray{
185222
&corev1.ServicePortArgs{
186-
Port: args.BuilderEnv.BuilderPort,
187-
TargetPort: args.BuilderEnv.BuilderPort,
223+
Port: parseBuilderPort(args.BuilderEnv.BuilderPort),
224+
TargetPort: parseBuilderPort(args.BuilderEnv.BuilderPort),
188225
Name: pulumi.String("http"),
189226
},
190227
&corev1.ServicePortArgs{

pkg/builder/helpers.go

Lines changed: 0 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ package builder
22

33
import (
44
"encoding/json"
5-
"reflect"
6-
"strings"
7-
"unicode"
85

9-
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
106
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
117
)
128

@@ -36,93 +32,3 @@ func CreateKMSPolicy(key pulumi.StringInput) pulumi.StringOutput {
3632
return string(jsonBytes), nil
3733
}).(pulumi.StringOutput)
3834
}
39-
40-
// CreateEnvVars creates environment variables by automatically mapping
41-
// struct field names to environment variable names.
42-
// Exported for testing.
43-
func CreateEnvVars(env BuilderEnv) corev1.EnvVarArray {
44-
result := corev1.EnvVarArray{}
45-
46-
// Special case for BuilderPort as it needs string conversion
47-
result = append(result, &corev1.EnvVarArgs{
48-
Name: pulumi.String("BUILDER_PORT"),
49-
Value: pulumi.Sprintf("%d", env.BuilderPort),
50-
})
51-
52-
// Process all string inputs from the struct's tags
53-
envVarMap := GetEnvironmentVarsFromStruct(env)
54-
for name, value := range envVarMap {
55-
if name != "BUILDER_PORT" { // Skip the one we already handled
56-
result = append(result, &corev1.EnvVarArgs{
57-
Name: pulumi.String(name),
58-
Value: value.(pulumi.StringInput),
59-
})
60-
}
61-
}
62-
63-
return result
64-
}
65-
66-
// createEnvVars is kept for backward compatibility
67-
func createEnvVars(env BuilderEnv) corev1.EnvVarArray {
68-
return CreateEnvVars(env)
69-
}
70-
71-
// GetEnvironmentVarsFromStruct uses reflection to extract environment variables from struct tags
72-
// Exported for testing.
73-
func GetEnvironmentVarsFromStruct(env BuilderEnv) map[string]pulumi.Input {
74-
result := make(map[string]pulumi.Input)
75-
76-
t := reflect.TypeOf(env)
77-
v := reflect.ValueOf(env)
78-
79-
for i := 0; i < t.NumField(); i++ {
80-
field := t.Field(i)
81-
82-
// Get the field value
83-
fieldValue := v.Field(i).Interface()
84-
85-
// Skip nil values and BuilderPort (handled specially)
86-
if fieldValue == nil || field.Name == "BuilderPort" {
87-
continue
88-
}
89-
90-
// Convert camelCase to SNAKE_CASE for env var name
91-
envName := CamelToSnake(field.Name)
92-
93-
// Add to map
94-
result[envName] = fieldValue.(pulumi.Input)
95-
}
96-
97-
return result
98-
}
99-
100-
// getEnvironmentVarsFromStruct is kept for backward compatibility
101-
func getEnvironmentVarsFromStruct(env BuilderEnv) map[string]pulumi.Input {
102-
return GetEnvironmentVarsFromStruct(env)
103-
}
104-
105-
// CamelToSnake converts a camelCase string to SNAKE_CASE
106-
// Exported for testing purposes
107-
func CamelToSnake(s string) string {
108-
var result strings.Builder
109-
110-
// Handle consecutive uppercase characters (acronyms)
111-
for i, r := range s {
112-
if unicode.IsUpper(r) {
113-
// Add underscore if not the first character and either:
114-
// 1. Previous character is lowercase, or
115-
// 2. Not the last character and next character is lowercase (end of acronym)
116-
needsUnderscore := i > 0 && (unicode.IsLower(rune(s[i-1])) ||
117-
(i < len(s)-1 && unicode.IsLower(rune(s[i+1])) && i > 1 && unicode.IsUpper(rune(s[i-1]))))
118-
119-
if needsUnderscore {
120-
result.WriteRune('_')
121-
}
122-
result.WriteRune(unicode.ToUpper(r))
123-
} else {
124-
result.WriteRune(unicode.ToUpper(r))
125-
}
126-
}
127-
return result.String()
128-
}

pkg/builder/helpers_test.go

Lines changed: 11 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,6 @@ import (
77
"github.com/stretchr/testify/assert"
88
)
99

10-
func TestCamelToSnake(t *testing.T) {
11-
testCases := []struct {
12-
input string
13-
output string
14-
}{
15-
{
16-
input: "BuilderPort",
17-
output: "BUILDER_PORT",
18-
},
19-
{
20-
input: "HostRpcUrl",
21-
output: "HOST_RPC_URL",
22-
},
23-
{
24-
input: "AWSRegion",
25-
output: "AWS_REGION",
26-
},
27-
{
28-
input: "OtelExporterOtlpEndpoint",
29-
output: "OTEL_EXPORTER_OTLP_ENDPOINT",
30-
},
31-
{
32-
input: "simpleText",
33-
output: "SIMPLE_TEXT",
34-
},
35-
}
36-
37-
for _, tc := range testCases {
38-
t.Run(tc.input, func(t *testing.T) {
39-
result := CamelToSnake(tc.input)
40-
assert.Equal(t, tc.output, result)
41-
})
42-
}
43-
}
44-
4510
func TestCreateKMSPolicy(t *testing.T) {
4611
// Test with a simple key ARN
4712
keyArn := "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab"
@@ -62,10 +27,10 @@ func TestCreateKMSPolicy(t *testing.T) {
6227
assert.NotNil(t, anotherPolicy)
6328
}
6429

65-
func TestCreateEnvVars(t *testing.T) {
30+
func TestBuilderEnvGetEnvMap(t *testing.T) {
6631
// Create a test BuilderEnv with some values
6732
env := BuilderEnv{
68-
BuilderPort: pulumi.Int(8080),
33+
BuilderPort: pulumi.String("8080"),
6934
BuilderKey: pulumi.String("test-key"),
7035
HostRpcUrl: pulumi.String("http://host-rpc"),
7136
RollupRpcUrl: pulumi.String("http://rollup-rpc"),
@@ -74,57 +39,22 @@ func TestCreateEnvVars(t *testing.T) {
7439
AwsAccountId: pulumi.String("123456789012"),
7540
}
7641

77-
// Get the environment variables
78-
envVars := CreateEnvVars(env)
79-
80-
// Test that the array is not nil and has the correct length
81-
// We expect one env var for BuilderPort plus one for each of the other fields
82-
assert.NotNil(t, envVars)
83-
84-
// Count the non-nil fields in env to determine expected size
85-
// We need to do this because BuilderEnv has many fields and we're only setting a few
86-
expectedSize := 0
87-
if env.BuilderPort != nil {
88-
expectedSize++
89-
}
90-
envVarMap := GetEnvironmentVarsFromStruct(env)
91-
expectedSize += len(envVarMap)
92-
93-
// Check if the array is the expected size
94-
assert.Len(t, envVars, expectedSize)
95-
96-
// Test that specific environment variables are set correctly
97-
// Due to the nature of Pulumi outputs, we can only test basics here
98-
// Just check that we have a non-empty array
99-
assert.Greater(t, len(envVars), 0, "Should have at least one environment variable")
100-
}
101-
102-
func TestGetEnvironmentVarsFromStruct(t *testing.T) {
103-
// Create a test BuilderEnv with some values
104-
env := BuilderEnv{
105-
BuilderPort: pulumi.Int(8080),
106-
BuilderKey: pulumi.String("test-key"),
107-
HostRpcUrl: pulumi.String("http://host-rpc"),
108-
AwsRegion: pulumi.String("us-west-2"),
109-
}
110-
11142
// Get the environment variables map
112-
envVars := GetEnvironmentVarsFromStruct(env)
43+
envMap := env.GetEnvMap()
11344

114-
// Test that the map is not nil and has the expected keys
115-
assert.NotNil(t, envVars)
45+
// Test that the map is not nil
46+
assert.NotNil(t, envMap)
11647

117-
// BuilderPort should be excluded (handled specially)
118-
_, hasBuilderPort := envVars["BUILDER_PORT"]
119-
assert.False(t, hasBuilderPort, "BUILDER_PORT should not be in the map")
48+
// Check that specific environment variables are in the map
49+
_, hasBuilderPort := envMap["BUILDER_PORT"]
50+
assert.True(t, hasBuilderPort, "BUILDER_PORT should be in the map")
12051

121-
// Other fields should be included
122-
_, hasBuilderKey := envVars["BUILDER_KEY"]
52+
_, hasBuilderKey := envMap["BUILDER_KEY"]
12353
assert.True(t, hasBuilderKey, "BUILDER_KEY should be in the map")
12454

125-
_, hasHostRpcUrl := envVars["HOST_RPC_URL"]
55+
_, hasHostRpcUrl := envMap["HOST_RPC_URL"]
12656
assert.True(t, hasHostRpcUrl, "HOST_RPC_URL should be in the map")
12757

128-
_, hasAwsRegion := envVars["AWS_REGION"]
58+
_, hasAwsRegion := envMap["AWS_REGION"]
12959
assert.True(t, hasAwsRegion, "AWS_REGION should be in the map")
13060
}

pkg/builder/types.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package builder
22

33
import (
4+
"github.com/init4tech/signet-infra-components/pkg/utils"
45
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam"
56
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
67
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
@@ -29,6 +30,7 @@ type BuilderComponent struct {
2930
ServiceAccount *corev1.ServiceAccount
3031
IAMRole *iam.Role
3132
IAMPolicy *iam.Policy
33+
ConfigMap *corev1.ConfigMap
3234
}
3335

3436
// BuilderComponentArgs contains the configuration for deploying a builder service.
@@ -52,7 +54,7 @@ type BuilderEnv struct {
5254
BlockQueryStart pulumi.StringInput `pulumi:"blockQueryStart"`
5355
BuilderHelperAddress pulumi.StringInput `pulumi:"builderHelperAddress"`
5456
BuilderKey pulumi.StringInput `pulumi:"builderKey"`
55-
BuilderPort pulumi.IntInput `pulumi:"builderPort"`
57+
BuilderPort pulumi.StringInput `pulumi:"builderPort"`
5658
BuilderRewardsAddress pulumi.StringInput `pulumi:"builderRewardsAddress"`
5759
ChainOffset pulumi.StringInput `pulumi:"chainOffset"`
5860
ConcurrentLimit pulumi.StringInput `pulumi:"concurrentLimit"`
@@ -80,6 +82,13 @@ type BuilderEnv struct {
8082
ZenithAddress pulumi.StringInput `pulumi:"zenithAddress"`
8183
}
8284

85+
// GetEnvMap implements the utils.EnvProvider interface
86+
// It creates a map of environment variables from the BuilderEnv struct
87+
func (e BuilderEnv) GetEnvMap() pulumi.StringMap {
88+
// All fields are now StringInput, so we can use the standard reflection method
89+
return utils.CreateEnvMap(e)
90+
}
91+
8392
type IAMStatement struct {
8493
Sid string `json:"sid,omitempty"`
8594
Effect string `json:"effect"`

pkg/builder/validation_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func TestBuilderComponentArgsValidate(t *testing.T) {
7474
Image: "test-image:latest",
7575
AppLabels: AppLabels{Labels: pulumi.StringMap{"app": pulumi.String("test")}},
7676
BuilderEnv: BuilderEnv{
77-
BuilderPort: pulumi.Int(8080),
77+
BuilderPort: pulumi.String("8080"),
7878
BuilderKey: pulumi.String("test-key"),
7979
HostRpcUrl: pulumi.String("http://host-rpc"),
8080
RollupRpcUrl: pulumi.String("http://rollup-rpc"),
@@ -114,7 +114,7 @@ func TestBuilderEnvValidate(t *testing.T) {
114114
// Test with just BuilderPort set
115115
t.Run("missing builder key", func(t *testing.T) {
116116
env := BuilderEnv{
117-
BuilderPort: pulumi.Int(8080),
117+
BuilderPort: pulumi.String("8080"),
118118
}
119119
err := env.Validate()
120120
assert.Error(t, err)

0 commit comments

Comments
 (0)