Skip to content

Commit 7a396d9

Browse files
committed
pkg/asset/machines: Convert Master to a WriteableAsset
Since 3326b6b (pkg/tfvars: Pull from []Machine instead of InstallConfig, 2018-11-28, openshift#792), we've been consuming structured master configurations to generate Terraform variables. But before this commit, the Openshift asset was the WriteableAsset responsible for the master configs, giving you the following issue: 1. openshift-install create manifests i. Master asset populated with structured data ii. Openshift asset pulls in Master, flattens to YAML, and writes to disk. 2. openshift-install create cluster i. Openshift asset reads master YAML from disk. ii. TerraformVariables asset pulls in Master, but it's empty. iii. Panic [1]. With this commit, responsibility for reading and writing master manifests to disk gets shifted to the Master asset itself. I've dropped the structured Master properties (Machines and MachineDeprecated) in favor of new methods on the asset. There are three possible paths towards recovering the asset that we feed in to TerraformVariables: a. After rendering it with Generate. This information had been structured before this commit, so no problems here. b. After reading from the disk. This information could be unmarshalled in Master.Load(), but... c. After loading it from the state file. There are no hooks for custom unmarshalling in this pathway, so while the generic YAML unmarshal would give us a structured machine object, it would not unmarshal the RawExtension that holds the provider spec. The lack of custom-unmarshal hooks for (c) means there's no reliable way to use Master properties to feed TerraformVariables structured provider information. With this commit, we use Master methods instead. That's just as efficient for the (b) and (c) cases, and while it's an uneccessary (de)serialize cycle for (a), that's not too expensive. Unpacking the YAML data into the appropriate structures is a bit hairy. I'm not familiar with this code though, maybe there's a better way. It will help once we complete the shift to the OpenShift machine-API types started in cf60daa (Pivot aws from cluster.k8s.io into machine.openshift.io, 2019-02-01, openshift#1175). I've also taken the opportunity to drop the list. It saves us a few lines of code for (de)serializing, and there's no upside to collecting the Machine configs together in a single file. The "glob, but not the master files" logic in the Openshift loader is also a bit ugly. Moving forward, I expect us to push the remaining Openshift assets out into their own assets as well, which would allow us to tighten down on the wildcard there. There's also a bit of dancing to ensure our Machine filenames are ordered by increasing index. I'm padding the filenames with %02d (for example) to get ...-01.yaml, etc. so they come back in the right order on load (filepath.Glob sorts its output [2]). To avoid hard-coding a pad size, I format the largest index, measure its length, and use that length to construct padFormat. [1]: openshift#1205 [2]: https://github.com/golang/go/blob/go1.11.5/src/path/filepath/match.go#L325
1 parent 3804a86 commit 7a396d9

File tree

8 files changed

+225
-82
lines changed

8 files changed

+225
-82
lines changed

pkg/asset/cluster/tfvars.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"github.com/openshift/installer/pkg/types/openstack"
2222
"github.com/pkg/errors"
2323
"github.com/sirupsen/logrus"
24-
awsprovider "sigs.k8s.io/cluster-api-provider-aws/pkg/apis/awsproviderconfig/v1alpha1"
24+
awsprovider "sigs.k8s.io/cluster-api-provider-aws/pkg/apis/awsproviderconfig/v1beta1"
2525
openstackprovider "sigs.k8s.io/cluster-api-provider-openstack/pkg/apis/openstackproviderconfig/v1alpha1"
2626
)
2727

@@ -76,10 +76,8 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
7676
bootstrapIgn := string(bootstrapIgnAsset.Files()[0].Data)
7777
masterIgn := string(masterIgnAsset.Files()[0].Data)
7878

79-
masterCount := len(mastersAsset.Machines)
80-
if mastersAsset.Machines == nil {
81-
masterCount = len(mastersAsset.MachinesDeprecated)
82-
}
79+
masters := mastersAsset.Machines()
80+
masterCount := len(masters)
8381
data, err := tfvars.TFVars(
8482
clusterID.ClusterID,
8583
installConfig.Config.ObjectMeta.Name,
@@ -105,8 +103,12 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
105103

106104
switch platform := installConfig.Config.Platform.Name(); platform {
107105
case aws.Name:
106+
masters, err := mastersAsset.StructuredMachines()
107+
if err != nil {
108+
return err
109+
}
108110
data, err = awstfvars.TFVars(
109-
mastersAsset.Machines[0].Spec.ProviderSpec.Value.Object.(*awsprovider.AWSMachineProviderConfig),
111+
masters[0].Spec.ProviderSpec.Value.Object.(*awsprovider.AWSMachineProviderConfig),
110112
)
111113
if err != nil {
112114
return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
@@ -116,8 +118,12 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
116118
Data: data,
117119
})
118120
case libvirt.Name:
121+
masters, err := mastersAsset.StructuredMachines()
122+
if err != nil {
123+
return err
124+
}
119125
data, err = libvirttfvars.TFVars(
120-
mastersAsset.Machines[0].Spec.ProviderSpec.Value.Object.(*libvirtprovider.LibvirtMachineProviderConfig),
126+
masters[0].Spec.ProviderSpec.Value.Object.(*libvirtprovider.LibvirtMachineProviderConfig),
121127
string(*rhcosImage),
122128
&installConfig.Config.Networking.MachineCIDR.IPNet,
123129
installConfig.Config.Platform.Libvirt.Network.IfName,
@@ -132,8 +138,12 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
132138
})
133139
case none.Name:
134140
case openstack.Name:
141+
masters, err := mastersAsset.StructuredMachinesDeprecated()
142+
if err != nil {
143+
return err
144+
}
135145
data, err = openstacktfvars.TFVars(
136-
mastersAsset.MachinesDeprecated[0].Spec.ProviderSpec.Value.Object.(*openstackprovider.OpenstackProviderSpec),
146+
masters[0].Spec.ProviderSpec.Value.Object.(*openstackprovider.OpenstackProviderSpec),
137147
installConfig.Config.Platform.OpenStack.Region,
138148
installConfig.Config.Platform.OpenStack.ExternalNetwork,
139149
installConfig.Config.Platform.OpenStack.TrunkSupport,

pkg/asset/ignition/bootstrap/bootstrap.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/openshift/installer/pkg/asset/ignition"
2323
"github.com/openshift/installer/pkg/asset/installconfig"
2424
"github.com/openshift/installer/pkg/asset/kubeconfig"
25+
"github.com/openshift/installer/pkg/asset/machines"
2526
"github.com/openshift/installer/pkg/asset/manifests"
2627
"github.com/openshift/installer/pkg/asset/tls"
2728
"github.com/openshift/installer/pkg/types"
@@ -75,6 +76,7 @@ func (a *Bootstrap) Dependencies() []asset.Asset {
7576
&tls.JournalCertKey{},
7677
&kubeconfig.Admin{},
7778
&kubeconfig.Kubelet{},
79+
&machines.Master{},
7880
&manifests.Manifests{},
7981
&manifests.Openshift{},
8082
}
@@ -347,6 +349,7 @@ func (a *Bootstrap) addParentFiles(dependencies asset.Parents) {
347349
for _, asset := range []asset.WritableAsset{
348350
&kubeconfig.Admin{},
349351
&kubeconfig.Kubelet{},
352+
&machines.Master{},
350353
&tls.KubeCA{},
351354
&tls.AggregatorCA{},
352355
&tls.EtcdCA{},

pkg/asset/machines/aws/machines.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"k8s.io/apimachinery/pkg/runtime"
1212
"k8s.io/apimachinery/pkg/util/sets"
1313
"k8s.io/utils/pointer"
14-
awsprovider "sigs.k8s.io/cluster-api-provider-aws/pkg/apis/awsproviderconfig/v1alpha1"
14+
awsprovider "sigs.k8s.io/cluster-api-provider-aws/pkg/apis/awsproviderconfig/v1beta1"
1515

1616
"github.com/openshift/installer/pkg/types"
1717
"github.com/openshift/installer/pkg/types/aws"

pkg/asset/machines/master.go

Lines changed: 174 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package machines
22

33
import (
44
"fmt"
5+
"os"
6+
"path/filepath"
57

8+
"github.com/ghodss/yaml"
9+
libvirtapi "github.com/openshift/cluster-api-provider-libvirt/pkg/apis"
10+
libvirtprovider "github.com/openshift/cluster-api-provider-libvirt/pkg/apis/libvirtproviderconfig/v1alpha1"
611
machineapi "github.com/openshift/cluster-api/pkg/apis/machine/v1beta1"
7-
"github.com/pkg/errors"
8-
clusterapi "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
9-
1012
"github.com/openshift/installer/pkg/asset"
1113
"github.com/openshift/installer/pkg/asset/ignition/machine"
1214
"github.com/openshift/installer/pkg/asset/installconfig"
@@ -19,16 +21,32 @@ import (
1921
libvirttypes "github.com/openshift/installer/pkg/types/libvirt"
2022
nonetypes "github.com/openshift/installer/pkg/types/none"
2123
openstacktypes "github.com/openshift/installer/pkg/types/openstack"
24+
"github.com/pkg/errors"
25+
"k8s.io/apimachinery/pkg/runtime"
26+
"k8s.io/apimachinery/pkg/runtime/serializer"
27+
awsapi "sigs.k8s.io/cluster-api-provider-aws/pkg/apis"
28+
awsprovider "sigs.k8s.io/cluster-api-provider-aws/pkg/apis/awsproviderconfig/v1beta1"
29+
openstackapi "sigs.k8s.io/cluster-api-provider-openstack/pkg/apis"
30+
openstackprovider "sigs.k8s.io/cluster-api-provider-openstack/pkg/apis/openstackproviderconfig/v1alpha1"
31+
clusterapi "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
2232
)
2333

2434
// Master generates the machines for the `master` machine pool.
2535
type Master struct {
26-
Machines []machineapi.Machine
27-
MachinesDeprecated []clusterapi.Machine
28-
UserDataSecretRaw []byte
36+
FileList []*asset.File
2937
}
3038

31-
var _ asset.Asset = (*Master)(nil)
39+
var (
40+
directory = "openshift"
41+
42+
// MasterMachineFileName is the format string for constucting the master Machine filenames.
43+
MasterMachineFileName = "99_openshift-cluster-api_master-machines-%s.yaml"
44+
45+
// MasterUserDataFileName is the filename used for the master user-data secret.
46+
MasterUserDataFileName = "99_openshift-cluster-api_master-user-data-secret.yaml"
47+
48+
_ asset.WritableAsset = (*Master)(nil)
49+
)
3250

3351
// Name returns a human friendly name for the Master Asset.
3452
func (m *Master) Name() string {
@@ -59,12 +77,8 @@ func (m *Master) Generate(dependencies asset.Parents) error {
5977
dependencies.Get(clusterID, installconfig, rhcosImage, mign)
6078

6179
var err error
62-
userDataMap := map[string][]byte{"master-user-data": mign.File.Data}
63-
m.UserDataSecretRaw, err = userDataList(userDataMap)
64-
if err != nil {
65-
return errors.Wrap(err, "failed to create user-data secret for master machines")
66-
}
67-
80+
machines := []machineapi.Machine{}
81+
machinesDeprecated := []clusterapi.Machine{}
6882
ic := installconfig.Config
6983
pool := masterPool(ic.Machines)
7084
switch ic.Platform.Name() {
@@ -81,42 +95,180 @@ func (m *Master) Generate(dependencies asset.Parents) error {
8195
mpool.Zones = azs
8296
}
8397
pool.Platform.AWS = &mpool
84-
m.Machines, err = aws.Machines(clusterID.ClusterID, ic, &pool, string(*rhcosImage), "master", "master-user-data")
98+
machines, err = aws.Machines(clusterID.ClusterID, ic, &pool, string(*rhcosImage), "master", "master-user-data")
8599
if err != nil {
86100
return errors.Wrap(err, "failed to create master machine objects")
87101
}
88-
aws.ConfigMasters(m.Machines, ic.ObjectMeta.Name)
102+
aws.ConfigMasters(machines, ic.ObjectMeta.Name)
89103
case libvirttypes.Name:
90104
mpool := defaultLibvirtMachinePoolPlatform()
91105
mpool.Set(ic.Platform.Libvirt.DefaultMachinePlatform)
92106
mpool.Set(pool.Platform.Libvirt)
93107
pool.Platform.Libvirt = &mpool
94-
m.Machines, err = libvirt.Machines(clusterID.ClusterID, ic, &pool, "master", "master-user-data")
108+
machines, err = libvirt.Machines(clusterID.ClusterID, ic, &pool, "master", "master-user-data")
95109
if err != nil {
96110
return errors.Wrap(err, "failed to create master machine objects")
97111
}
98112
case nonetypes.Name:
99-
// This is needed to ensure that roundtrip generate-load tests pass when
100-
// comparing this value. Otherwise, generate will use a nil value while
101-
// load will use an empty slice.
102-
m.Machines = []machineapi.Machine{}
113+
return nil
103114
case openstacktypes.Name:
104115
mpool := defaultOpenStackMachinePoolPlatform(ic.Platform.OpenStack.FlavorName)
105116
mpool.Set(ic.Platform.OpenStack.DefaultMachinePlatform)
106117
mpool.Set(pool.Platform.OpenStack)
107118
pool.Platform.OpenStack = &mpool
108119

109-
m.MachinesDeprecated, err = openstack.Machines(clusterID.ClusterID, ic, &pool, string(*rhcosImage), "master", "master-user-data")
120+
machinesDeprecated, err = openstack.Machines(clusterID.ClusterID, ic, &pool, string(*rhcosImage), "master", "master-user-data")
110121
if err != nil {
111122
return errors.Wrap(err, "failed to create master machine objects")
112123
}
113-
openstack.ConfigMasters(m.MachinesDeprecated, ic.ObjectMeta.Name)
124+
openstack.ConfigMasters(machinesDeprecated, ic.ObjectMeta.Name)
114125
default:
115126
return fmt.Errorf("invalid Platform")
116127
}
128+
129+
userDataMap := map[string][]byte{"master-user-data": mign.File.Data}
130+
data, err := userDataList(userDataMap)
131+
if err != nil {
132+
return errors.Wrap(err, "failed to create user-data secret for master machines")
133+
}
134+
135+
m.FileList = []*asset.File{{
136+
Filename: filepath.Join(directory, MasterUserDataFileName),
137+
Data: data,
138+
}}
139+
140+
machinesAll := []runtime.Object{}
141+
for _, machine := range machines {
142+
machinesAll = append(machinesAll, &machine)
143+
}
144+
for _, machine := range machinesDeprecated {
145+
machinesAll = append(machinesAll, &machine)
146+
}
147+
148+
count := len(machinesAll)
149+
if count == 0 {
150+
return errors.New("at least one master machine must be configured")
151+
}
152+
153+
padFormat := fmt.Sprintf("%%0%dd", len(fmt.Sprintf("%d", count)))
154+
for i, machine := range machinesAll {
155+
data, err := yaml.Marshal(machine)
156+
if err != nil {
157+
return errors.Wrapf(err, "marshal master %d", i)
158+
}
159+
160+
padded := fmt.Sprintf(padFormat, i)
161+
m.FileList = append(m.FileList, &asset.File{
162+
Filename: filepath.Join(directory, fmt.Sprintf(MasterMachineFileName, padded)),
163+
Data: data,
164+
})
165+
}
166+
117167
return nil
118168
}
119169

170+
// Files returns the files generated by the asset.
171+
func (m *Master) Files() []*asset.File {
172+
return m.FileList
173+
}
174+
175+
// Load reads the asset files from disk.
176+
func (m *Master) Load(f asset.FileFetcher) (found bool, err error) {
177+
file, err := f.FetchByName(filepath.Join(directory, MasterUserDataFileName))
178+
if err != nil {
179+
if os.IsNotExist(err) {
180+
return false, nil
181+
}
182+
return false, err
183+
}
184+
m.FileList = []*asset.File{file}
185+
186+
fileList, err := f.FetchByPattern(filepath.Join(directory, fmt.Sprintf(MasterMachineFileName, "*")))
187+
if err != nil {
188+
return true, err
189+
}
190+
191+
if len(fileList) == 0 {
192+
return true, errors.Errorf("master machine manifests are required if you also provide %s", file.Filename)
193+
}
194+
195+
m.FileList = append(m.FileList, fileList...)
196+
return true, nil
197+
}
198+
199+
// Machines returns master Machine manifest YAML.
200+
func (m *Master) Machines() [][]byte {
201+
machines := [][]byte{}
202+
userData := filepath.Join(directory, MasterUserDataFileName)
203+
for _, file := range m.FileList {
204+
if file.Filename == userData {
205+
continue
206+
}
207+
machines = append(machines, file.Data)
208+
}
209+
return machines
210+
}
211+
212+
// StructuredMachines returns master Machine manifest structures.
213+
func (m *Master) StructuredMachines() ([]machineapi.Machine, error) {
214+
scheme := runtime.NewScheme()
215+
awsapi.AddToScheme(scheme)
216+
libvirtapi.AddToScheme(scheme)
217+
openstackapi.AddToScheme(scheme)
218+
decoder := serializer.NewCodecFactory(scheme).UniversalDecoder(
219+
awsprovider.SchemeGroupVersion,
220+
libvirtprovider.SchemeGroupVersion,
221+
openstackprovider.SchemeGroupVersion,
222+
)
223+
224+
machines := []machineapi.Machine{}
225+
for i, data := range m.Machines() {
226+
machine := &machineapi.Machine{}
227+
err := yaml.Unmarshal(data, &machine)
228+
if err != nil {
229+
return machines, errors.Wrapf(err, "unmarshal master %d", i)
230+
}
231+
232+
obj, _, err := decoder.Decode(machine.Spec.ProviderSpec.Value.Raw, nil, nil)
233+
if err != nil {
234+
return machines, errors.Wrapf(err, "unmarshal master %d", i)
235+
}
236+
237+
machine.Spec.ProviderSpec.Value = &runtime.RawExtension{Object: obj}
238+
machines = append(machines, *machine)
239+
}
240+
241+
return machines, nil
242+
}
243+
244+
// StructuredMachinesDeprecated returns master Machine manifest structures.
245+
func (m *Master) StructuredMachinesDeprecated() ([]clusterapi.Machine, error) {
246+
scheme := runtime.NewScheme()
247+
openstackapi.AddToScheme(scheme)
248+
decoder := serializer.NewCodecFactory(scheme).UniversalDecoder(
249+
openstackprovider.SchemeGroupVersion,
250+
)
251+
252+
machines := []clusterapi.Machine{}
253+
for i, data := range m.Machines() {
254+
machine := &clusterapi.Machine{}
255+
err := yaml.Unmarshal(data, &machine)
256+
if err != nil {
257+
return machines, errors.Wrapf(err, "unmarshal master %d", i)
258+
}
259+
260+
obj, _, err := decoder.Decode(machine.Spec.ProviderSpec.Value.Raw, nil, nil)
261+
if err != nil {
262+
return machines, errors.Wrapf(err, "unmarshal master %d", i)
263+
}
264+
265+
machine.Spec.ProviderSpec.Value = &runtime.RawExtension{Object: obj}
266+
machines = append(machines, *machine)
267+
}
268+
269+
return machines, nil
270+
}
271+
120272
func masterPool(pools []types.MachinePool) types.MachinePool {
121273
for idx, pool := range pools {
122274
if pool.Name == "master" {

0 commit comments

Comments
 (0)