Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(): layer2 support for CAPP #787

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
05cbc0c
API and CRD changes for adding layer2 support
rahulii Aug 22, 2024
15aaba7
json tag fix
rahulii Aug 22, 2024
ebfddac
change ipAddressReservation to address
rahulii Aug 29, 2024
1d2b71e
make changes to the api as per design doc changes
rahulii Sep 17, 2024
e1a19b5
minor change
rahulii Sep 17, 2024
cdf7155
api changes and user-data implementation
rahulii Sep 25, 2024
4cfb7b8
add IPAddressManagement Service
rahulii Sep 26, 2024
f762e5b
merge bootstrap and layer2 cloud configs
rahulii Oct 8, 2024
a452289
add unit test cases
rahulii Oct 8, 2024
5f33a86
ipam support
vardhaman-surana Oct 8, 2024
aad4223
added rbac
vardhaman-surana Oct 8, 2024
25510bd
minor fix
rahulii Oct 9, 2024
16fdf8d
reconcile port settings for layer2 and bonded port
rahulii Oct 10, 2024
b7e1120
minor fix
rahulii Oct 11, 2024
fcfcaac
refactor code and add static routes
rahulii Oct 11, 2024
b7e871a
bug fix
rahulii Oct 11, 2024
ddf37d9
Merge pull request #1 from rahulii/ipam
rahulii Oct 11, 2024
d901fe6
delete unused code
rahulii Oct 11, 2024
c55ea3f
minor fix
rahulii Oct 14, 2024
4c64c5d
minor fix
rahulii Oct 14, 2024
d4d4b25
change failure to failed in state
rahulii Oct 14, 2024
4d30dcc
added ipaddress claim deletion
vardhaman-surana Oct 14, 2024
4f9c8fc
Merge pull request #2 from vardhaman-surana/ipaddressclaim-deletion
rahulii Oct 14, 2024
4efbfd8
add logs
rahulii Oct 14, 2024
07516df
add routes at network file and change user-data to make them persistent
rahulii Oct 15, 2024
6207973
fix a bug where multiple vxlans are involved
rahulii Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ spec:
If not specified, the first available IP address from the IP address range will be assigned.
This is useful when you want to reserve some IP addresses for other purposes for eg Gateways, DNS etc.
type: string
netmask:
description: |-
Netmask is the netmask for the network.
eg: 255.255.255.248
type: string
vxlan:
description: VLANs for EM API to find by vxlan, project,
and metro match then attach to device. OS userdata template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ spec:
If not specified, the first available IP address from the IP address range will be assigned.
This is useful when you want to reserve some IP addresses for other purposes for eg Gateways, DNS etc.
type: string
netmask:
description: |-
Netmask is the netmask for the network.
eg: 255.255.255.248
type: string
vxlan:
description: VLANs for EM API to find by vxlan,
project, and metro match then attach to device.
Expand Down
2 changes: 1 addition & 1 deletion config/default/manager_image_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ spec:
spec:
containers:
# Change the value of image field below to your controller image URL
- image: quay.io/equinix-oss/cluster-api-provider-packet:dev
- image: docker.io/rahulsawra/cluster-api-provider-packet-amd64:dev

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep this for your local testing only

name: manager
2 changes: 1 addition & 1 deletion config/default/manager_pull_policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ spec:
spec:
containers:
- name: manager
imagePullPolicy: IfNotPresent
imagePullPolicy: Always

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local testing only

13 changes: 6 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
golang.org/x/oauth2 v0.18.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.29.3
k8s.io/apimachinery v0.29.3
k8s.io/client-go v0.29.3
Expand Down Expand Up @@ -47,7 +48,7 @@ require (
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand All @@ -71,13 +72,12 @@ require (
go.opentelemetry.io/otel/sdk v1.20.0 // indirect
go.opentelemetry.io/otel/trace v1.20.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
Expand All @@ -87,7 +87,6 @@ require (
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.29.3 // indirect
k8s.io/apiserver v0.29.3 // indirect
k8s.io/cluster-bootstrap v0.29.3 // indirect
Expand Down
28 changes: 14 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
Expand Down Expand Up @@ -236,8 +236,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
Expand All @@ -254,28 +254,28 @@ golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
4 changes: 3 additions & 1 deletion internal/layer2/ipadressmanagement.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
corev1 "k8s.io/api/core/v1"
)


type IPAddressManagement interface {
// CreateClusterIPAssignmentConfigMap creates a ConfigMap object for tracking IP address assignments per port and VLAN at the cluster level.
// Name of the ConfigMap is <cluster-name>-ip-allocations.
CreateClusterIPAssignmentConfigMap(clusterName string) error
// GetClusterIPAssignmentConfigMap returns the ConfigMap map object for tracking IP address assignments per port and VLAN at the cluster level.
// It returns the ConfigMap object and a boolean indicating if the ConfigMap was found or not.
Expand All @@ -25,7 +27,7 @@ type IPAddressManagement interface {
// GetNextAvailableIPAddress returns the next available IP address for the given cluster, metro, and vxlan from the assignment range.
// It queries the ConfigMap to get the list of assigned IP addresses and returns the next available IP address from the assignment range.
// For example, if the assignment range is 10.60.10.2-10.60.10.8 and the IP addresses 10.60.10.2, 10.60.10.3 are already assigned, it will return
// the next in the sequence.
// the next available IP address.
GetNextAvailableIPAddress(clusterName, metro, assignmentRange, vxlan string) (string, error)

// GetIPAddressForMachine returns the IP address assigned to the machine.
Expand Down
11 changes: 6 additions & 5 deletions internal/layer2/layer2.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@ type PortNetwork struct {
Vxlan int
IPAddress string
Netmask string
Gateway string // Added Gateway field to match template
}

type Config struct {
// Ports is a list of network configurations for the Layer2
Ports []PortNetwork
// VLANs is a list of network configurations for the Layer2
VLANs []PortNetwork // Changed from Ports to VLANs to match template
}

// NewConfig returns a new Config object
func NewConfig() *Config {
return &Config{
Ports: make([]PortNetwork, 0),
VLANs: make([]PortNetwork, 0),
}
}

func (c *Config) AddPortNetwork(portName string, vxlan int, ipAddress string, netmask string) {
c.Ports = append(c.Ports, PortNetwork{
c.VLANs = append(c.VLANs, PortNetwork{
PortName: portName,
Vxlan: vxlan,
IPAddress: ipAddress,
Expand All @@ -46,4 +47,4 @@ func (c *Config) GetTemplate() (string, error) {
return "", err
}
return output.String(), nil
}
}
146 changes: 146 additions & 0 deletions internal/layer2/merge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package layer2

import (
"fmt"
"strings"

"gopkg.in/yaml.v3"
)

const (
cloudConfigHeader = "#cloud-config"
jinjaHeader = "## template: jinja"
)

// CloudConfigMerger handles the merging of cloud configs
type CloudConfigMerger struct {
}

// NewCloudConfigMerger creates a new instance of CloudConfigMerger
func NewCloudConfigMerger() *CloudConfigMerger {
return &CloudConfigMerger{}
}

// configHeaders represents the headers found in a cloud-config file
type configHeaders struct {
hasJinja bool
hasCloudConfig bool
}

// stripHeaders removes the template and cloud-config headers and returns the remaining content
func stripHeaders(data string) (string, configHeaders) {
headers := configHeaders{}
lines := strings.Split(strings.TrimSpace(data), "\n")
startIndex := 0

for i, line := range lines {
trimmedLine := strings.TrimSpace(line)
switch trimmedLine {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this assumes there will not be any other header !

Can we modify this to be more generic - is there a way to identify headers apart from string equals

case jinjaHeader:
headers.hasJinja = true
startIndex = i + 1
case cloudConfigHeader:
headers.hasCloudConfig = true
startIndex = i + 1
default:
if trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") {
return strings.Join(lines[startIndex:], "\n"), headers
}
}
}
return "", headers
}

// deepMerge recursively merges two maps
func (m *CloudConfigMerger) deepMerge(base, overlay map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})

// Copy base map
for k, v := range base {
result[k] = v
}

// Merge overlay
for k, v := range overlay {
if baseVal, exists := result[k]; exists {
// If both values are maps, merge them recursively
if baseMap, ok := baseVal.(map[string]interface{}); ok {
if overlayMap, ok := v.(map[string]interface{}); ok {
result[k] = m.deepMerge(baseMap, overlayMap)
continue
}
}

// If both values are slices, append them
if baseSlice, ok := baseVal.([]interface{}); ok {
if overlaySlice, ok := v.([]interface{}); ok {
result[k] = append(baseSlice, overlaySlice...)
continue
}
}
}

// For all other cases, overlay value takes precedence
result[k] = v
}

return result
}

// buildHeader constructs the appropriate header based on the input configurations
func buildHeader(bootstrapHeaders, layer2Headers configHeaders) string {
var headers []string

// If either input has the Jinja header, include it in the output
if bootstrapHeaders.hasJinja || layer2Headers.hasJinja {
headers = append(headers, jinjaHeader)
}

// Always include the cloud-config header
headers = append(headers, cloudConfigHeader)

return strings.Join(headers, "\n")
}

// MergeConfigs combines bootstrap data with layer2 config
func (m *CloudConfigMerger) MergeConfigs(bootstrapData string, layer2UserData string) (string, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this generic as well ?

CloudConfigMerger.WithBootstrapData(bdata).
WithLayer2Data(l2data).
WithAddlUserDatas([]userdatas). // this can be optional and can be extensible
MergeConfigs()

// Strip headers and get header info
bootstrapStripped, bootstrapHeaders := stripHeaders(bootstrapData)
layer2Stripped, layer2Headers := stripHeaders(layer2UserData)

// Validate that at least one input has the cloud-config header
if !bootstrapHeaders.hasCloudConfig && !layer2Headers.hasCloudConfig {
return "", fmt.Errorf("neither input contains #cloud-config header")
}

var bootstrapConfig, layer2UserDataConfig map[string]interface{}

if bootstrapStripped != "" {
if err := yaml.Unmarshal([]byte(bootstrapStripped), &bootstrapConfig); err != nil {
return "", fmt.Errorf("error parsing bootstrap YAML: %v", err)
}
} else {
bootstrapConfig = make(map[string]interface{})
}

if layer2Stripped != "" {
if err := yaml.Unmarshal([]byte(layer2Stripped), &layer2UserDataConfig); err != nil {
return "", fmt.Errorf("error parsing layer2 YAML: %v", err)
}
} else {
layer2UserDataConfig = make(map[string]interface{})
}
Comment on lines +124 to +138

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bootstrapConfig := make(map[string]interface{})

if err := yaml.Unmarshal([]byte(layer2Stripped), &layer2UserDataConfig); err != nil {
	return "", fmt.Errorf("error parsing layer2 YAML: %v", err)
}

layer2UserDataConfig := make(map[string]interface{})

if err := yaml.Unmarshal([]byte(layer2Stripped), &layer2UserDataConfig); err != nil {
	return "", fmt.Errorf("error parsing layer2 YAML: %v", err)
}


// Merge configurations
mergedConfig := m.deepMerge(layer2UserDataConfig, bootstrapConfig)

// Convert merged config back to YAML
result, err := yaml.Marshal(mergedConfig)
if err != nil {
return "", fmt.Errorf("error marshaling merged config: %v", err)
}

// Build appropriate headers and combine with content
header := buildHeader(bootstrapHeaders, layer2Headers)
return fmt.Sprintf("%s\n%s", header, string(result)), nil
}
53 changes: 0 additions & 53 deletions internal/layer2/mime.go

This file was deleted.

Loading