diff --git a/cmd/openshift-install/main.go b/cmd/openshift-install/main.go index 452cf420835..1562737b89d 100644 --- a/cmd/openshift-install/main.go +++ b/cmd/openshift-install/main.go @@ -9,7 +9,13 @@ import ( "gopkg.in/alecthomas/kingpin.v2" "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/stock" + "github.com/openshift/installer/pkg/asset/cluster" + "github.com/openshift/installer/pkg/asset/ignition/bootstrap" + "github.com/openshift/installer/pkg/asset/ignition/machine" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/kubeconfig" + "github.com/openshift/installer/pkg/asset/manifests" + "github.com/openshift/installer/pkg/asset/metadata" "github.com/openshift/installer/pkg/destroy" _ "github.com/openshift/installer/pkg/destroy/libvirt" "github.com/openshift/installer/pkg/terraform" @@ -58,29 +64,27 @@ func main() { return } - assetStock := stock.EstablishStock() - - var targetAssets []asset.Asset + var targetAssets []asset.WritableAsset switch command { case installConfigCommand.FullCommand(): - targetAssets = []asset.Asset{assetStock.InstallConfig()} + targetAssets = []asset.WritableAsset{&installconfig.InstallConfig{}} case ignitionConfigsCommand.FullCommand(): - targetAssets = []asset.Asset{ - assetStock.BootstrapIgnition(), - assetStock.MasterIgnition(), - assetStock.WorkerIgnition(), + targetAssets = []asset.WritableAsset{ + &bootstrap.Bootstrap{}, + &machine.Master{}, + &machine.Worker{}, } case manifestsCommand.FullCommand(): - targetAssets = []asset.Asset{ - assetStock.Manifests(), - assetStock.Tectonic(), + targetAssets = []asset.WritableAsset{ + &manifests.Manifests{}, + &manifests.Tectonic{}, } case clusterCommand.FullCommand(): - targetAssets = []asset.Asset{ - assetStock.TFVars(), - assetStock.KubeconfigAdmin(), - assetStock.Cluster(), - assetStock.Metadata(), + targetAssets = []asset.WritableAsset{ + &cluster.TerraformVariables{}, + &kubeconfig.Admin{}, + &cluster.Cluster{}, + &metadata.Metadata{}, } } @@ -90,14 +94,14 @@ func main() { manifestsCommand.FullCommand(), clusterCommand.FullCommand(): assetStore := &asset.StoreImpl{} - for _, asset := range targetAssets { - st, err := assetStore.Fetch(asset) + for _, a := range targetAssets { + err := assetStore.Fetch(a) if err != nil { - logrus.Fatalf("Failed to generate %s: %v", asset.Name(), err) + logrus.Fatalf("Failed to generate %s: %v", a.Name(), err) } - if err := st.PersistToFile(*dirFlag); err != nil { - logrus.Fatalf("Failed to write asset (%s) to disk: %v", asset.Name(), err) + if err := asset.PersistToFile(a, *dirFlag); err != nil { + logrus.Fatalf("Failed to write asset (%s) to disk: %v", a.Name(), err) } } case destroyCommand.FullCommand(): diff --git a/docs/design/assetgeneration.md b/docs/design/assetgeneration.md index cf4b8a7dcb1..8bb3b5a08c1 100644 --- a/docs/design/assetgeneration.md +++ b/docs/design/assetgeneration.md @@ -10,30 +10,6 @@ The installer is also able to read assets from disk if they have been provided b Each asset is individually responsible for declaring its dependencies. Each asset is also responsible resolving conflicts when combining its input from disk and its state from a previous run. The installer ensures all the dependencies for an asset is generated and provides the asset with latest state to generate its own output. -## State - -State is an internal state of the installer. It stores the contents of all the assets that have been generated. - -An example of the State: - -```go -// State is map of assets -type State struct { - Root string // hash for state object. - - Objects map[string]AssetState -} - -// AssetState is the state of an Asset. -type AssetState struct { - Parents maps[string]string // hashes for our each parent. - Contents []Content{ - Name string // the path on disk for this content. - Data []byte - } -} -``` - ## Asset An asset is the generic representation of work-item for installer that needs to be generated. Each asset defines all the other assets that are required for it to generate itself as dependencies. @@ -63,6 +39,22 @@ type Asset interface{ } ``` +## Writable Asset + +A writable asset is an asset that generates files to write to disk. These files could be for the user to consume as output from installer targets, such as install-config.yml from the InstallConfig asset. Or these files could be used internally by the installer, such as the cert/key files generated by TLS assets. + +```go +type WritableAsset interface{ + Asset + Files() []File +} + +type File struct { + Filename string + Data []byte +} +``` + ## Target generation The installer uses depth-first traversal on the dependency graph, starting at the target nodes, generating all the dependencies of the asset before generating the asset itself. After all the target assets have been generated, the installer outputs the contents of the components of the targets to disk. diff --git a/pkg/asset/asset.go b/pkg/asset/asset.go index 9cb78ac9909..e416ef40494 100644 --- a/pkg/asset/asset.go +++ b/pkg/asset/asset.go @@ -1,13 +1,52 @@ package asset +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + // Asset used to install OpenShift. type Asset interface { // Dependencies returns the assets upon which this asset directly depends. Dependencies() []Asset - // Generate generates this asset given the states of its dependent assets. - Generate(map[Asset]*State) (*State, error) + // Generate generates this asset given the states of its parent assets. + Generate(Parents) error // Name returns the human-friendly name of the asset. Name() string } + +// WritableAsset is an Asset that has files that can be written to disk. +type WritableAsset interface { + Asset + + // Files returns the files to write. + Files() []*File +} + +// File is a file for an Asset. +type File struct { + // Filename is the name of the file. + Filename string + // Data is the contents of the file. + Data []byte +} + +// PersistToFile writes all of the files of the specified asset into the specified +// directory. +func PersistToFile(asset WritableAsset, directory string) error { + for _, f := range asset.Files() { + path := filepath.Join(directory, f.Filename) + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return errors.Wrap(err, "failed to create dir") + } + if err := ioutil.WriteFile(path, f.Data, 0644); err != nil { + return errors.Wrap(err, "failed to write file") + } + } + return nil +} diff --git a/pkg/asset/state_test.go b/pkg/asset/asset_test.go similarity index 73% rename from pkg/asset/state_test.go rename to pkg/asset/asset_test.go index b77ba63c44b..4acb0c8e758 100644 --- a/pkg/asset/state_test.go +++ b/pkg/asset/asset_test.go @@ -10,14 +10,37 @@ import ( "github.com/stretchr/testify/assert" ) -func TestStatePersistToFile(t *testing.T) { +type persistAsset struct{} + +func (a *persistAsset) Name() string { + return "persist-asset" +} + +func (a *persistAsset) Dependencies() []Asset { + return []Asset{} +} + +func (a *persistAsset) Generate(Parents) error { + return nil +} + +type writablePersistAsset struct { + persistAsset + files []*File +} + +func (a *writablePersistAsset) Files() []*File { + return a.files +} + +func TestPersistToFile(t *testing.T) { cases := []struct { name string filenames []string }{ { name: "no files", - filenames: []string{""}, + filenames: []string{}, }, { name: "single file", @@ -40,19 +63,19 @@ func TestStatePersistToFile(t *testing.T) { } defer os.RemoveAll(dir) - state := &State{ - Contents: make([]Content, len(tc.filenames)), + asset := &writablePersistAsset{ + files: make([]*File, len(tc.filenames)), } expectedFiles := map[string][]byte{} for i, filename := range tc.filenames { data := []byte(fmt.Sprintf("data%d", i)) - state.Contents[i].Name = filename - state.Contents[i].Data = data - if filename != "" { - expectedFiles[filepath.Join(dir, filename)] = data + asset.files[i] = &File{ + Filename: filename, + Data: data, } + expectedFiles[filepath.Join(dir, filename)] = data } - err = state.PersistToFile(dir) + err = PersistToFile(asset, dir) assert.NoError(t, err, "unexpected error persisting state to file") verifyFilesCreated(t, dir, expectedFiles) }) diff --git a/pkg/asset/cluster/cluster.go b/pkg/asset/cluster/cluster.go index c379a50a11e..ba4a2a8b062 100644 --- a/pkg/asset/cluster/cluster.go +++ b/pkg/asset/cluster/cluster.go @@ -10,6 +10,7 @@ import ( "github.com/openshift/installer/data" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/kubeconfig" "github.com/openshift/installer/pkg/terraform" ) @@ -20,13 +21,10 @@ const ( // Cluster uses the terraform executable to launch a cluster // with the given terraform tfvar and generated templates. type Cluster struct { - // The root directory of the generated assets. - rootDir string - tfvars asset.Asset - kubeconfig asset.Asset + file *asset.File } -var _ asset.Asset = (*Cluster)(nil) +var _ asset.WritableAsset = (*Cluster)(nil) // Name returns the human-friendly name of the asset. func (c *Cluster) Name() string { @@ -36,34 +34,37 @@ func (c *Cluster) Name() string { // Dependencies returns the direct dependency for launching // the cluster. func (c *Cluster) Dependencies() []asset.Asset { - return []asset.Asset{c.tfvars, c.kubeconfig} + return []asset.Asset{ + &TerraformVariables{}, + &kubeconfig.Admin{}, + } } // Generate launches the cluster and generates the terraform state file on disk. -func (c *Cluster) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { - state, ok := parents[c.tfvars] - if !ok { - return nil, errors.Errorf("failed to get terraform.tfvars from parent") - } +func (c *Cluster) Generate(parents asset.Parents) error { + terraformVariables := &TerraformVariables{} + adminKubeconfig := &kubeconfig.Admin{} + parents.Get(terraformVariables, adminKubeconfig) // Copy the terraform.tfvars to a temp directory where the terraform will be invoked within. tmpDir, err := ioutil.TempDir(os.TempDir(), "openshift-install-") if err != nil { - return nil, errors.Wrap(err, "failed to create temp dir for terraform execution") + return errors.Wrap(err, "failed to create temp dir for terraform execution") } defer os.RemoveAll(tmpDir) - if err := ioutil.WriteFile(filepath.Join(tmpDir, state.Contents[0].Name), state.Contents[0].Data, 0600); err != nil { - return nil, errors.Wrap(err, "failed to write terraform.tfvars file") + terraformVariablesFile := terraformVariables.Files()[0] + if err := ioutil.WriteFile(filepath.Join(tmpDir, terraformVariablesFile.Filename), terraformVariablesFile.Data, 0600); err != nil { + return errors.Wrap(err, "failed to write terraform.tfvars file") } - platform := string(state.Contents[1].Data) + platform := terraformVariables.platform if err := data.Unpack(tmpDir, platform); err != nil { - return nil, err + return err } if err := data.Unpack(filepath.Join(tmpDir, "config.tf"), "config.tf"); err != nil { - return nil, err + return err } logrus.Infof("Using Terraform to create cluster...") @@ -71,7 +72,7 @@ func (c *Cluster) Generate(parents map[asset.Asset]*asset.State) (*asset.State, // This runs the terraform in a temp directory, the tfstate file will be returned // to the asset store to persist it on the disk. if err := terraform.Init(tmpDir); err != nil { - return nil, errors.Wrap(err, "failed to initialize terraform") + return errors.Wrap(err, "failed to initialize terraform") } stateFile, err := terraform.Apply(tmpDir) @@ -88,12 +89,16 @@ func (c *Cluster) Generate(parents map[asset.Asset]*asset.State) (*asset.State, } } - return &asset.State{ - Contents: []asset.Content{ - { - Name: stateFileName, - Data: data, - }, - }, - }, err + c.file = &asset.File{ + Filename: stateFileName, + Data: data, + } + + // TODO(yifan): Use the kubeconfig to verify the cluster is up. + return nil +} + +// Files returns the files generated by the asset. +func (c *Cluster) Files() []*asset.File { + return []*asset.File{c.file} } diff --git a/pkg/asset/cluster/stock.go b/pkg/asset/cluster/stock.go deleted file mode 100644 index 9b86495f617..00000000000 --- a/pkg/asset/cluster/stock.go +++ /dev/null @@ -1,46 +0,0 @@ -package cluster - -import ( - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/ignition/bootstrap" - "github.com/openshift/installer/pkg/asset/ignition/machine" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/asset/kubeconfig" -) - -// Stock is the stock of the cluster assets that can be generated. -type Stock interface { - // TFVars is the asset that generates the terraform.tfvar file - TFVars() asset.Asset - // Cluster is the asset that creates the cluster. - Cluster() asset.Asset -} - -// StockImpl is the implementation of the cluster asset stock. -type StockImpl struct { - tfvars asset.Asset - cluster asset.Asset -} - -var _ Stock = (*StockImpl)(nil) - -// EstablishStock establishes the stock of assets in the specified directory. -func (s *StockImpl) EstablishStock(installConfigStock installconfig.Stock, bootstrapStock bootstrap.Stock, machineStock machine.Stock, kubeconfigStock kubeconfig.Stock) { - s.tfvars = &TerraformVariables{ - installConfig: installConfigStock.InstallConfig(), - bootstrapIgnition: bootstrapStock.BootstrapIgnition(), - masterIgnition: machineStock.MasterIgnition(), - workerIgnition: machineStock.WorkerIgnition(), - } - - s.cluster = &Cluster{ - tfvars: s.tfvars, - kubeconfig: kubeconfigStock.KubeconfigAdmin(), - } -} - -// TFVars returns the terraform tfvar asset. -func (s *StockImpl) TFVars() asset.Asset { return s.tfvars } - -// Cluster returns the cluster asset. -func (s *StockImpl) Cluster() asset.Asset { return s.cluster } diff --git a/pkg/asset/cluster/tfvars.go b/pkg/asset/cluster/tfvars.go index 2726eeaafc6..e815cfd2379 100644 --- a/pkg/asset/cluster/tfvars.go +++ b/pkg/asset/cluster/tfvars.go @@ -2,6 +2,8 @@ package cluster import ( "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/ignition/bootstrap" + "github.com/openshift/installer/pkg/asset/ignition/machine" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/tfvars" "github.com/pkg/errors" @@ -15,14 +17,11 @@ const ( // TerraformVariables depends on InstallConfig and // Ignition to generate the terrafor.tfvars. type TerraformVariables struct { - // The Assets that this tfvars file depends. - installConfig asset.Asset - bootstrapIgnition asset.Asset - masterIgnition asset.Asset - workerIgnition asset.Asset + platform string + file *asset.File } -var _ asset.Asset = (*TerraformVariables)(nil) +var _ asset.WritableAsset = (*TerraformVariables)(nil) // Name returns the human-friendly name of the asset. func (t *TerraformVariables) Name() string { @@ -31,48 +30,47 @@ func (t *TerraformVariables) Name() string { // Dependencies returns the dependency of the TerraformVariable func (t *TerraformVariables) Dependencies() []asset.Asset { - return []asset.Asset{t.installConfig, t.bootstrapIgnition, t.masterIgnition, t.workerIgnition} + return []asset.Asset{ + &installconfig.InstallConfig{}, + &bootstrap.Bootstrap{}, + &machine.Master{}, + &machine.Worker{}, + } } // Generate generates the terraform.tfvars file. -func (t *TerraformVariables) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { - installCfg, err := installconfig.GetInstallConfig(t.installConfig, parents) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } +func (t *TerraformVariables) Generate(parents asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + bootstrap := &bootstrap.Bootstrap{} + master := &machine.Master{} + worker := &machine.Worker{} + parents.Get(installConfig, bootstrap, master, worker) - contents := map[asset.Asset][]string{} + t.platform = installConfig.Config.Platform.Name() - for _, ign := range []asset.Asset{ - t.bootstrapIgnition, - t.masterIgnition, - t.workerIgnition, - } { - state, ok := parents[ign] - if !ok { - return nil, errors.Errorf("failed to get the ignition state for %v in the parent asset states", ign) - } + bootstrapIgn := string(bootstrap.Files()[0].Data) - for _, content := range state.Contents { - contents[ign] = append(contents[ign], string(content.Data)) - } + masterFiles := master.Files() + masterIgns := make([]string, len(masterFiles)) + for i, f := range masterFiles { + masterIgns[i] = string(f.Data) } - data, err := tfvars.TFVars(installCfg, contents[t.bootstrapIgnition][0], contents[t.masterIgnition], contents[t.workerIgnition][0]) + workerIgn := string(worker.Files()[0].Data) + + data, err := tfvars.TFVars(installConfig.Config, bootstrapIgn, masterIgns, workerIgn) if err != nil { - return nil, errors.Wrap(err, "failed to get Tfvars") + return errors.Wrap(err, "failed to get Tfvars") } + t.file = &asset.File{ + Filename: tfvarsFilename, + Data: data, + } + + return nil +} - return &asset.State{ - Contents: []asset.Content{ - { - Name: tfvarsFilename, - Data: []byte(data), - }, - { - Name: "platform", - Data: []byte(installCfg.Platform.Name()), - }, - }, - }, nil +// Files returns the files generated by the asset. +func (t *TerraformVariables) Files() []*asset.File { + return []*asset.File{t.file} } diff --git a/pkg/asset/ignition/bootstrap/bootstrap.go b/pkg/asset/ignition/bootstrap/bootstrap.go index 678f44056d9..8483d1ffded 100644 --- a/pkg/asset/ignition/bootstrap/bootstrap.go +++ b/pkg/asset/ignition/bootstrap/bootstrap.go @@ -44,147 +44,98 @@ type bootstrapTemplateData struct { ReleaseImage string } -// bootstrap is an asset that generates the ignition config for bootstrap nodes. -type bootstrap struct { - installConfig asset.Asset - rootCA asset.Asset - etcdCA asset.Asset - ingressCertKey asset.Asset - kubeCA asset.Asset - aggregatorCA asset.Asset - serviceServingCA asset.Asset - clusterAPIServerCertKey asset.Asset - etcdClientCertKey asset.Asset - apiServerCertKey asset.Asset - openshiftAPIServerCertKey asset.Asset - apiServerProxyCertKey asset.Asset - adminCertKey asset.Asset - kubeletCertKey asset.Asset - mcsCertKey asset.Asset - serviceAccountKeyPair asset.Asset - kubeconfig asset.Asset - kubeconfigKubelet asset.Asset - manifests asset.Asset - tectonic asset.Asset - kubeCoreOperator asset.Asset +// Bootstrap is an asset that generates the ignition config for bootstrap nodes. +type Bootstrap struct { + config *igntypes.Config + file *asset.File } -var _ asset.Asset = (*bootstrap)(nil) - -// newBootstrap creates a new bootstrap asset. -func newBootstrap( - installConfigStock installconfig.Stock, - tlsStock tls.Stock, - kubeconfigStock kubeconfig.Stock, - manifestStock manifests.Stock, -) *bootstrap { - return &bootstrap{ - installConfig: installConfigStock.InstallConfig(), - rootCA: tlsStock.RootCA(), - etcdCA: tlsStock.EtcdCA(), - ingressCertKey: tlsStock.IngressCertKey(), - kubeCA: tlsStock.KubeCA(), - aggregatorCA: tlsStock.AggregatorCA(), - serviceServingCA: tlsStock.ServiceServingCA(), - clusterAPIServerCertKey: tlsStock.ClusterAPIServerCertKey(), - etcdClientCertKey: tlsStock.EtcdClientCertKey(), - apiServerCertKey: tlsStock.APIServerCertKey(), - openshiftAPIServerCertKey: tlsStock.OpenshiftAPIServerCertKey(), - apiServerProxyCertKey: tlsStock.APIServerProxyCertKey(), - adminCertKey: tlsStock.AdminCertKey(), - kubeletCertKey: tlsStock.KubeletCertKey(), - mcsCertKey: tlsStock.MCSCertKey(), - serviceAccountKeyPair: tlsStock.ServiceAccountKeyPair(), - kubeconfig: kubeconfigStock.KubeconfigAdmin(), - kubeconfigKubelet: kubeconfigStock.KubeconfigKubelet(), - manifests: manifestStock.Manifests(), - tectonic: manifestStock.Tectonic(), - kubeCoreOperator: manifestStock.KubeCoreOperator(), - } -} +var _ asset.WritableAsset = (*Bootstrap)(nil) -// Dependencies returns the assets on which the bootstrap asset depends. -func (a *bootstrap) Dependencies() []asset.Asset { +// Dependencies returns the assets on which the Bootstrap asset depends. +func (a *Bootstrap) Dependencies() []asset.Asset { return []asset.Asset{ - a.installConfig, - a.rootCA, - a.etcdCA, - a.ingressCertKey, - a.kubeCA, - a.aggregatorCA, - a.serviceServingCA, - a.clusterAPIServerCertKey, - a.etcdClientCertKey, - a.apiServerCertKey, - a.openshiftAPIServerCertKey, - a.apiServerProxyCertKey, - a.adminCertKey, - a.kubeletCertKey, - a.mcsCertKey, - a.serviceAccountKeyPair, - a.kubeconfig, - a.kubeconfigKubelet, - a.manifests, - a.tectonic, - a.kubeCoreOperator, + &installconfig.InstallConfig{}, + &tls.RootCA{}, + &tls.EtcdCA{}, + &tls.IngressCertKey{}, + &tls.KubeCA{}, + &tls.AggregatorCA{}, + &tls.ServiceServingCA{}, + &tls.ClusterAPIServerCertKey{}, + &tls.EtcdClientCertKey{}, + &tls.APIServerCertKey{}, + &tls.OpenshiftAPIServerCertKey{}, + &tls.APIServerProxyCertKey{}, + &tls.AdminCertKey{}, + &tls.KubeletCertKey{}, + &tls.MCSCertKey{}, + &tls.ServiceAccountKeyPair{}, + &kubeconfig.Admin{}, + &kubeconfig.Kubelet{}, + &manifests.Manifests{}, + &manifests.Tectonic{}, + &manifests.KubeCoreOperator{}, } } -// Generate generates the ignition config for the bootstrap asset. -func (a *bootstrap) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - installConfig, err := installconfig.GetInstallConfig(a.installConfig, dependencies) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } +// Generate generates the ignition config for the Bootstrap asset. +func (a *Bootstrap) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + dependencies.Get(installConfig) - templateData, err := a.getTemplateData(installConfig) + templateData, err := a.getTemplateData(installConfig.Config) if err != nil { - return nil, errors.Wrap(err, "failed to get bootstrap templates") + return errors.Wrap(err, "failed to get bootstrap templates") } - config := igntypes.Config{ + a.config = &igntypes.Config{ Ignition: igntypes.Ignition{ Version: igntypes.MaxVersion.String(), }, } - a.addBootstrapFiles(&config, dependencies) - a.addBootkubeFiles(&config, dependencies, templateData) - a.addTectonicFiles(&config, dependencies, templateData) - a.addTLSCertFiles(&config, dependencies) + a.addBootstrapFiles(dependencies) + a.addBootkubeFiles(dependencies, templateData) + a.addTectonicFiles(dependencies, templateData) + a.addTLSCertFiles(dependencies) - config.Systemd.Units = append( - config.Systemd.Units, + a.config.Systemd.Units = append( + a.config.Systemd.Units, igntypes.Unit{Name: "bootkube.service", Contents: content.BootkubeSystemdContents}, igntypes.Unit{Name: "tectonic.service", Contents: content.TectonicSystemdContents, Enabled: util.BoolToPtr(true)}, igntypes.Unit{Name: "kubelet.service", Contents: applyTemplateData(content.KubeletSystemdTemplate, templateData), Enabled: util.BoolToPtr(true)}, ) - config.Passwd.Users = append( - config.Passwd.Users, - igntypes.PasswdUser{Name: "core", SSHAuthorizedKeys: []igntypes.SSHAuthorizedKey{igntypes.SSHAuthorizedKey(installConfig.Admin.SSHKey)}}, + a.config.Passwd.Users = append( + a.config.Passwd.Users, + igntypes.PasswdUser{Name: "core", SSHAuthorizedKeys: []igntypes.SSHAuthorizedKey{igntypes.SSHAuthorizedKey(installConfig.Config.Admin.SSHKey)}}, ) - data, err := json.Marshal(config) + data, err := json.Marshal(a.config) if err != nil { - return nil, errors.Wrap(err, "failed to Marshal Ignition config") + return errors.Wrap(err, "failed to Marshal Ignition config") + } + a.file = &asset.File{ + Filename: "bootstrap.ign", + Data: data, } - return &asset.State{ - Contents: []asset.Content{{ - Name: "bootstrap.ign", - Data: data, - }}, - }, nil + return nil } // Name returns the human-friendly name of the asset. -func (a *bootstrap) Name() string { +func (a *Bootstrap) Name() string { return "Bootstrap Ignition Config" } +// Files returns the files generated by the asset. +func (a *Bootstrap) Files() []*asset.File { + return []*asset.File{a.file} +} + // getTemplateData returns the data to use to execute bootstrap templates. -func (a *bootstrap) getTemplateData(installConfig *types.InstallConfig) (*bootstrapTemplateData, error) { +func (a *Bootstrap) getTemplateData(installConfig *types.InstallConfig) (*bootstrapTemplateData, error) { clusterDNSIP, err := installconfig.ClusterDNSIP(installConfig) if err != nil { return nil, errors.Wrap(err, "failed to get ClusterDNSIP from InstallConfig") @@ -215,67 +166,81 @@ func (a *bootstrap) getTemplateData(installConfig *types.InstallConfig) (*bootst }, nil } -func (a *bootstrap) addBootstrapFiles(config *igntypes.Config, dependencies map[asset.Asset]*asset.State) { - config.Storage.Files = append( - config.Storage.Files, - ignition.FileFromBytes("/etc/kubernetes/kubeconfig", 0600, dependencies[a.kubeconfigKubelet].Contents[0].Data), - ignition.FileFromBytes("/var/lib/kubelet/kubeconfig", 0600, dependencies[a.kubeconfigKubelet].Contents[0].Data), +func (a *Bootstrap) addBootstrapFiles(dependencies asset.Parents) { + kubeletKubeconfig := &kubeconfig.Kubelet{} + kubeCoreOperator := &manifests.KubeCoreOperator{} + dependencies.Get(kubeletKubeconfig, kubeCoreOperator) + + a.config.Storage.Files = append( + a.config.Storage.Files, + ignition.FileFromBytes("/etc/kubernetes/kubeconfig", 0600, kubeletKubeconfig.Files()[0].Data), + ignition.FileFromBytes("/var/lib/kubelet/kubeconfig", 0600, kubeletKubeconfig.Files()[0].Data), ) - config.Storage.Files = append( - config.Storage.Files, - ignition.FilesFromContents(rootDir, 0644, dependencies[a.kubeCoreOperator].Contents)..., + a.config.Storage.Files = append( + a.config.Storage.Files, + ignition.FilesFromAsset(rootDir, 0644, kubeCoreOperator)..., ) } -func (a *bootstrap) addBootkubeFiles(config *igntypes.Config, dependencies map[asset.Asset]*asset.State, templateData *bootstrapTemplateData) { - config.Storage.Files = append( - config.Storage.Files, +func (a *Bootstrap) addBootkubeFiles(dependencies asset.Parents, templateData *bootstrapTemplateData) { + adminKubeconfig := &kubeconfig.Admin{} + manifests := &manifests.Manifests{} + dependencies.Get(adminKubeconfig, manifests) + + a.config.Storage.Files = append( + a.config.Storage.Files, ignition.FileFromString("/opt/tectonic/bootkube.sh", 0555, applyTemplateData(content.BootkubeShFileTemplate, templateData)), ) - config.Storage.Files = append( - config.Storage.Files, - ignition.FilesFromContents(rootDir, 0600, dependencies[a.kubeconfig].Contents)..., + a.config.Storage.Files = append( + a.config.Storage.Files, + ignition.FilesFromAsset(rootDir, 0600, adminKubeconfig)..., ) - config.Storage.Files = append( - config.Storage.Files, - ignition.FilesFromContents(rootDir, 0644, dependencies[a.manifests].Contents)..., + a.config.Storage.Files = append( + a.config.Storage.Files, + ignition.FilesFromAsset(rootDir, 0644, manifests)..., ) } -func (a *bootstrap) addTectonicFiles(config *igntypes.Config, dependencies map[asset.Asset]*asset.State, templateData *bootstrapTemplateData) { - config.Storage.Files = append( - config.Storage.Files, +func (a *Bootstrap) addTectonicFiles(dependencies asset.Parents, templateData *bootstrapTemplateData) { + tectonic := &manifests.Tectonic{} + dependencies.Get(tectonic) + + a.config.Storage.Files = append( + a.config.Storage.Files, ignition.FileFromString("/opt/tectonic/tectonic.sh", 0555, content.TectonicShFileContents), ) - config.Storage.Files = append( - config.Storage.Files, - ignition.FilesFromContents(rootDir, 0644, dependencies[a.tectonic].Contents)..., + a.config.Storage.Files = append( + a.config.Storage.Files, + ignition.FilesFromAsset(rootDir, 0644, tectonic)..., ) } -func (a *bootstrap) addTLSCertFiles(config *igntypes.Config, dependencies map[asset.Asset]*asset.State) { - for _, asset := range []asset.Asset{ - a.rootCA, - a.kubeCA, - a.aggregatorCA, - a.serviceServingCA, - a.etcdCA, - a.clusterAPIServerCertKey, - a.etcdClientCertKey, - a.apiServerCertKey, - a.openshiftAPIServerCertKey, - a.apiServerProxyCertKey, - a.adminCertKey, - a.kubeletCertKey, - a.mcsCertKey, - a.serviceAccountKeyPair, +func (a *Bootstrap) addTLSCertFiles(dependencies asset.Parents) { + for _, asset := range []asset.WritableAsset{ + &tls.RootCA{}, + &tls.KubeCA{}, + &tls.AggregatorCA{}, + &tls.ServiceServingCA{}, + &tls.EtcdCA{}, + &tls.ClusterAPIServerCertKey{}, + &tls.EtcdClientCertKey{}, + &tls.APIServerCertKey{}, + &tls.OpenshiftAPIServerCertKey{}, + &tls.APIServerProxyCertKey{}, + &tls.AdminCertKey{}, + &tls.KubeletCertKey{}, + &tls.MCSCertKey{}, + &tls.ServiceAccountKeyPair{}, } { - config.Storage.Files = append(config.Storage.Files, ignition.FilesFromContents(rootDir, 0600, dependencies[asset].Contents)...) + dependencies.Get(asset) + a.config.Storage.Files = append(a.config.Storage.Files, ignition.FilesFromAsset(rootDir, 0600, asset)...) } - config.Storage.Files = append( - config.Storage.Files, - ignition.FileFromBytes("/etc/ssl/etcd/ca.crt", 0600, dependencies[a.etcdClientCertKey].Contents[tls.CertIndex].Data), + etcdClientCertKey := &tls.EtcdClientCertKey{} + dependencies.Get(etcdClientCertKey) + a.config.Storage.Files = append( + a.config.Storage.Files, + ignition.FileFromBytes("/etc/ssl/etcd/ca.crt", 0600, etcdClientCertKey.Cert()), ) } diff --git a/pkg/asset/ignition/bootstrap/stock.go b/pkg/asset/ignition/bootstrap/stock.go deleted file mode 100644 index 69901b1f49e..00000000000 --- a/pkg/asset/ignition/bootstrap/stock.go +++ /dev/null @@ -1,35 +0,0 @@ -package bootstrap - -import ( - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/asset/kubeconfig" - "github.com/openshift/installer/pkg/asset/manifests" - "github.com/openshift/installer/pkg/asset/tls" -) - -// Stock is the stock of the bootstrap ignition asset. -type Stock interface { - // BootstrapIgnition is the asset that generates the bootstrap.ign ignition - // config file for the bootstrap node. - BootstrapIgnition() asset.Asset -} - -// StockImpl is the implementation of the master and worker ignition -// asset stock. -type StockImpl struct { - boostrap asset.Asset -} - -// EstablishStock establishes the stock of assets. -func (s *StockImpl) EstablishStock( - installConfigStock installconfig.Stock, - tlsStock tls.Stock, - kubeconfigStock kubeconfig.Stock, - manifestStock manifests.Stock, -) { - s.boostrap = newBootstrap(installConfigStock, tlsStock, kubeconfigStock, manifestStock) -} - -// BootstrapIgnition returns the bootstrap asset. -func (s *StockImpl) BootstrapIgnition() asset.Asset { return s.boostrap } diff --git a/pkg/asset/ignition/machine/master.go b/pkg/asset/ignition/machine/master.go index 31c668d1080..a7383befabe 100644 --- a/pkg/asset/ignition/machine/master.go +++ b/pkg/asset/ignition/machine/master.go @@ -1,8 +1,10 @@ package machine import ( + "encoding/json" "fmt" + igntypes "github.com/coreos/ignition/config/v2_2/types" "github.com/pkg/errors" "github.com/openshift/installer/pkg/asset" @@ -10,52 +12,54 @@ import ( "github.com/openshift/installer/pkg/asset/tls" ) -// master is an asset that generates the ignition config for master nodes. -type master struct { - installConfig asset.Asset - rootCA asset.Asset +// Master is an asset that generates the ignition config for master nodes. +type Master struct { + configs []*igntypes.Config + files []*asset.File } -var _ asset.Asset = (*master)(nil) +var _ asset.WritableAsset = (*Master)(nil) -// newMaster generates a new master asset. -func newMaster( - installConfigStock installconfig.Stock, - tlsStock tls.Stock, -) *master { - return &master{ - installConfig: installConfigStock.InstallConfig(), - rootCA: tlsStock.RootCA(), - } -} - -// Dependencies returns the assets on which the master asset depends. -func (a *master) Dependencies() []asset.Asset { +// Dependencies returns the assets on which the Master asset depends. +func (a *Master) Dependencies() []asset.Asset { return []asset.Asset{ - a.installConfig, - a.rootCA, + &installconfig.InstallConfig{}, + &tls.RootCA{}, } } -// Generate generates the ignition config for the master asset. -func (a *master) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - installConfig, err := installconfig.GetInstallConfig(a.installConfig, dependencies) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } +// Generate generates the ignition config for the Master asset. +func (a *Master) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + rootCA := &tls.RootCA{} + dependencies.Get(installConfig, rootCA) - state := &asset.State{ - Contents: make([]asset.Content, installConfig.MasterCount()), + a.configs = make([]*igntypes.Config, installConfig.Config.MasterCount()) + for i := range a.configs { + a.configs[i] = pointerIgnitionConfig(installConfig.Config, rootCA.Cert(), "master", fmt.Sprintf("etcd_index=%d", i)) } - for i := range state.Contents { - state.Contents[i].Name = fmt.Sprintf("master-%d.ign", i) - state.Contents[i].Data = pointerIgnitionConfig(installConfig, dependencies[a.rootCA].Contents[tls.CertIndex].Data, "master", fmt.Sprintf("etcd_index=%d", i)) + + a.files = make([]*asset.File, len(a.configs)) + for i, c := range a.configs { + data, err := json.Marshal(c) + if err != nil { + return errors.Wrap(err, "failed to marshal ignition config") + } + a.files[i] = &asset.File{ + Filename: fmt.Sprintf("master-%d.ign", i), + Data: data, + } } - return state, nil + return nil } // Name returns the human-friendly name of the asset. -func (a *master) Name() string { +func (a *Master) Name() string { return "Master Ignition Config(s)" } + +// Files returns the files generated by the asset. +func (a *Master) Files() []*asset.File { + return a.files +} diff --git a/pkg/asset/ignition/machine/master_test.go b/pkg/asset/ignition/machine/master_test.go index da7abc6a5ae..950ae921a97 100644 --- a/pkg/asset/ignition/machine/master_test.go +++ b/pkg/asset/ignition/machine/master_test.go @@ -1,48 +1,68 @@ package machine import ( + "net" "testing" "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" + "github.com/openshift/installer/pkg/ipnet" + "github.com/openshift/installer/pkg/types" ) // TestMasterGenerate tests generating the master asset. func TestMasterGenerate(t *testing.T) { - installConfig := ` -metadata: - name: test-cluster -baseDomain: test-domain -networking: - ServiceCIDR: 10.0.1.0/24 -platform: - aws: - region: us-east -machines: -- name: master - replicas: 3 -` - installConfigAsset := &testAsset{"install-config"} - rootCAAsset := &testAsset{"rootCA"} - master := &master{ - installConfig: installConfigAsset, - rootCA: rootCAAsset, + installConfig := &installconfig.InstallConfig{ + Config: &types.InstallConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + BaseDomain: "test-domain", + Networking: types.Networking{ + ServiceCIDR: ipnet.IPNet{ + IPNet: func(s string) net.IPNet { + _, cidr, _ := net.ParseCIDR(s) + return *cidr + }("10.0.1.0/24"), + }, + }, + Platform: types.Platform{ + AWS: &types.AWSPlatform{ + Region: "us-east", + }, + }, + Machines: []types.MachinePool{ + { + Name: "master", + Replicas: func(x int64) *int64 { return &x }(3), + }, + }, + }, } - dependencies := map[asset.Asset]*asset.State{ - installConfigAsset: stateWithContentsData(installConfig), - rootCAAsset: stateWithContentsData("test-rootCA-priv", "test-rootCA-pub"), - } - masterState, err := master.Generate(dependencies) + + rootCA := &tls.RootCA{} + err := rootCA.Generate(nil) + assert.NoError(t, err, "unexpected error generating root CA") + + parents := asset.Parents{} + parents.Add(installConfig, rootCA) + + master := &Master{} + err = master.Generate(parents) assert.NoError(t, err, "unexpected error generating master asset") expectedIgnitionConfigNames := []string{ "master-0.ign", "master-1.ign", "master-2.ign", } - actualIgnitionConfigNames := make([]string, len(masterState.Contents)) - for i, c := range masterState.Contents { - actualIgnitionConfigNames[i] = c.Name + actualFiles := master.Files() + actualIgnitionConfigNames := make([]string, len(actualFiles)) + for i, f := range actualFiles { + actualIgnitionConfigNames[i] = f.Filename } assert.Equal(t, expectedIgnitionConfigNames, actualIgnitionConfigNames, "unexpected names for master ignition configs") } diff --git a/pkg/asset/ignition/machine/node.go b/pkg/asset/ignition/machine/node.go index 2f9798bd08a..9cdefca7978 100644 --- a/pkg/asset/ignition/machine/node.go +++ b/pkg/asset/ignition/machine/node.go @@ -1,7 +1,6 @@ package machine import ( - "encoding/json" "fmt" "net/url" @@ -13,8 +12,8 @@ import ( // pointerIgnitionConfig generates a config which references the remote config // served by the machine config server. -func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, role string, query string) []byte { - data, err := json.Marshal(ignition.Config{ +func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, role string, query string) *ignition.Config { + return &ignition.Config{ Ignition: ignition.Ignition{ Version: ignition.MaxVersion.String(), Config: ignition.IgnitionConfig{ @@ -44,9 +43,5 @@ func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, ro SSHAuthorizedKeys: []ignition.SSHAuthorizedKey{ignition.SSHAuthorizedKey(installConfig.Admin.SSHKey)}, }}, }, - }) - if err != nil { - panic(fmt.Sprintf("Failed to marshal pointer Ignition config: %v", err)) } - return data } diff --git a/pkg/asset/ignition/machine/stock.go b/pkg/asset/ignition/machine/stock.go deleted file mode 100644 index bbab8b8323d..00000000000 --- a/pkg/asset/ignition/machine/stock.go +++ /dev/null @@ -1,38 +0,0 @@ -package machine - -import ( - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/asset/tls" -) - -// Stock is the stock of master and worker ignition assets. -type Stock interface { - // MasterIgnition is the asset that generates the master.ign ignition config - // files for the master nodes. - MasterIgnition() asset.Asset - // WorkerIgnition is the asset that generates the worker.ign ignition config - // file for the worker nodes. - WorkerIgnition() asset.Asset -} - -// StockImpl is the implementation of the bootstrap ignition asset stock. -type StockImpl struct { - master asset.Asset - worker asset.Asset -} - -// EstablishStock establishes the stock of assets. -func (s *StockImpl) EstablishStock( - installConfigStock installconfig.Stock, - tlsStock tls.Stock, -) { - s.master = newMaster(installConfigStock, tlsStock) - s.worker = newWorker(installConfigStock, tlsStock) -} - -// MasterIgnition returns the master asset. -func (s *StockImpl) MasterIgnition() asset.Asset { return s.master } - -// WorkerIgnition returns the worker asset. -func (s *StockImpl) WorkerIgnition() asset.Asset { return s.worker } diff --git a/pkg/asset/ignition/machine/testasset_test.go b/pkg/asset/ignition/machine/testasset_test.go deleted file mode 100644 index 7e46b42bc4d..00000000000 --- a/pkg/asset/ignition/machine/testasset_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package machine - -import ( - "github.com/openshift/installer/pkg/asset" -) - -type testAsset struct { - name string -} - -func (a *testAsset) Dependencies() []asset.Asset { - return []asset.Asset{} -} - -func (a *testAsset) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { - return nil, nil -} - -func (a *testAsset) Name() string { - return "Test Asset" -} - -func stateWithContentsData(contentsData ...string) *asset.State { - state := &asset.State{ - Contents: make([]asset.Content, len(contentsData)), - } - for i, d := range contentsData { - state.Contents[i].Data = []byte(d) - } - return state -} diff --git a/pkg/asset/ignition/machine/worker.go b/pkg/asset/ignition/machine/worker.go index 2b92ffa6648..4a2f61ad9d0 100644 --- a/pkg/asset/ignition/machine/worker.go +++ b/pkg/asset/ignition/machine/worker.go @@ -1,6 +1,9 @@ package machine import ( + "encoding/json" + + igntypes "github.com/coreos/ignition/config/v2_2/types" "github.com/pkg/errors" "github.com/openshift/installer/pkg/asset" @@ -8,49 +11,48 @@ import ( "github.com/openshift/installer/pkg/asset/tls" ) -// worker is an asset that generates the ignition config for worker nodes. -type worker struct { - installConfig asset.Asset - rootCA asset.Asset +// Worker is an asset that generates the ignition config for worker nodes. +type Worker struct { + config *igntypes.Config + file *asset.File } -var _ asset.Asset = (*worker)(nil) +var _ asset.WritableAsset = (*Worker)(nil) -// newWorker creates a new worker asset. -func newWorker( - installConfigStock installconfig.Stock, - tlsStock tls.Stock, -) *worker { - return &worker{ - installConfig: installConfigStock.InstallConfig(), - rootCA: tlsStock.RootCA(), - } -} - -// Dependencies returns the assets on which the worker asset depends. -func (a *worker) Dependencies() []asset.Asset { +// Dependencies returns the assets on which the Worker asset depends. +func (a *Worker) Dependencies() []asset.Asset { return []asset.Asset{ - a.installConfig, - a.rootCA, + &installconfig.InstallConfig{}, + &tls.RootCA{}, } } -// Generate generates the ignition config for the worker asset. -func (a *worker) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - installConfig, err := installconfig.GetInstallConfig(a.installConfig, dependencies) +// Generate generates the ignition config for the Worker asset. +func (a *Worker) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + rootCA := &tls.RootCA{} + dependencies.Get(installConfig, rootCA) + + a.config = pointerIgnitionConfig(installConfig.Config, rootCA.Cert(), "worker", "") + + data, err := json.Marshal(a.config) if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") + return errors.Wrap(err, "failed to get InstallConfig from parents") + } + a.file = &asset.File{ + Filename: "worker.ign", + Data: data, } - return &asset.State{ - Contents: []asset.Content{{ - Name: "worker.ign", - Data: pointerIgnitionConfig(installConfig, dependencies[a.rootCA].Contents[tls.CertIndex].Data, "worker", ""), - }}, - }, nil + return nil } // Name returns the human-friendly name of the asset. -func (a *worker) Name() string { +func (a *Worker) Name() string { return "Worker Ignition Config" } + +// Files returns the files generated by the asset. +func (a *Worker) Files() []*asset.File { + return []*asset.File{a.file} +} diff --git a/pkg/asset/ignition/machine/worker_test.go b/pkg/asset/ignition/machine/worker_test.go index d8bde0f7519..680badc62a2 100644 --- a/pkg/asset/ignition/machine/worker_test.go +++ b/pkg/asset/ignition/machine/worker_test.go @@ -1,34 +1,50 @@ package machine import ( + "net" "testing" "github.com/stretchr/testify/assert" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" + "github.com/openshift/installer/pkg/ipnet" + "github.com/openshift/installer/pkg/types" ) // TestWorkerGenerate tests generating the worker asset. func TestWorkerGenerate(t *testing.T) { - installConfig := ` -networking: - ServiceCIDR: 10.0.1.0/24 -platform: - aws: -region: us-east -` - installConfigAsset := &testAsset{"install-config"} - rootCAAsset := &testAsset{"rootCA"} - worker := &worker{ - installConfig: installConfigAsset, - rootCA: rootCAAsset, + installConfig := &installconfig.InstallConfig{ + Config: &types.InstallConfig{ + Networking: types.Networking{ + ServiceCIDR: ipnet.IPNet{ + IPNet: func(s string) net.IPNet { + _, cidr, _ := net.ParseCIDR(s) + return *cidr + }("10.0.1.0/24"), + }, + }, + Platform: types.Platform{ + AWS: &types.AWSPlatform{ + Region: "us-east", + }, + }, + }, } - dependencies := map[asset.Asset]*asset.State{ - installConfigAsset: stateWithContentsData(installConfig), - rootCAAsset: stateWithContentsData("test-rootCA-priv", "test-rootCA-pub"), - } - workerState, err := worker.Generate(dependencies) + + rootCA := &tls.RootCA{} + err := rootCA.Generate(nil) + assert.NoError(t, err, "unexpected error generating root CA") + + parents := asset.Parents{} + parents.Add(installConfig, rootCA) + + worker := &Worker{} + err = worker.Generate(parents) assert.NoError(t, err, "unexpected error generating worker asset") - assert.Equal(t, 1, len(workerState.Contents), "unexpected number of contents in worker state") - assert.Equal(t, "worker.ign", workerState.Contents[0].Name, "unexpected name for worker ignition config") + + actualFiles := worker.Files() + assert.Equal(t, 1, len(actualFiles), "unexpected number of files in worker state") + assert.Equal(t, "worker.ign", actualFiles[0].Filename, "unexpected name for worker ignition config") } diff --git a/pkg/asset/ignition/node.go b/pkg/asset/ignition/node.go index 89ae76a94e9..09b70eef2de 100644 --- a/pkg/asset/ignition/node.go +++ b/pkg/asset/ignition/node.go @@ -9,12 +9,12 @@ import ( "github.com/openshift/installer/pkg/asset" ) -// FilesFromContents creates an ignition-config file with the contents from the -// specified index in the specified asset state. -func FilesFromContents(pathPrefix string, mode int, contents []asset.Content) []ignition.File { +// FilesFromAsset creates ignition files for each of the files in the specified +// asset. +func FilesFromAsset(pathPrefix string, mode int, asset asset.WritableAsset) []ignition.File { var files []ignition.File - for _, c := range contents { - files = append(files, FileFromBytes(filepath.Join(pathPrefix, c.Name), mode, c.Data)) + for _, f := range asset.Files() { + files = append(files, FileFromBytes(filepath.Join(pathPrefix, f.Filename), mode, f.Data)) } return files } diff --git a/pkg/asset/installconfig/basedomain.go b/pkg/asset/installconfig/basedomain.go new file mode 100644 index 00000000000..aa6428e6a2b --- /dev/null +++ b/pkg/asset/installconfig/basedomain.go @@ -0,0 +1,43 @@ +package installconfig + +import ( + survey "gopkg.in/AlecAivazis/survey.v1" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/validate" +) + +type baseDomain struct { + baseDomain string +} + +var _ asset.Asset = (*baseDomain)(nil) + +// Dependencies returns no dependencies. +func (a *baseDomain) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate queries for the base domain from the user. +func (a *baseDomain) Generate(asset.Parents) error { + bd, err := asset.GenerateUserProvidedAsset( + a.Name(), + &survey.Question{ + Prompt: &survey.Input{ + Message: "Base Domain", + Help: "The base domain of the cluster. All DNS records will be sub-domains of this base.\n\nFor AWS, this must be a previously-existing public Route 53 zone. You can check for any already in your account with:\n\n $ aws route53 list-hosted-zones --query 'HostedZones[? !(Config.PrivateZone)].Name' --output text", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + return validate.DomainName(ans.(string)) + }), + }, + "OPENSHIFT_INSTALL_BASE_DOMAIN", + ) + a.baseDomain = bd + return err +} + +// Name returns the human-friendly name of the asset. +func (a *baseDomain) Name() string { + return "Base Domain" +} diff --git a/pkg/asset/installconfig/clusterid.go b/pkg/asset/installconfig/clusterid.go index 5adebe26a27..3d250ee5f5d 100644 --- a/pkg/asset/installconfig/clusterid.go +++ b/pkg/asset/installconfig/clusterid.go @@ -6,7 +6,9 @@ import ( "github.com/openshift/installer/pkg/asset" ) -type clusterID struct{} +type clusterID struct { + clusterID string +} var _ asset.Asset = (*clusterID)(nil) @@ -16,12 +18,9 @@ func (a *clusterID) Dependencies() []asset.Asset { } // Generate generates a new UUID -func (a *clusterID) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { - return &asset.State{ - Contents: []asset.Content{ - {Data: []byte(uuid.New())}, - }, - }, nil +func (a *clusterID) Generate(asset.Parents) error { + a.clusterID = uuid.New() + return nil } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/installconfig/clustername.go b/pkg/asset/installconfig/clustername.go new file mode 100644 index 00000000000..2d5f2c7b617 --- /dev/null +++ b/pkg/asset/installconfig/clustername.go @@ -0,0 +1,43 @@ +package installconfig + +import ( + survey "gopkg.in/AlecAivazis/survey.v1" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/validate" +) + +type clusterName struct { + clusterName string +} + +var _ asset.Asset = (*clusterName)(nil) + +// Dependencies returns no dependencies. +func (a *clusterName) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate queries for the cluster name from the user. +func (a *clusterName) Generate(asset.Parents) error { + n, err := asset.GenerateUserProvidedAsset( + a.Name(), + &survey.Question{ + Prompt: &survey.Input{ + Message: "Cluster Name", + Help: "The name of the cluster. This will be used when generating sub-domains.", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + return validate.DomainName(ans.(string)) + }), + }, + "OPENSHIFT_INSTALL_CLUSTER_NAME", + ) + a.clusterName = n + return err +} + +// Name returns the human-friendly name of the asset. +func (a *clusterName) Name() string { + return "Cluster Name" +} diff --git a/pkg/asset/installconfig/emailaddress.go b/pkg/asset/installconfig/emailaddress.go new file mode 100644 index 00000000000..ece9f4209b0 --- /dev/null +++ b/pkg/asset/installconfig/emailaddress.go @@ -0,0 +1,43 @@ +package installconfig + +import ( + survey "gopkg.in/AlecAivazis/survey.v1" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/validate" +) + +type emailAddress struct { + emailAddress string +} + +var _ asset.Asset = (*emailAddress)(nil) + +// Dependencies returns no dependencies. +func (a *emailAddress) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate queries for the email address from the user. +func (a *emailAddress) Generate(asset.Parents) error { + email, err := asset.GenerateUserProvidedAsset( + a.Name(), + &survey.Question{ + Prompt: &survey.Input{ + Message: "Email Address", + Help: "The email address of the cluster administrator. This will be used to log in to the console.", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + return validate.Email(ans.(string)) + }), + }, + "OPENSHIFT_INSTALL_EMAIL_ADDRESS", + ) + a.emailAddress = email + return err +} + +// Name returns the human-friendly name of the asset. +func (a *emailAddress) Name() string { + return "Email Address" +} diff --git a/pkg/asset/installconfig/installconfig.go b/pkg/asset/installconfig/installconfig.go index 33a7833d86f..993cf7772e4 100644 --- a/pkg/asset/installconfig/installconfig.go +++ b/pkg/asset/installconfig/installconfig.go @@ -1,8 +1,6 @@ package installconfig import ( - "encoding/json" - "fmt" "net" "github.com/apparentlymart/go-cidr/cidr" @@ -15,54 +13,70 @@ import ( "github.com/openshift/installer/pkg/types" ) +const ( + installConfigFilename = "install-config.yml" +) + var ( defaultServiceCIDR = parseCIDR("10.3.0.0/16") defaultPodCIDR = parseCIDR("10.2.0.0/16") ) -// installConfig generates the install-config.yml file. -type installConfig struct { - assetStock Stock +// InstallConfig generates the install-config.yml file. +type InstallConfig struct { + Config *types.InstallConfig + file *asset.File } -var _ asset.Asset = (*installConfig)(nil) +var _ asset.WritableAsset = (*InstallConfig)(nil) // Dependencies returns all of the dependencies directly needed by an -// installConfig asset. -func (a *installConfig) Dependencies() []asset.Asset { +// InstallConfig asset. +func (a *InstallConfig) Dependencies() []asset.Asset { return []asset.Asset{ - a.assetStock.ClusterID(), - a.assetStock.EmailAddress(), - a.assetStock.Password(), - a.assetStock.SSHKey(), - a.assetStock.BaseDomain(), - a.assetStock.ClusterName(), - a.assetStock.PullSecret(), - a.assetStock.Platform(), + &clusterID{}, + &emailAddress{}, + &password{}, + &sshPublicKey{}, + &baseDomain{}, + &clusterName{}, + &pullSecret{}, + &platform{}, } } // Generate generates the install-config.yml file. -func (a *installConfig) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - clusterID := string(dependencies[a.assetStock.ClusterID()].Contents[0].Data) - emailAddress := string(dependencies[a.assetStock.EmailAddress()].Contents[0].Data) - password := string(dependencies[a.assetStock.Password()].Contents[0].Data) - sshKey := string(dependencies[a.assetStock.SSHKey()].Contents[0].Data) - baseDomain := string(dependencies[a.assetStock.BaseDomain()].Contents[0].Data) - clusterName := string(dependencies[a.assetStock.ClusterName()].Contents[0].Data) - pullSecret := string(dependencies[a.assetStock.PullSecret()].Contents[0].Data) - - installConfig := types.InstallConfig{ +func (a *InstallConfig) Generate(parents asset.Parents) error { + clusterID := &clusterID{} + emailAddress := &emailAddress{} + password := &password{} + sshPublicKey := &sshPublicKey{} + baseDomain := &baseDomain{} + clusterName := &clusterName{} + pullSecret := &pullSecret{} + platform := &platform{} + parents.Get( + clusterID, + emailAddress, + password, + sshPublicKey, + baseDomain, + clusterName, + pullSecret, + platform, + ) + + a.Config = &types.InstallConfig{ ObjectMeta: metav1.ObjectMeta{ - Name: clusterName, + Name: clusterName.clusterName, }, - ClusterID: clusterID, + ClusterID: clusterID.clusterID, Admin: types.Admin{ - Email: emailAddress, - Password: password, - SSHKey: sshKey, + Email: emailAddress.emailAddress, + Password: password.password, + SSHKey: sshPublicKey.key, }, - BaseDomain: baseDomain, + BaseDomain: baseDomain.baseDomain, Networking: types.Networking{ // TODO(yifan): Flannel is the temporal default network type for now, // Need to update it to the new types. @@ -75,93 +89,56 @@ func (a *installConfig) Generate(dependencies map[asset.Asset]*asset.State) (*as IPNet: defaultPodCIDR, }, }, - PullSecret: pullSecret, + PullSecret: pullSecret.pullSecret, } - platformState := dependencies[a.assetStock.Platform()] - platform := string(platformState.Contents[0].Data) - switch platform { - case AWSPlatformType: - if err := json.Unmarshal(platformState.Contents[1].Data, &installConfig.AWS); err != nil { - return nil, err - } - - installConfig.Machines = []types.MachinePool{ - { - Name: "master", - Replicas: func(x int64) *int64 { return &x }(3), - }, - { - Name: "worker", - Replicas: func(x int64) *int64 { return &x }(3), - }, - } - case OpenStackPlatformType: - if err := json.Unmarshal(platformState.Contents[1].Data, &installConfig.OpenStack); err != nil { - return nil, err - } - installConfig.Machines = []types.MachinePool{ - { - Name: "master", - Replicas: func(x int64) *int64 { return &x }(3), - }, - { - Name: "worker", - Replicas: func(x int64) *int64 { return &x }(3), - }, - } - case LibvirtPlatformType: - if err := json.Unmarshal(platformState.Contents[1].Data, &installConfig.Libvirt); err != nil { - return nil, err - } - installConfig.Libvirt.Network.Name = clusterName - installConfig.Machines = []types.MachinePool{ - { - Name: "master", - Replicas: func(x int64) *int64 { return &x }(1), - }, - { - Name: "worker", - Replicas: func(x int64) *int64 { return &x }(1), - }, - } + numberOfMasters := int64(3) + numberOfWorkers := int64(3) + switch { + case platform.aws != nil: + a.Config.AWS = platform.aws + case platform.openstack != nil: + a.Config.OpenStack = platform.openstack + case platform.libvirt != nil: + a.Config.Libvirt = platform.libvirt + a.Config.Libvirt.Network.Name = clusterName.clusterName + numberOfMasters = 1 + numberOfWorkers = 1 default: - return nil, fmt.Errorf("unknown platform type %q", platform) + panic("unknown platform type") } - data, err := yaml.Marshal(installConfig) + a.Config.Machines = []types.MachinePool{ + { + Name: "master", + Replicas: func(x int64) *int64 { return &x }(numberOfMasters), + }, + { + Name: "worker", + Replicas: func(x int64) *int64 { return &x }(numberOfWorkers), + }, + } + + data, err := yaml.Marshal(a.Config) if err != nil { - return nil, errors.Wrap(err, "failed to Marshal InstallConfig") + return errors.Wrap(err, "failed to Marshal InstallConfig") + } + a.file = &asset.File{ + Filename: "install-config.yml", + Data: data, } - return &asset.State{ - Contents: []asset.Content{ - { - Name: "install-config.yml", - Data: data, - }, - }, - }, nil + return nil } // Name returns the human-friendly name of the asset. -func (a installConfig) Name() string { +func (a *InstallConfig) Name() string { return "Install Config" } -// GetInstallConfig returns the *types.InstallConfig from the parent asset map. -func GetInstallConfig(installConfig asset.Asset, parents map[asset.Asset]*asset.State) (*types.InstallConfig, error) { - var cfg types.InstallConfig - - st, ok := parents[installConfig] - if !ok { - return nil, errors.Errorf("%T does not exist in parents", installConfig) - } - - if err := yaml.Unmarshal(st.Contents[0].Data, &cfg); err != nil { - return nil, errors.Wrap(err, "failed to Unmarshal the installconfig") - } - return &cfg, nil +// Files returns the files generated by the asset. +func (a *InstallConfig) Files() []*asset.File { + return []*asset.File{a.file} } // ClusterDNSIP returns the string representation of the DNS server's IP diff --git a/pkg/asset/installconfig/installconfig_test.go b/pkg/asset/installconfig/installconfig_test.go index 12c3d6e8989..f0768f051db 100644 --- a/pkg/asset/installconfig/installconfig_test.go +++ b/pkg/asset/installconfig/installconfig_test.go @@ -6,61 +6,10 @@ import ( "github.com/stretchr/testify/assert" - "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/ipnet" "github.com/openshift/installer/pkg/types" ) -type testAsset struct { - name string -} - -func (a *testAsset) Dependencies() []asset.Asset { - return []asset.Asset{} -} - -func (a *testAsset) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { - return nil, nil -} - -func (a *testAsset) Name() string { - return "Test Asset" -} - -func TestInstallConfigDependencies(t *testing.T) { - stock := &StockImpl{ - clusterID: &testAsset{name: "test-cluster-id"}, - emailAddress: &testAsset{name: "test-email"}, - password: &testAsset{name: "test-password"}, - sshKey: &testAsset{name: "test-sshkey"}, - baseDomain: &testAsset{name: "test-domain"}, - clusterName: &testAsset{name: "test-cluster"}, - pullSecret: &testAsset{name: "test-pull-secret"}, - platform: &testAsset{name: "test-platform"}, - } - installConfig := &installConfig{ - assetStock: stock, - } - exp := []string{ - "test-cluster-id", - "test-email", - "test-password", - "test-sshkey", - "test-domain", - "test-cluster", - "test-pull-secret", - "test-platform", - } - deps := installConfig.Dependencies() - act := make([]string, len(deps)) - for i, d := range deps { - a, ok := d.(*testAsset) - assert.True(t, ok, "expected dependency to be a *testAsset") - act[i] = a.name - } - assert.Equal(t, exp, act, "unexpected dependency") -} - // TestClusterDNSIP tests the ClusterDNSIP function. func TestClusterDNSIP(t *testing.T) { _, cidr, err := net.ParseCIDR("10.0.1.0/24") diff --git a/pkg/asset/installconfig/password.go b/pkg/asset/installconfig/password.go new file mode 100644 index 00000000000..4045443296f --- /dev/null +++ b/pkg/asset/installconfig/password.go @@ -0,0 +1,39 @@ +package installconfig + +import ( + survey "gopkg.in/AlecAivazis/survey.v1" + + "github.com/openshift/installer/pkg/asset" +) + +type password struct { + password string +} + +var _ asset.Asset = (*password)(nil) + +// Dependencies returns no dependencies. +func (a *password) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate queries for the password from the user. +func (a *password) Generate(asset.Parents) error { + p, err := asset.GenerateUserProvidedAsset( + a.Name(), + &survey.Question{ + Prompt: &survey.Password{ + Message: "Password", + Help: "The password of the cluster administrator. This will be used to log in to the console.", + }, + }, + "OPENSHIFT_INSTALL_PASSWORD", + ) + a.password = p + return err +} + +// Name returns the human-friendly name of the asset. +func (a *password) Name() string { + return "Password" +} diff --git a/pkg/asset/installconfig/platform.go b/pkg/asset/installconfig/platform.go index 0372e73086a..f0c191b4119 100644 --- a/pkg/asset/installconfig/platform.go +++ b/pkg/asset/installconfig/platform.go @@ -57,52 +57,62 @@ var ( // Platform is an asset that queries the user for the platform on which to install // the cluster. -// -// Contents[0] is the type of the platform. -// -// * AWS -// Contents[1] is the region. -// -// * Libvirt -// Contents[1] is the URI. -type Platform struct{} +type platform struct { + aws *types.AWSPlatform + openstack *types.OpenStackPlatform + libvirt *types.LibvirtPlatform +} -var _ asset.Asset = (*Platform)(nil) +var _ asset.Asset = (*platform)(nil) // Dependencies returns no dependencies. -func (a *Platform) Dependencies() []asset.Asset { +func (a *platform) Dependencies() []asset.Asset { return []asset.Asset{} } // Generate queries for input from the user. -func (a *Platform) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { +func (a *platform) Generate(asset.Parents) error { platform, err := a.queryUserForPlatform() if err != nil { - return nil, err + return err } switch platform { case AWSPlatformType: - return a.awsPlatform() + aws, err := a.awsPlatform() + if err != nil { + return err + } + a.aws = aws case OpenStackPlatformType: - return a.openstackPlatform() + openstack, err := a.openstackPlatform() + if err != nil { + return err + } + a.openstack = openstack case LibvirtPlatformType: - return a.libvirtPlatform() + libvirt, err := a.libvirtPlatform() + if err != nil { + return err + } + a.libvirt = libvirt default: - return nil, fmt.Errorf("unknown platform type %q", platform) + return fmt.Errorf("unknown platform type %q", platform) } + + return nil } // Name returns the human-friendly name of the asset. -func (a *Platform) Name() string { +func (a *platform) Name() string { return "Platform" } -func (a *Platform) queryUserForPlatform() (string, error) { +func (a *platform) queryUserForPlatform() (string, error) { sort.Strings(validPlatforms) - prompt := asset.UserProvided{ - AssetName: "Platform", - Question: &survey.Question{ + return asset.GenerateUserProvidedAsset( + "Platform", + &survey.Question{ Prompt: &survey.Select{ Message: "Platform", Options: validPlatforms, @@ -116,22 +126,11 @@ func (a *Platform) queryUserForPlatform() (string, error) { return nil }), }, - EnvVarName: "OPENSHIFT_INSTALL_PLATFORM", - } - - platform, err := prompt.Generate(nil) - if err != nil { - return "", err - } - - return string(platform.Contents[0].Data), nil + "OPENSHIFT_INSTALL_PLATFORM", + ) } -func (a *Platform) awsPlatform() (*asset.State, error) { - platform := &types.AWSPlatform{ - VPCCIDRBlock: defaultVPCCIDR, - } - +func (a *platform) awsPlatform() (*types.AWSPlatform, error) { longRegions := make([]string, 0, len(validAWSRegions)) shortRegions := make([]string, 0, len(validAWSRegions)) for id, location := range validAWSRegions { @@ -143,9 +142,9 @@ func (a *Platform) awsPlatform() (*asset.State, error) { }) sort.Strings(longRegions) sort.Strings(shortRegions) - prompt := asset.UserProvided{ - AssetName: "AWS Region", - Question: &survey.Question{ + region, err := asset.GenerateUserProvidedAsset( + "AWS Region", + &survey.Question{ Prompt: &survey.Select{ Message: "Region", Help: "The AWS region to be used for installation.", @@ -162,46 +161,30 @@ func (a *Platform) awsPlatform() (*asset.State, error) { }), Transform: regionTransform, }, - EnvVarName: "OPENSHIFT_INSTALL_AWS_REGION", - } - region, err := prompt.Generate(nil) + "OPENSHIFT_INSTALL_AWS_REGION", + ) if err != nil { return nil, err } - platform.Region = string(region.Contents[0].Data) + userTags := map[string]string{} if value, ok := os.LookupEnv("_CI_ONLY_STAY_AWAY_OPENSHIFT_INSTALL_AWS_USER_TAGS"); ok { - if err := json.Unmarshal([]byte(value), &platform.UserTags); err != nil { + if err := json.Unmarshal([]byte(value), &userTags); err != nil { return nil, errors.Wrapf(err, "_CI_ONLY_STAY_AWAY_OPENSHIFT_INSTALL_AWS_USER_TAGS contains invalid JSON: %s", value) } } - data, err := json.Marshal(platform) - if err != nil { - return nil, errors.Wrapf(err, "failed to Marshal %s platform", AWSPlatformType) - } - - return &asset.State{ - Contents: []asset.Content{ - { - Name: "platform", - Data: []byte(AWSPlatformType), - }, - { - Name: "platform.json", - Data: data, - }, - }, + return &types.AWSPlatform{ + VPCCIDRBlock: defaultVPCCIDR, + Region: region, + UserTags: userTags, }, nil } -func (a *Platform) openstackPlatform() (*asset.State, error) { - platform := &types.OpenStackPlatform{ - NetworkCIDRBlock: defaultVPCCIDR, - } - prompt := asset.UserProvided{ - AssetName: "OpenStack Region", - Question: &survey.Question{ +func (a *platform) openstackPlatform() (*types.OpenStackPlatform, error) { + region, err := asset.GenerateUserProvidedAsset( + "OpenStack Region", + &survey.Question{ Prompt: &survey.Input{ Message: "Region", Help: "The OpenStack region to be used for installation.", @@ -213,16 +196,15 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { return nil }), }, - EnvVarName: "OPENSHIFT_INSTALL_OPENSTACK_REGION", - } - region, err := prompt.Generate(nil) + "OPENSHIFT_INSTALL_OPENSTACK_REGION", + ) if err != nil { return nil, err } - platform.Region = string(region.Contents[0].Data) - prompt2 := asset.UserProvided{ - AssetName: "OpenStack Image", - Question: &survey.Question{ + + image, err := asset.GenerateUserProvidedAsset( + "OpenStack Image", + &survey.Question{ Prompt: &survey.Input{ Message: "Image", Help: "The OpenStack image to be used for installation.", @@ -234,16 +216,15 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { return nil }), }, - EnvVarName: "OPENSHIFT_INSTALL_OPENSTACK_IMAGE", - } - image, err := prompt2.Generate(nil) + "OPENSHIFT_INSTALL_OPENSTACK_IMAGE", + ) if err != nil { return nil, err } - platform.BaseImage = string(image.Contents[0].Data) - prompt3 := asset.UserProvided{ - AssetName: "OpenStack Cloud", - Question: &survey.Question{ + + cloud, err := asset.GenerateUserProvidedAsset( + "OpenStack Cloud", + &survey.Question{ //TODO(russellb) - We could open clouds.yaml here and read the list of defined clouds //and then use survey.Select to let the user choose one. Prompt: &survey.Input{ @@ -256,16 +237,15 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { return nil }), }, - EnvVarName: "OPENSHIFT_INSTALL_OPENSTACK_CLOUD", - } - cloud, err := prompt3.Generate(nil) + "OPENSHIFT_INSTALL_OPENSTACK_CLOUD", + ) if err != nil { return nil, err } - platform.Cloud = string(cloud.Contents[0].Data) - prompt4 := asset.UserProvided{ - AssetName: "OpenStack External Network", - Question: &survey.Question{ + + extNet, err := asset.GenerateUserProvidedAsset( + "OpenStack External Network", + &survey.Question{ Prompt: &survey.Input{ Message: "ExternalNetwork", Help: "The OpenStack external network to be used for installation.", @@ -276,48 +256,25 @@ func (a *Platform) openstackPlatform() (*asset.State, error) { return nil }), }, - EnvVarName: "OPENSHIFT_INSTALL_OPENSTACK_EXTERNAL_NETWORK", - } - extNet, err := prompt4.Generate(nil) - if err != nil { - return nil, err - } - platform.ExternalNetwork = string(extNet.Contents[0].Data) - - data, err := json.Marshal(platform) + "OPENSHIFT_INSTALL_OPENSTACK_EXTERNAL_NETWORK", + ) if err != nil { return nil, errors.Wrapf(err, "failed to Marshal %s platform", OpenStackPlatformType) } - return &asset.State{ - Contents: []asset.Content{ - { - Name: "platform", - Data: []byte(OpenStackPlatformType), - }, - { - Name: "platform.json", - Data: data, - }, - }, + return &types.OpenStackPlatform{ + NetworkCIDRBlock: defaultVPCCIDR, + Region: region, + BaseImage: image, + Cloud: cloud, + ExternalNetwork: extNet, }, nil - } -func (a *Platform) libvirtPlatform() (*asset.State, error) { - platform := &types.LibvirtPlatform{ - Network: types.LibvirtNetwork{ - IfName: defaultLibvirtNetworkIfName, - IPRange: defaultLibvirtNetworkIPRange, - }, - DefaultMachinePlatform: &types.LibvirtMachinePoolPlatform{ - Image: defaultLibvirtImageURL, - }, - } - - uriPrompt := asset.UserProvided{ - AssetName: "Libvirt Connection URI", - Question: &survey.Question{ +func (a *platform) libvirtPlatform() (*types.LibvirtPlatform, error) { + uri, err := asset.GenerateUserProvidedAsset( + "Libvirt Connection URI", + &survey.Question{ Prompt: &survey.Input{ Message: "Libvirt Connection URI", Help: "The libvirt connection URI to be used. This must be accessible from the running cluster.", @@ -325,48 +282,37 @@ func (a *Platform) libvirtPlatform() (*asset.State, error) { }, Validate: survey.ComposeValidators(survey.Required, uriValidator), }, - EnvVarName: "OPENSHIFT_INSTALL_LIBVIRT_URI", - } - uri, err := uriPrompt.Generate(nil) + "OPENSHIFT_INSTALL_LIBVIRT_URI", + ) if err != nil { return nil, err } - imagePrompt := asset.UserProvided{ - AssetName: "Libvirt Image", - Question: &survey.Question{ + image, err := asset.GenerateUserProvidedAsset( + "Libvirt Image", + &survey.Question{ Prompt: &survey.Input{ Message: "Image", Help: "URI of the OS image.", - Default: platform.DefaultMachinePlatform.Image, + Default: defaultLibvirtImageURL, }, Validate: survey.ComposeValidators(survey.Required, uriValidator), }, - EnvVarName: "OPENSHIFT_INSTALL_LIBVIRT_IMAGE", - } - image, err := imagePrompt.Generate(nil) - if err != nil { - return nil, err - } - platform.URI = string(uri.Contents[0].Data) - platform.DefaultMachinePlatform.Image = string(image.Contents[0].Data) - - data, err := json.Marshal(platform) + "OPENSHIFT_INSTALL_LIBVIRT_IMAGE", + ) if err != nil { return nil, errors.Wrapf(err, "failed to Marshal %s platform", LibvirtPlatformType) } - return &asset.State{ - Contents: []asset.Content{ - { - Name: "platform-type", - Data: []byte(LibvirtPlatformType), - }, - { - Name: "platform.json", - Data: data, - }, + return &types.LibvirtPlatform{ + Network: types.LibvirtNetwork{ + IfName: defaultLibvirtNetworkIfName, + IPRange: defaultLibvirtNetworkIPRange, + }, + DefaultMachinePlatform: &types.LibvirtMachinePoolPlatform{ + Image: image, }, + URI: uri, }, nil } diff --git a/pkg/asset/installconfig/pullsecret.go b/pkg/asset/installconfig/pullsecret.go new file mode 100644 index 00000000000..de5d7edf99c --- /dev/null +++ b/pkg/asset/installconfig/pullsecret.go @@ -0,0 +1,44 @@ +package installconfig + +import ( + survey "gopkg.in/AlecAivazis/survey.v1" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/validate" +) + +type pullSecret struct { + pullSecret string +} + +var _ asset.Asset = (*pullSecret)(nil) + +// Dependencies returns no dependencies. +func (a *pullSecret) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate queries for the pull secret from the user. +func (a *pullSecret) Generate(asset.Parents) error { + s, err := asset.GenerateUserProvidedAssetForPath( + a.Name(), + &survey.Question{ + Prompt: &survey.Input{ + Message: "Pull Secret", + Help: "The container registry pull secret for this cluster.", + }, + Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { + return validate.JSON([]byte(ans.(string))) + }), + }, + "OPENSHIFT_INSTALL_PULL_SECRET", + "OPENSHIFT_INSTALL_PULL_SECRET_PATH", + ) + a.pullSecret = s + return err +} + +// Name returns the human-friendly name of the asset. +func (a *pullSecret) Name() string { + return "Pull Secret" +} diff --git a/pkg/asset/installconfig/ssh.go b/pkg/asset/installconfig/ssh.go index 13c2a31cd2b..b1011ff2f59 100644 --- a/pkg/asset/installconfig/ssh.go +++ b/pkg/asset/installconfig/ssh.go @@ -19,56 +19,59 @@ const ( none = "" ) -type sshPublicKey struct{} +type sshPublicKey struct { + key string +} + +var _ asset.Asset = (*sshPublicKey)(nil) // Dependencies returns no dependencies. func (a *sshPublicKey) Dependencies() []asset.Asset { return nil } -func readSSHKey(path string) (key []byte, err error) { - key, err = ioutil.ReadFile(path) +func readSSHKey(path string) (string, error) { + keyAsBytes, err := ioutil.ReadFile(path) if err != nil { - return key, err + return "", err } - err = validateOpenSSHPublicKey(string(key)) + key := string(keyAsBytes) + + err = validateOpenSSHPublicKey(key) if err != nil { - return key, err + return "", err } return key, nil } // Generate generates the SSH public key asset. -func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.State, err error) { +func (a *sshPublicKey) Generate(asset.Parents) error { if value, ok := os.LookupEnv("OPENSHIFT_INSTALL_SSH_PUB_KEY"); ok { if value != "" { if err := validateOpenSSHPublicKey(value); err != nil { - return nil, errors.Wrap(err, "failed to validate public key") + return errors.Wrap(err, "failed to validate public key") } } - return &asset.State{ - Contents: []asset.Content{{ - Data: []byte(value), - }}, - }, nil + a.key = value + return nil } - pubKeys := map[string][]byte{} + pubKeys := map[string]string{} if path, ok := os.LookupEnv("OPENSHIFT_INSTALL_SSH_PUB_KEY_PATH"); ok { key, err := readSSHKey(path) if err != nil { - return nil, errors.Wrap(err, "failed to read public key file") + return errors.Wrap(err, "failed to read public key file") } pubKeys[path] = key } else { - pubKeys[none] = []byte{} + pubKeys[none] = "" home := os.Getenv("HOME") if home != "" { paths, err := filepath.Glob(filepath.Join(home, ".ssh", "*.pub")) if err != nil { - return nil, errors.Wrap(err, "failed to glob for public key files") + return errors.Wrap(err, "failed to glob for public key files") } for _, path := range paths { key, err := readSSHKey(path) @@ -82,12 +85,9 @@ func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.Stat if len(pubKeys) == 1 { for _, value := range pubKeys { - return &asset.State{ - Contents: []asset.Content{{ - Data: value, - }}, - }, nil + a.key = value } + return nil } var paths []string @@ -97,7 +97,7 @@ func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.Stat sort.Strings(paths) var path string - err = survey.AskOne(&survey.Select{ + if err := survey.AskOne(&survey.Select{ Message: "SSH Public Key", Help: "The SSH public key used to access all nodes within the cluster. This is optional.", Options: paths, @@ -109,20 +109,16 @@ func (a *sshPublicKey) Generate(map[asset.Asset]*asset.State) (state *asset.Stat return fmt.Errorf("invalid path %q", choice) } return nil - }) - if err != nil { - return nil, errors.Wrap(err, "failed UserInput for SSH public key") + }); err != nil { + return errors.Wrap(err, "failed UserInput for SSH public key") } - return &asset.State{ - Contents: []asset.Content{{ - Data: pubKeys[path], - }}, - }, nil + a.key = pubKeys[path] + return nil } // Name returns the human-friendly name of the asset. -func (a *sshPublicKey) Name() string { +func (a sshPublicKey) Name() string { return "SSH Key" } diff --git a/pkg/asset/installconfig/stock.go b/pkg/asset/installconfig/stock.go deleted file mode 100644 index d7640984f69..00000000000 --- a/pkg/asset/installconfig/stock.go +++ /dev/null @@ -1,165 +0,0 @@ -package installconfig - -import ( - survey "gopkg.in/AlecAivazis/survey.v1" - - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/validate" -) - -// Stock is the stock of InstallConfig assets that can be generated. -type Stock interface { - // InstallConfig is the asset that generates install-config.yml. - InstallConfig() asset.Asset - // ClusterID is the asset that generates a UUID for the cluster. - ClusterID() asset.Asset - // EmailAddress is the asset that queries the user for the admin email address. - EmailAddress() asset.Asset - // Password is the asset that queries the user for the admin password. - Password() asset.Asset - // SSHKey is the asset that queries the user for the ssh public key in a string format. - SSHKey() asset.Asset - // BaseDomain is the asset that queries the user for the base domain to use - // for the cluster. - BaseDomain() asset.Asset - // ClusterName is the asset that queries the user for the name of the cluster. - ClusterName() asset.Asset - // PullSecret is the asset that queries the user for the pull secret. - PullSecret() asset.Asset - // Platform is the asset that queries the user for the platform on which - // to create the cluster. - Platform() asset.Asset -} - -// StockImpl implements the Stock interface for installconfig and user inputs. -type StockImpl struct { - installConfig asset.Asset - clusterID asset.Asset - emailAddress asset.Asset - password asset.Asset - sshKey asset.Asset - baseDomain asset.Asset - clusterName asset.Asset - pullSecret asset.Asset - platform asset.Asset -} - -// EstablishStock establishes the stock of assets. -func (s *StockImpl) EstablishStock() { - s.installConfig = &installConfig{ - assetStock: s, - } - s.clusterID = &clusterID{} - s.emailAddress = &asset.UserProvided{ - AssetName: "Email Address", - Question: &survey.Question{ - Prompt: &survey.Input{ - Message: "Email Address", - Help: "The email address of the cluster administrator. This will be used to log in to the console.", - }, - Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { - return validate.Email(ans.(string)) - }), - }, - EnvVarName: "OPENSHIFT_INSTALL_EMAIL_ADDRESS", - } - s.password = &asset.UserProvided{ - AssetName: "Password", - Question: &survey.Question{ - Prompt: &survey.Password{ - Message: "Password", - Help: "The password of the cluster administrator. This will be used to log in to the console.", - }, - }, - EnvVarName: "OPENSHIFT_INSTALL_PASSWORD", - } - s.baseDomain = &asset.UserProvided{ - AssetName: "Base Domain", - Question: &survey.Question{ - Prompt: &survey.Input{ - Message: "Base Domain", - Help: "The base domain of the cluster. All DNS records will be sub-domains of this base.\n\nFor AWS, this must be a previously-existing public Route 53 zone. You can check for any already in your account with:\n\n $ aws route53 list-hosted-zones --query 'HostedZones[? !(Config.PrivateZone)].Name' --output text", - }, - Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { - return validate.DomainName(ans.(string)) - }), - }, - EnvVarName: "OPENSHIFT_INSTALL_BASE_DOMAIN", - } - s.clusterName = &asset.UserProvided{ - AssetName: "Cluster Name", - Question: &survey.Question{ - Prompt: &survey.Input{ - Message: "Cluster Name", - Help: "The name of the cluster. This will be used when generating sub-domains.", - }, - Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { - return validate.ClusterName(ans.(string)) - }), - }, - EnvVarName: "OPENSHIFT_INSTALL_CLUSTER_NAME", - } - s.pullSecret = &asset.UserProvided{ - AssetName: "Pull Secret", - Question: &survey.Question{ - Prompt: &survey.Input{ - Message: "Pull Secret", - Help: "The container registry pull secret for this cluster.", - }, - Validate: survey.ComposeValidators(survey.Required, func(ans interface{}) error { - return validate.JSON([]byte(ans.(string))) - }), - }, - EnvVarName: "OPENSHIFT_INSTALL_PULL_SECRET", - PathEnvVarName: "OPENSHIFT_INSTALL_PULL_SECRET_PATH", - } - s.platform = &Platform{} - s.sshKey = &sshPublicKey{} -} - -// ClusterID is the asset that generates a UUID for the cluster. -func (s *StockImpl) ClusterID() asset.Asset { - return s.clusterID -} - -// InstallConfig is the asset that generates install-config.yml. -func (s *StockImpl) InstallConfig() asset.Asset { - return s.installConfig -} - -// EmailAddress is the asset that queries the user for the admin email address. -func (s *StockImpl) EmailAddress() asset.Asset { - return s.emailAddress -} - -// Password is the asset that queries the user for the admin password. -func (s *StockImpl) Password() asset.Asset { - return s.password -} - -// SSHKey is the asset that queries the user for the ssh public key in a string format. -func (s *StockImpl) SSHKey() asset.Asset { - return s.sshKey -} - -// BaseDomain is the asset that queries the user for the base domain to use -// for the cluster. -func (s *StockImpl) BaseDomain() asset.Asset { - return s.baseDomain -} - -// ClusterName is the asset that queries the user for the name of the cluster. -func (s *StockImpl) ClusterName() asset.Asset { - return s.clusterName -} - -// PullSecret is the asset that queries the user for the pull secret. -func (s *StockImpl) PullSecret() asset.Asset { - return s.pullSecret -} - -// Platform is the asset that queries the user for the platform on which -// to create the cluster. -func (s *StockImpl) Platform() asset.Asset { - return s.platform -} diff --git a/pkg/asset/kubeconfig/admin.go b/pkg/asset/kubeconfig/admin.go new file mode 100644 index 00000000000..2151dc6cf1f --- /dev/null +++ b/pkg/asset/kubeconfig/admin.go @@ -0,0 +1,44 @@ +package kubeconfig + +import ( + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" +) + +// Admin is the asset for the admin kubeconfig. +type Admin struct { + kubeconfig +} + +var _ asset.WritableAsset = (*Admin)(nil) + +// Dependencies returns the dependency of the kubeconfig. +func (k *Admin) Dependencies() []asset.Asset { + return []asset.Asset{ + &tls.RootCA{}, + &tls.AdminCertKey{}, + &installconfig.InstallConfig{}, + } +} + +// Generate generates the kubeconfig. +func (k *Admin) Generate(parents asset.Parents) error { + rootCA := &tls.RootCA{} + adminCertKey := &tls.AdminCertKey{} + installConfig := &installconfig.InstallConfig{} + parents.Get(rootCA, adminCertKey, installConfig) + + return k.kubeconfig.generate( + rootCA, + adminCertKey, + installConfig.Config, + "admin", + "kubeconfig", + ) +} + +// Name returns the human-friendly name of the asset. +func (k *Admin) Name() string { + return "Kubeconfig Admin" +} diff --git a/pkg/asset/kubeconfig/kubeconfig.go b/pkg/asset/kubeconfig/kubeconfig.go index 8237b9b80f2..3d56dd7687d 100644 --- a/pkg/asset/kubeconfig/kubeconfig.go +++ b/pkg/asset/kubeconfig/kubeconfig.go @@ -9,99 +9,68 @@ import ( clientcmd "k8s.io/client-go/tools/clientcmd/api/v1" "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/tls" + "github.com/openshift/installer/pkg/types" ) -const ( - // KubeconfigUserNameAdmin is the user name of the admin kubeconfig. - KubeconfigUserNameAdmin = "admin" - // KubeconfigUserNameKubelet is the user name of the kubelet kubeconfig. - KubeconfigUserNameKubelet = "kubelet" -) - -// Kubeconfig implements the asset.Asset interface that generates -// the admin kubeconfig and kubelet kubeconfig. -type Kubeconfig struct { - userName string // admin or kubelet. - rootCA asset.Asset - certKey asset.Asset - installConfig asset.Asset -} - -var _ asset.Asset = (*Kubeconfig)(nil) - -// Dependencies returns the dependency of the kubeconfig. -func (k *Kubeconfig) Dependencies() []asset.Asset { - return []asset.Asset{ - k.rootCA, - k.certKey, - k.installConfig, - } +type kubeconfig struct { + config *clientcmd.Config + file *asset.File } -// Generate generates the kubeconfig. -func (k *Kubeconfig) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { - installConfig, err := installconfig.GetInstallConfig(k.installConfig, parents) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } - - kubeconfig := clientcmd.Config{ +// generate generates the kubeconfig. +func (k *kubeconfig) generate( + rootCA tls.CertKeyInterface, + clientCertKey tls.CertKeyInterface, + installConfig *types.InstallConfig, + userName string, + filename string, +) error { + k.config = &clientcmd.Config{ Clusters: []clientcmd.NamedCluster{ { Name: installConfig.ObjectMeta.Name, Cluster: clientcmd.Cluster{ Server: fmt.Sprintf("https://%s-api.%s:6443", installConfig.ObjectMeta.Name, installConfig.BaseDomain), - CertificateAuthorityData: parents[k.rootCA].Contents[tls.CertIndex].Data, + CertificateAuthorityData: []byte(rootCA.Cert()), }, }, }, AuthInfos: []clientcmd.NamedAuthInfo{ { - Name: k.userName, + Name: userName, AuthInfo: clientcmd.AuthInfo{ - ClientCertificateData: parents[k.certKey].Contents[tls.CertIndex].Data, - ClientKeyData: parents[k.certKey].Contents[tls.KeyIndex].Data, + ClientCertificateData: []byte(clientCertKey.Cert()), + ClientKeyData: []byte(clientCertKey.Key()), }, }, }, Contexts: []clientcmd.NamedContext{ { - Name: k.userName, + Name: userName, Context: clientcmd.Context{ Cluster: installConfig.ObjectMeta.Name, - AuthInfo: k.userName, + AuthInfo: userName, }, }, }, - CurrentContext: k.userName, + CurrentContext: userName, } - data, err := yaml.Marshal(kubeconfig) + data, err := yaml.Marshal(k.config) if err != nil { - return nil, errors.Wrap(err, "failed to Marshal kubeconfig") + return errors.Wrap(err, "failed to Marshal kubeconfig") } - var kubeconfigName string - switch k.userName { - case KubeconfigUserNameAdmin: - kubeconfigName = "kubeconfig" - default: - kubeconfigName = fmt.Sprintf("kubeconfig-%s", k.userName) + k.file = &asset.File{ + Filename: filepath.Join("auth", filename), + Data: data, } - return &asset.State{ - Contents: []asset.Content{ - { - Name: filepath.Join("auth", kubeconfigName), - Data: data, - }, - }, - }, nil + return nil } -// Name returns the human-friendly name of the asset. -func (k *Kubeconfig) Name() string { - return "Kubeconfig" +// Files returns the files generated by the asset. +func (k *kubeconfig) Files() []*asset.File { + return []*asset.File{k.file} } diff --git a/pkg/asset/kubeconfig/kubeconfig_test.go b/pkg/asset/kubeconfig/kubeconfig_test.go index 0c9a9ebd285..f40bbf35803 100644 --- a/pkg/asset/kubeconfig/kubeconfig_test.go +++ b/pkg/asset/kubeconfig/kubeconfig_test.go @@ -3,99 +3,62 @@ package kubeconfig import ( "testing" - "github.com/openshift/installer/pkg/asset" "github.com/stretchr/testify/assert" -) -type fakeAsset int + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -var _ asset.Asset = fakeAsset(0) + "github.com/openshift/installer/pkg/asset/tls" + "github.com/openshift/installer/pkg/types" +) -func (f fakeAsset) Dependencies() []asset.Asset { - return nil +type testCertKey struct { + key string + cert string } -func (f fakeAsset) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { - return nil, nil +func (t *testCertKey) Key() []byte { + return []byte(t.key) } -func (f fakeAsset) Name() string { - return "Fake Asset" +func (t *testCertKey) Cert() []byte { + return []byte(t.cert) } func TestKubeconfigGenerate(t *testing.T) { - rootCA := fakeAsset(0) - adminCertKey := fakeAsset(1) - kubeletCertKey := fakeAsset(2) - installConfig := fakeAsset(3) - - rootCAState := &asset.State{ - Contents: []asset.Content{ - { - Name: "root-ca.key", - Data: []byte("THIS IS ROOT CA KEY DATA"), - }, - { - Name: "root-ca.crt", - Data: []byte("THIS IS ROOT CA CERT DATA"), - }, - }, + rootCA := &testCertKey{ + key: "THIS IS ROOT CA KEY DATA", + cert: "THIS IS ROOT CA CERT DATA", } - adminCertState := &asset.State{ - Contents: []asset.Content{ - { - Name: "admin.key", - Data: []byte("THIS IS ADMIN KEY DATA"), - }, - { - Name: "admin.crt", - Data: []byte("THIS IS ADMIN CERT DATA"), - }, - }, + adminCert := &testCertKey{ + key: "THIS IS ADMIN KEY DATA", + cert: "THIS IS ADMIN CERT DATA", } - kubeletCertState := &asset.State{ - Contents: []asset.Content{ - { - Name: "kubelet.key", - Data: []byte("THIS IS KUBELET KEY DATA"), - }, - { - Name: "kubelet.crt", - Data: []byte("THIS IS KUBELET CERT DATA"), - }, - }, + kubeletCert := &testCertKey{ + key: "THIS IS KUBELET KEY DATA", + cert: "THIS IS KUBELET CERT DATA", } - installConfigState := &asset.State{ - Contents: []asset.Content{ - { - Name: "installconfig.yaml", - Data: []byte(`baseDomain: test.example.com -metadata: - name: test-cluster-name -`), - }, + installConfig := &types.InstallConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-name", }, + BaseDomain: "test.example.com", } tests := []struct { name string userName string - certKey asset.Asset - parents map[asset.Asset]*asset.State + filename string + clientCert tls.CertKeyInterface expectedData []byte }{ { - name: "admin kubeconfig", - userName: "admin", - certKey: adminCertKey, - parents: map[asset.Asset]*asset.State{ - rootCA: rootCAState, - adminCertKey: adminCertState, - installConfig: installConfigState, - }, + name: "admin kubeconfig", + userName: "admin", + filename: "kubeconfig", + clientCert: adminCert, expectedData: []byte(`clusters: - cluster: certificate-authority-data: VEhJUyBJUyBST09UIENBIENFUlQgREFUQQ== @@ -116,14 +79,10 @@ users: `), }, { - name: "kubelet kubeconfig", - userName: "kubelet", - certKey: kubeletCertKey, - parents: map[asset.Asset]*asset.State{ - rootCA: rootCAState, - kubeletCertKey: kubeletCertState, - installConfig: installConfigState, - }, + name: "kubelet kubeconfig", + userName: "kubelet", + filename: "kubeconfig-kubelet", + clientCert: kubeletCert, expectedData: []byte(`clusters: - cluster: certificate-authority-data: VEhJUyBJUyBST09UIENBIENFUlQgREFUQQ== @@ -147,17 +106,13 @@ users: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - kubeconfig := &Kubeconfig{ - userName: tt.userName, - rootCA: rootCA, - certKey: tt.certKey, - installConfig: installConfig, - } - st, err := kubeconfig.Generate(tt.parents) - assert.Nil(t, err) - if assert.NotNil(t, st) { - assert.Equal(t, tt.expectedData, st.Contents[0].Data, "unexpected data in kubeconfig") - } + kc := &kubeconfig{} + err := kc.generate(rootCA, tt.clientCert, installConfig, tt.userName, tt.filename) + assert.NoError(t, err, "unexpected error generating config") + actualFiles := kc.Files() + assert.Equal(t, 1, len(actualFiles), "unexpected number of files generated") + assert.Equal(t, "auth/"+tt.filename, actualFiles[0].Filename, "unexpected file name generated") + assert.Equal(t, tt.expectedData, actualFiles[0].Data, "unexpected config") }) } diff --git a/pkg/asset/kubeconfig/kubelet.go b/pkg/asset/kubeconfig/kubelet.go new file mode 100644 index 00000000000..2337312b266 --- /dev/null +++ b/pkg/asset/kubeconfig/kubelet.go @@ -0,0 +1,44 @@ +package kubeconfig + +import ( + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" +) + +// Kubelet is the asset for the kubelet kubeconfig. +type Kubelet struct { + kubeconfig +} + +var _ asset.WritableAsset = (*Kubelet)(nil) + +// Dependencies returns the dependency of the kubeconfig. +func (k *Kubelet) Dependencies() []asset.Asset { + return []asset.Asset{ + &tls.RootCA{}, + &tls.KubeletCertKey{}, + &installconfig.InstallConfig{}, + } +} + +// Generate generates the kubeconfig. +func (k *Kubelet) Generate(parents asset.Parents) error { + rootCA := &tls.RootCA{} + kubeletCertKey := &tls.KubeletCertKey{} + installConfig := &installconfig.InstallConfig{} + parents.Get(rootCA, kubeletCertKey, installConfig) + + return k.kubeconfig.generate( + rootCA, + kubeletCertKey, + installConfig.Config, + "kubelet", + "kubeconfig-kubelet", + ) +} + +// Name returns the human-friendly name of the asset. +func (k *Kubelet) Name() string { + return "Kubeconfig Kubelet" +} diff --git a/pkg/asset/kubeconfig/stock.go b/pkg/asset/kubeconfig/stock.go deleted file mode 100644 index 3d691f4bab8..00000000000 --- a/pkg/asset/kubeconfig/stock.go +++ /dev/null @@ -1,46 +0,0 @@ -package kubeconfig - -import ( - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/asset/tls" -) - -// Stock is the stock of the kubeconfig assets that can be generated. -type Stock interface { - // KubeconfigAdmin is the asset that generates the admin kubeconfig. - KubeconfigAdmin() asset.Asset - // KubeconfigKubelet is the asset that generates the kubelet kubeconfig. - KubeconfigKubelet() asset.Asset -} - -// StockImpl implements the Stock interface for kubeconfig assets. -type StockImpl struct { - kubeconfig asset.Asset - kubeconfigKubelet asset.Asset -} - -var _ Stock = (*StockImpl)(nil) - -// EstablishStock establishes the stock of assets. -func (s *StockImpl) EstablishStock(installConfigStock installconfig.Stock, tlsStock tls.Stock) { - s.kubeconfig = &Kubeconfig{ - rootCA: tlsStock.RootCA(), - certKey: tlsStock.AdminCertKey(), - installConfig: installConfigStock.InstallConfig(), - userName: KubeconfigUserNameAdmin, - } - - s.kubeconfigKubelet = &Kubeconfig{ - rootCA: tlsStock.RootCA(), - certKey: tlsStock.KubeletCertKey(), - installConfig: installConfigStock.InstallConfig(), - userName: KubeconfigUserNameKubelet, - } -} - -// KubeconfigAdmin is the asset that generates the admin kubeconfig. -func (s *StockImpl) KubeconfigAdmin() asset.Asset { return s.kubeconfig } - -// KubeconfigKubelet is the asset that generates the kubelet kubeconfig. -func (s *StockImpl) KubeconfigKubelet() asset.Asset { return s.kubeconfigKubelet } diff --git a/pkg/asset/manifests/kube-addon-operator.go b/pkg/asset/manifests/kube-addon-operator.go index 9b593236cf5..8b4ceefc869 100644 --- a/pkg/asset/manifests/kube-addon-operator.go +++ b/pkg/asset/manifests/kube-addon-operator.go @@ -1,26 +1,23 @@ package manifests import ( - "fmt" - "github.com/ghodss/yaml" "github.com/pkg/errors" kubeaddon "github.com/coreos/tectonic-config/config/kube-addon" "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" ) // kubeAddonOperator generates the network-operator-*.yml files type kubeAddonOperator struct { - installConfigAsset asset.Asset - installConfig *types.InstallConfig + config *kubeaddon.OperatorConfig + file *asset.File } -var _ asset.Asset = (*kubeAddonOperator)(nil) +var _ asset.WritableAsset = (*kubeAddonOperator)(nil) // Name returns a human friendly name for the operator func (kao *kubeAddonOperator) Name() string { @@ -31,48 +28,41 @@ func (kao *kubeAddonOperator) Name() string { // kubeAddonOperator asset. func (kao *kubeAddonOperator) Dependencies() []asset.Asset { return []asset.Asset{ - kao.installConfigAsset, + &installconfig.InstallConfig{}, } } // Generate generates the network-operator-config.yml and network-operator-manifest.yml files -func (kao *kubeAddonOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - ic, err := installconfig.GetInstallConfig(kao.installConfigAsset, dependencies) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") +func (kao *kubeAddonOperator) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + dependencies.Get(installConfig) + + kao.config = &kubeaddon.OperatorConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kubeaddon.APIVersion, + Kind: kubeaddon.Kind, + }, + CloudProvider: tectonicCloudProvider(installConfig.Config.Platform), + RegistryHTTPSecret: rand.String(16), + ClusterConfig: kubeaddon.ClusterConfig{ + APIServerURL: getAPIServerURL(installConfig.Config), + }, } - kao.installConfig = ic - // installconfig is ready, we can create the addon config from it now - addonConfig, err := kao.addonConfig() + data, err := yaml.Marshal(kao.config) if err != nil { - return nil, errors.Wrapf(err, "failed to create %s config from InstallConfig", kao.Name()) + return errors.Wrapf(err, "failed to create %s config from InstallConfig", kao.Name()) } - state := &asset.State{ - Contents: []asset.Content{ - { - Name: "kube-addon-operator-config.yml", - Data: addonConfig, - }, - }, + kao.file = &asset.File{ + Filename: "kube-addon-operator-config.yml", + Data: data, } - return state, nil -} -func (kao *kubeAddonOperator) addonConfig() ([]byte, error) { - addonConfig := kubeaddon.OperatorConfig{ - TypeMeta: metav1.TypeMeta{ - APIVersion: kubeaddon.APIVersion, - Kind: kubeaddon.Kind, - }, - } - addonConfig.CloudProvider = tectonicCloudProvider(kao.installConfig.Platform) - addonConfig.ClusterConfig.APIServerURL = kao.getAPIServerURL() - addonConfig.RegistryHTTPSecret = rand.String(16) - return yaml.Marshal(addonConfig) + return nil } -func (kao *kubeAddonOperator) getAPIServerURL() string { - return fmt.Sprintf("https://%s-api.%s:6443", kao.installConfig.ObjectMeta.Name, kao.installConfig.BaseDomain) +// Files returns the files generated by the asset. +func (kao *kubeAddonOperator) Files() []*asset.File { + return []*asset.File{kao.file} } diff --git a/pkg/asset/manifests/kube-core-operator.go b/pkg/asset/manifests/kube-core-operator.go index 2b7e5726a4e..ef03c396689 100644 --- a/pkg/asset/manifests/kube-core-operator.go +++ b/pkg/asset/manifests/kube-core-operator.go @@ -22,101 +22,100 @@ const ( networkConfigAdvertiseAddress = "0.0.0.0" ) -// kubeCoreOperator generates the kube-core-operator.yaml files -type kubeCoreOperator struct { - installConfigAsset asset.Asset - installConfig *types.InstallConfig +// KubeCoreOperator generates the kube-core-operator.yaml files +type KubeCoreOperator struct { + config *kubecore.OperatorConfig + file *asset.File } -var _ asset.Asset = (*kubeCoreOperator)(nil) +var _ asset.WritableAsset = (*KubeCoreOperator)(nil) // Name returns a human friendly name for the operator -func (kco *kubeCoreOperator) Name() string { +func (kco *KubeCoreOperator) Name() string { return "Kube Core Operator" } // Dependencies returns all of the dependencies directly needed by an -// kubeCoreOperator asset. -func (kco *kubeCoreOperator) Dependencies() []asset.Asset { +// KubeCoreOperator asset. +func (kco *KubeCoreOperator) Dependencies() []asset.Asset { return []asset.Asset{ - kco.installConfigAsset, + &installconfig.InstallConfig{}, } } // Generate generates the kube-core-operator-config.yml files -func (kco *kubeCoreOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - ic, err := installconfig.GetInstallConfig(kco.installConfigAsset, dependencies) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } - kco.installConfig = ic +func (kco *KubeCoreOperator) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + dependencies.Get(installConfig) - // installconfig is ready, we can create the core config from it now - coreConfig, err := kco.coreConfig() + clusterIP, err := cidr.Host(&installConfig.Config.Networking.ServiceCIDR.IPNet, 10) if err != nil { - return nil, errors.Wrapf(err, "failed to create %s config from InstallConfig", kco.Name()) - } - - state := &asset.State{ - Contents: []asset.Content{ - { - Name: "kco-config.yaml", - Data: coreConfig, - }, - }, + return errors.Wrapf(err, "failed to create %s config from InstallConfig", kco.Name()) } - return state, nil -} -func (kco *kubeCoreOperator) coreConfig() ([]byte, error) { - coreConfig := kubecore.OperatorConfig{ + kco.config = &kubecore.OperatorConfig{ TypeMeta: metav1.TypeMeta{ APIVersion: kubecore.APIVersion, Kind: kubecore.Kind, }, + ClusterConfig: kubecore.ClusterConfig{ + APIServerURL: getAPIServerURL(installConfig.Config), + }, + AuthConfig: kubecore.AuthConfig{ + OIDCClientID: authConfigOIDCClientID, + OIDCIssuerURL: getOicdIssuerURL(installConfig.Config), + OIDCGroupsClaim: authConfigOIDCGroupsClaim, + OIDCUsernameClaim: authConfigOIDCUsernameClaim, + }, + DNSConfig: kubecore.DNSConfig{ + ClusterIP: clusterIP.String(), + }, + CloudProviderConfig: kubecore.CloudProviderConfig{ + CloudConfigPath: "", + CloudProviderProfile: k8sCloudProvider(installConfig.Config.Platform), + }, + RoutingConfig: kubecore.RoutingConfig{ + Subdomain: getBaseAddress(installConfig.Config), + }, + NetworkConfig: kubecore.NetworkConfig{ + ClusterCIDR: installConfig.Config.Networking.PodCIDR.String(), + ServiceCIDR: installConfig.Config.Networking.ServiceCIDR.String(), + AdvertiseAddress: networkConfigAdvertiseAddress, + EtcdServers: strings.Join(getEtcdServersURLs(installConfig.Config), ","), + }, } - coreConfig.ClusterConfig.APIServerURL = kco.getAPIServerURL() - coreConfig.AuthConfig.OIDCClientID = authConfigOIDCClientID - coreConfig.AuthConfig.OIDCIssuerURL = kco.getOicdIssuerURL() - coreConfig.AuthConfig.OIDCGroupsClaim = authConfigOIDCGroupsClaim - coreConfig.AuthConfig.OIDCUsernameClaim = authConfigOIDCUsernameClaim - ip, err := cidr.Host(&kco.installConfig.Networking.ServiceCIDR.IPNet, 10) + data, err := yaml.Marshal(kco.config) if err != nil { - return nil, err + return errors.Wrapf(err, "failed to create %s config from InstallConfig", kco.Name()) + } + kco.file = &asset.File{ + Filename: "kco-config.yaml", + Data: data, } - coreConfig.DNSConfig.ClusterIP = ip.String() - - coreConfig.CloudProviderConfig.CloudConfigPath = "" - coreConfig.CloudProviderConfig.CloudProviderProfile = k8sCloudProvider(kco.installConfig.Platform) - - coreConfig.RoutingConfig.Subdomain = kco.getBaseAddress() - coreConfig.NetworkConfig.ClusterCIDR = kco.installConfig.Networking.PodCIDR.String() - coreConfig.NetworkConfig.ServiceCIDR = kco.installConfig.Networking.ServiceCIDR.String() - coreConfig.NetworkConfig.AdvertiseAddress = networkConfigAdvertiseAddress - coreConfig.NetworkConfig.EtcdServers = strings.Join(kco.getEtcdServersURLs(), ",") - return yaml.Marshal(coreConfig) + return nil } -func (kco *kubeCoreOperator) getAPIServerURL() string { - return fmt.Sprintf("https://%s-api.%s:6443", kco.installConfig.ObjectMeta.Name, kco.installConfig.BaseDomain) +// Files returns the files generated by the asset. +func (kco *KubeCoreOperator) Files() []*asset.File { + return []*asset.File{kco.file} } -func (kco *kubeCoreOperator) getEtcdServersURLs() []string { +func getEtcdServersURLs(ic *types.InstallConfig) []string { var urls []string - for i := 0; i < kco.installConfig.MasterCount(); i++ { - urls = append(urls, fmt.Sprintf("https://%s-etcd-%d.%s:2379", kco.installConfig.ObjectMeta.Name, i, kco.installConfig.BaseDomain)) + for i := 0; i < ic.MasterCount(); i++ { + urls = append(urls, fmt.Sprintf("https://%s-etcd-%d.%s:2379", ic.ObjectMeta.Name, i, ic.BaseDomain)) } return urls } -func (kco *kubeCoreOperator) getOicdIssuerURL() string { - return fmt.Sprintf("https://%s.%s/identity", kco.installConfig.ObjectMeta.Name, kco.installConfig.BaseDomain) +func getOicdIssuerURL(ic *types.InstallConfig) string { + return fmt.Sprintf("https://%s.%s/identity", ic.ObjectMeta.Name, ic.BaseDomain) } -func (kco *kubeCoreOperator) getBaseAddress() string { - return fmt.Sprintf("%s.%s", kco.installConfig.ObjectMeta.Name, kco.installConfig.BaseDomain) +func getBaseAddress(ic *types.InstallConfig) string { + return fmt.Sprintf("%s.%s", ic.ObjectMeta.Name, ic.BaseDomain) } // Converts a platform to the cloudProvider that k8s understands diff --git a/pkg/asset/manifests/machine-api-operator.go b/pkg/asset/manifests/machine-api-operator.go index c17628af189..cd34963ed6d 100644 --- a/pkg/asset/manifests/machine-api-operator.go +++ b/pkg/asset/manifests/machine-api-operator.go @@ -3,12 +3,14 @@ package manifests import ( "context" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" "github.com/openshift/installer/pkg/rhcos" - "github.com/openshift/installer/pkg/types" - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -19,12 +21,11 @@ const ( // machineAPIOperator generates the network-operator-*.yml files type machineAPIOperator struct { - installConfigAsset asset.Asset - installConfig *types.InstallConfig - aggregatorCA asset.Asset + config *maoOperatorConfig + file *asset.File } -var _ asset.Asset = (*machineAPIOperator)(nil) +var _ asset.WritableAsset = (*machineAPIOperator)(nil) // maoOperatorConfig contains configuration for mao managed stack // TODO(enxebre): move up to github.com/coreos/tectonic-config (to install-config? /rchopra) @@ -71,84 +72,76 @@ func (mao *machineAPIOperator) Name() string { // machineAPIOperator asset. func (mao *machineAPIOperator) Dependencies() []asset.Asset { return []asset.Asset{ - mao.installConfigAsset, - mao.aggregatorCA, + &installconfig.InstallConfig{}, + &tls.AggregatorCA{}, } } // Generate generates the network-operator-config.yml and network-operator-manifest.yml files -func (mao *machineAPIOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - ic, err := installconfig.GetInstallConfig(mao.installConfigAsset, dependencies) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } - mao.installConfig = ic - - // installconfig is ready, we can create the mao config from it now - maoConfig, err := mao.maoConfig(dependencies) - if err != nil { - return nil, errors.Wrapf(err, "failed to create %s config from InstallConfig", mao.Name()) - } - - state := &asset.State{ - Contents: []asset.Content{ - { - Name: "machine-api-operator-config.yml", - Data: []byte(maoConfig), - }, - }, - } - return state, nil -} +func (mao *machineAPIOperator) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + aggregatorCA := &tls.AggregatorCA{} + dependencies.Get(installConfig, aggregatorCA) -func (mao *machineAPIOperator) maoConfig(dependencies map[asset.Asset]*asset.State) (string, error) { - cfg := maoOperatorConfig{ + mao.config = &maoOperatorConfig{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "machineAPIOperatorConfig", }, - TargetNamespace: maoTargetNamespace, + APIServiceCA: string(aggregatorCA.Cert()), + Provider: tectonicCloudProvider(installConfig.Config.Platform), } - ca := dependencies[mao.aggregatorCA].Contents[certIndex].Data - cfg.APIServiceCA = string(ca) - cfg.Provider = tectonicCloudProvider(mao.installConfig.Platform) - - if mao.installConfig.Platform.AWS != nil { + switch { + case installConfig.Config.Platform.AWS != nil: var ami string - ami, err := rhcos.AMI(context.TODO(), DefaultChannel, mao.installConfig.Platform.AWS.Region) + ami, err := rhcos.AMI(context.TODO(), DefaultChannel, installConfig.Config.Platform.AWS.Region) if err != nil { - return "", errors.Wrapf(err, "failed to get AMI for %s config", mao.Name()) + return errors.Wrapf(err, "failed to get AMI for %s config", mao.Name()) } - cfg.AWS = &awsConfig{ - ClusterName: mao.installConfig.ObjectMeta.Name, - ClusterID: mao.installConfig.ClusterID, - Region: mao.installConfig.Platform.AWS.Region, + mao.config.AWS = &awsConfig{ + ClusterName: installConfig.Config.ObjectMeta.Name, + ClusterID: installConfig.Config.ClusterID, + Region: installConfig.Config.Platform.AWS.Region, AvailabilityZone: "", Image: ami, - Replicas: int(*mao.installConfig.Machines[1].Replicas), + Replicas: int(*installConfig.Config.Machines[1].Replicas), } - } else if mao.installConfig.Platform.Libvirt != nil { - cfg.Libvirt = &libvirtConfig{ - ClusterName: mao.installConfig.ObjectMeta.Name, - URI: mao.installConfig.Platform.Libvirt.URI, - NetworkName: mao.installConfig.Platform.Libvirt.Network.Name, - IPRange: mao.installConfig.Platform.Libvirt.Network.IPRange, - Replicas: int(*mao.installConfig.Machines[1].Replicas), + case installConfig.Config.Platform.Libvirt != nil: + mao.config.Libvirt = &libvirtConfig{ + ClusterName: installConfig.Config.ObjectMeta.Name, + URI: installConfig.Config.Platform.Libvirt.URI, + NetworkName: installConfig.Config.Platform.Libvirt.Network.Name, + IPRange: installConfig.Config.Platform.Libvirt.Network.IPRange, + Replicas: int(*installConfig.Config.Machines[1].Replicas), } - } else if mao.installConfig.Platform.OpenStack != nil { - cfg.OpenStack = &openstackConfig{ - ClusterName: mao.installConfig.ObjectMeta.Name, - ClusterID: mao.installConfig.ClusterID, - Region: mao.installConfig.Platform.OpenStack.Region, - Replicas: int(*mao.installConfig.Machines[1].Replicas), + case installConfig.Config.Platform.OpenStack != nil: + mao.config.OpenStack = &openstackConfig{ + ClusterName: installConfig.Config.ObjectMeta.Name, + ClusterID: installConfig.Config.ClusterID, + Region: installConfig.Config.Platform.OpenStack.Region, + Replicas: int(*installConfig.Config.Machines[1].Replicas), } - } else { - return "", errors.Errorf("unknown provider for machine-api-operator") + default: + return errors.Errorf("unknown provider for machine-api-operator") } - return marshalYAML(cfg) + data, err := yaml.Marshal(mao.config) + if err != nil { + return errors.Wrapf(err, "failed to marshal %s config", mao.Name()) + } + mao.file = &asset.File{ + Filename: "machine-api-operator-config.yml", + Data: data, + } + + return nil +} + +// Files returns the files generated by the asset. +func (mao *machineAPIOperator) Files() []*asset.File { + return []*asset.File{mao.file} } diff --git a/pkg/asset/manifests/network-operator.go b/pkg/asset/manifests/network-operator.go index e0297cf99be..acea3c1204d 100644 --- a/pkg/asset/manifests/network-operator.go +++ b/pkg/asset/manifests/network-operator.go @@ -6,7 +6,6 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/types" tectonicnetwork "github.com/coreos/tectonic-config/config/tectonic-network" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,11 +17,11 @@ const ( // networkOperator generates the network-operator-*.yml files type networkOperator struct { - installConfigAsset asset.Asset - installConfig *types.InstallConfig + config *tectonicnetwork.OperatorConfig + files []*asset.File } -var _ asset.Asset = (*networkOperator)(nil) +var _ asset.WritableAsset = (*networkOperator)(nil) // Name returns a human friendly name for the operator func (no *networkOperator) Name() string { @@ -33,58 +32,46 @@ func (no *networkOperator) Name() string { // networkOperator asset. func (no *networkOperator) Dependencies() []asset.Asset { return []asset.Asset{ - no.installConfigAsset, + &installconfig.InstallConfig{}, } } // Generate generates the network-operator-config.yml and network-operator-manifest.yml files -func (no *networkOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - ic, err := installconfig.GetInstallConfig(no.installConfigAsset, dependencies) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } - no.installConfig = ic +func (no *networkOperator) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + dependencies.Get(installConfig) - // installconfig is ready, we can create the core config from it now - netConfig, err := no.netConfig() - if err != nil { - return nil, errors.Wrapf(err, "failed to create %s config from InstallConfig", no.Name()) + no.config = &tectonicnetwork.OperatorConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: tectonicnetwork.APIVersion, + Kind: tectonicnetwork.Kind, + }, + PodCIDR: installConfig.Config.Networking.PodCIDR.String(), + CalicoConfig: tectonicnetwork.CalicoConfig{ + MTU: defaultMTU, + }, + NetworkProfile: tectonicnetwork.NetworkType(installConfig.Config.Networking.Type), } - netManifest, err := no.manifest() + configData, err := yaml.Marshal(no.config) if err != nil { - return nil, errors.Wrapf(err, "failed to create %s manifests from InstallConfig", no.Name()) + return errors.Wrapf(err, "failed to create %s manifests from InstallConfig", no.Name()) } - state := &asset.State{ - Contents: []asset.Content{ - { - Name: "network-operator-config.yml", - Data: netConfig, - }, - { - Name: "network-operator-manifests.yml", - Data: netManifest, - }, + no.files = []*asset.File{ + { + Filename: "network-operator-config.yml", + Data: configData, }, - } - return state, nil -} - -func (no *networkOperator) netConfig() ([]byte, error) { - networkConfig := tectonicnetwork.OperatorConfig{ - TypeMeta: metav1.TypeMeta{ - APIVersion: tectonicnetwork.APIVersion, - Kind: tectonicnetwork.Kind, + { + Filename: "network-operator-manifests.yml", + Data: []byte{}, }, } - networkConfig.PodCIDR = no.installConfig.Networking.PodCIDR.String() - networkConfig.CalicoConfig.MTU = defaultMTU - networkConfig.NetworkProfile = tectonicnetwork.NetworkType(no.installConfig.Networking.Type) - - return yaml.Marshal(networkConfig) + return nil } -func (no *networkOperator) manifest() ([]byte, error) { - return []byte(""), nil +// Files returns the files generated by the asset. +func (no *networkOperator) Files() []*asset.File { + return no.files } diff --git a/pkg/asset/manifests/operators.go b/pkg/asset/manifests/operators.go index 9882a4f5cf5..c16832c1a4e 100644 --- a/pkg/asset/manifests/operators.go +++ b/pkg/asset/manifests/operators.go @@ -7,158 +7,180 @@ import ( "path/filepath" "text/template" + "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/ignition/machine" "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/kubeconfig" "github.com/openshift/installer/pkg/asset/manifests/content/bootkube" + "github.com/openshift/installer/pkg/asset/tls" ) const ( - keyIndex = 0 - certIndex = 1 manifestDir = "manifests" ) -// manifests generates the dependent operator config.yaml files -type manifests struct { - assetStock Stock - installConfig asset.Asset - rootCA asset.Asset - etcdCA asset.Asset - ingressCertKey asset.Asset - kubeCA asset.Asset - aggregatorCA asset.Asset - serviceServingCA asset.Asset - clusterAPIServerCertKey asset.Asset - etcdClientCertKey asset.Asset - apiServerCertKey asset.Asset - openshiftAPIServerCertKey asset.Asset - apiServerProxyCertKey asset.Asset - kubeletCertKey asset.Asset - mcsCertKey asset.Asset - serviceAccountKeyPair asset.Asset - kubeconfig asset.Asset - workerIgnition asset.Asset +// Manifests generates the dependent operator config.yaml files +type Manifests struct { + kubeSysConfig *configurationObject + tectonicConfig *configurationObject + files []*asset.File } -var _ asset.Asset = (*manifests)(nil) +var _ asset.WritableAsset = (*Manifests)(nil) type genericData map[string]string // Name returns a human friendly name for the operator -func (m *manifests) Name() string { +func (m *Manifests) Name() string { return "Common Manifests" } -// Dependencies returns all of the dependencies directly needed by an -// manifests asset. -func (m *manifests) Dependencies() []asset.Asset { +// Dependencies returns all of the dependencies directly needed by a +// Manifests asset. +func (m *Manifests) Dependencies() []asset.Asset { return []asset.Asset{ - m.installConfig, - m.assetStock.KubeCoreOperator(), - m.assetStock.NetworkOperator(), - m.assetStock.KubeAddonOperator(), - m.assetStock.Mao(), - m.assetStock.Tectonic(), - m.rootCA, - m.etcdCA, - m.ingressCertKey, - m.kubeCA, - m.aggregatorCA, - m.serviceServingCA, - m.clusterAPIServerCertKey, - m.etcdClientCertKey, - m.apiServerCertKey, - m.openshiftAPIServerCertKey, - m.apiServerProxyCertKey, - m.mcsCertKey, - m.kubeletCertKey, - m.serviceAccountKeyPair, - m.kubeconfig, - m.workerIgnition, + &installconfig.InstallConfig{}, + &KubeCoreOperator{}, + &networkOperator{}, + &kubeAddonOperator{}, + &machineAPIOperator{}, + &Tectonic{}, + &tls.RootCA{}, + &tls.EtcdCA{}, + &tls.IngressCertKey{}, + &tls.KubeCA{}, + &tls.AggregatorCA{}, + &tls.ServiceServingCA{}, + &tls.ClusterAPIServerCertKey{}, + &tls.EtcdClientCertKey{}, + &tls.APIServerCertKey{}, + &tls.OpenshiftAPIServerCertKey{}, + &tls.APIServerProxyCertKey{}, + &tls.MCSCertKey{}, + &tls.KubeletCertKey{}, + &tls.ServiceAccountKeyPair{}, + &kubeconfig.Admin{}, + &machine.Worker{}, } } // Generate generates the respective operator config.yml files -func (m *manifests) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - //cvo := dependencies[m.assetStock.ClusterVersionOperator()].Contents[0] - kco := dependencies[m.assetStock.KubeCoreOperator()].Contents[0] - no := dependencies[m.assetStock.NetworkOperator()].Contents[0] - addon := dependencies[m.assetStock.KubeAddonOperator()].Contents[0] - mao := dependencies[m.assetStock.Mao()].Contents[0] - installConfig := dependencies[m.installConfig].Contents[0] +func (m *Manifests) Generate(dependencies asset.Parents) error { + kco := &KubeCoreOperator{} + no := &networkOperator{} + addon := &kubeAddonOperator{} + mao := &machineAPIOperator{} + installConfig := &installconfig.InstallConfig{} + dependencies.Get(kco, no, addon, mao, installConfig) // kco+no+mao go to kube-system config map - kubeSys, err := configMap("kube-system", "cluster-config-v1", genericData{ - "kco-config": string(kco.Data), - "network-config": string(no.Data), - "install-config": string(installConfig.Data), - "mao-config": string(mao.Data), + m.kubeSysConfig = configMap("kube-system", "cluster-config-v1", genericData{ + "kco-config": string(kco.Files()[0].Data), + "network-config": string(no.Files()[0].Data), + "install-config": string(installConfig.Files()[0].Data), + "mao-config": string(mao.Files()[0].Data), }) + kubeSysConfigData, err := yaml.Marshal(m.kubeSysConfig) if err != nil { - return nil, errors.Wrap(err, "failed to create kube-system/cluster-config-v1 configmap") + return errors.Wrap(err, "failed to create kube-system/cluster-config-v1 configmap") } // addon goes to openshift system - tectonicSys, err := configMap("tectonic-system", "cluster-config-v1", genericData{ - "addon-config": string(addon.Data), + m.tectonicConfig = configMap("tectonic-system", "cluster-config-v1", genericData{ + "addon-config": string(addon.Files()[0].Data), }) + tectonicConfigData, err := yaml.Marshal(m.tectonicConfig) if err != nil { - return nil, errors.Wrap(err, "failed to create tectonic-system/cluster-config-v1 configmap") + return errors.Wrap(err, "failed to create tectonic-system/cluster-config-v1 configmap") } - state := &asset.State{ - Contents: []asset.Content{ - { - Name: filepath.Join(manifestDir, "cluster-config.yaml"), - Data: []byte(kubeSys), - }, - { - Name: filepath.Join("tectonic", "cluster-config.yaml"), - Data: []byte(tectonicSys), - }, + m.files = []*asset.File{ + { + Filename: filepath.Join(manifestDir, "cluster-config.yaml"), + Data: kubeSysConfigData, + }, + { + Filename: filepath.Join("tectonic", "cluster-config.yaml"), + Data: tectonicConfigData, }, } - state.Contents = append(state.Contents, m.generateBootKubeManifests(dependencies)...) - return state, nil + m.files = append(m.files, m.generateBootKubeManifests(dependencies)...) + + return nil } -func (m *manifests) generateBootKubeManifests(dependencies map[asset.Asset]*asset.State) []asset.Content { - ic, err := installconfig.GetInstallConfig(m.installConfig, dependencies) - if err != nil { - return nil - } +// Files returns the files generated by the asset. +func (m *Manifests) Files() []*asset.File { + return m.files +} + +func (m *Manifests) generateBootKubeManifests(dependencies asset.Parents) []*asset.File { + installConfig := &installconfig.InstallConfig{} + aggregatorCA := &tls.AggregatorCA{} + apiServerCertKey := &tls.APIServerCertKey{} + apiServerProxyCertKey := &tls.APIServerProxyCertKey{} + clusterAPIServerCertKey := &tls.ClusterAPIServerCertKey{} + etcdCA := &tls.EtcdCA{} + etcdClientCertKey := &tls.EtcdClientCertKey{} + kubeCA := &tls.KubeCA{} + mcsCertKey := &tls.MCSCertKey{} + openshiftAPIServerCertKey := &tls.OpenshiftAPIServerCertKey{} + adminKubeconfig := &kubeconfig.Admin{} + rootCA := &tls.RootCA{} + serviceAccountKeyPair := &tls.ServiceAccountKeyPair{} + serviceServingCA := &tls.ServiceServingCA{} + workerIgnition := &machine.Worker{} + dependencies.Get( + installConfig, + aggregatorCA, + apiServerCertKey, + apiServerProxyCertKey, + clusterAPIServerCertKey, + etcdCA, + etcdClientCertKey, + kubeCA, + mcsCertKey, + openshiftAPIServerCertKey, + adminKubeconfig, + rootCA, + serviceAccountKeyPair, + serviceServingCA, + workerIgnition, + ) + templateData := &bootkubeTemplateData{ - AggregatorCaCert: base64.StdEncoding.EncodeToString(dependencies[m.aggregatorCA].Contents[certIndex].Data), - AggregatorCaKey: base64.StdEncoding.EncodeToString(dependencies[m.aggregatorCA].Contents[keyIndex].Data), - ApiserverCert: base64.StdEncoding.EncodeToString(dependencies[m.apiServerCertKey].Contents[certIndex].Data), - ApiserverKey: base64.StdEncoding.EncodeToString(dependencies[m.apiServerCertKey].Contents[keyIndex].Data), - ApiserverProxyCert: base64.StdEncoding.EncodeToString(dependencies[m.apiServerProxyCertKey].Contents[certIndex].Data), - ApiserverProxyKey: base64.StdEncoding.EncodeToString(dependencies[m.apiServerProxyCertKey].Contents[keyIndex].Data), + AggregatorCaCert: base64.StdEncoding.EncodeToString(aggregatorCA.Cert()), + AggregatorCaKey: base64.StdEncoding.EncodeToString(aggregatorCA.Key()), + ApiserverCert: base64.StdEncoding.EncodeToString(apiServerCertKey.Cert()), + ApiserverKey: base64.StdEncoding.EncodeToString(apiServerCertKey.Key()), + ApiserverProxyCert: base64.StdEncoding.EncodeToString(apiServerProxyCertKey.Cert()), + ApiserverProxyKey: base64.StdEncoding.EncodeToString(apiServerProxyCertKey.Key()), Base64encodeCloudProviderConfig: "", // FIXME - ClusterapiCaCert: base64.StdEncoding.EncodeToString(dependencies[m.clusterAPIServerCertKey].Contents[certIndex].Data), - ClusterapiCaKey: base64.StdEncoding.EncodeToString(dependencies[m.clusterAPIServerCertKey].Contents[keyIndex].Data), - EtcdCaCert: base64.StdEncoding.EncodeToString(dependencies[m.etcdCA].Contents[certIndex].Data), - EtcdClientCert: base64.StdEncoding.EncodeToString(dependencies[m.etcdClientCertKey].Contents[certIndex].Data), - EtcdClientKey: base64.StdEncoding.EncodeToString(dependencies[m.etcdClientCertKey].Contents[keyIndex].Data), - KubeCaCert: base64.StdEncoding.EncodeToString(dependencies[m.kubeCA].Contents[certIndex].Data), - KubeCaKey: base64.StdEncoding.EncodeToString(dependencies[m.kubeCA].Contents[keyIndex].Data), - McsTLSCert: base64.StdEncoding.EncodeToString(dependencies[m.mcsCertKey].Contents[certIndex].Data), - McsTLSKey: base64.StdEncoding.EncodeToString(dependencies[m.mcsCertKey].Contents[keyIndex].Data), - OidcCaCert: base64.StdEncoding.EncodeToString(dependencies[m.kubeCA].Contents[certIndex].Data), - OpenshiftApiserverCert: base64.StdEncoding.EncodeToString(dependencies[m.openshiftAPIServerCertKey].Contents[certIndex].Data), - OpenshiftApiserverKey: base64.StdEncoding.EncodeToString(dependencies[m.openshiftAPIServerCertKey].Contents[keyIndex].Data), - OpenshiftLoopbackKubeconfig: base64.StdEncoding.EncodeToString(dependencies[m.kubeconfig].Contents[0].Data), - PullSecret: base64.StdEncoding.EncodeToString([]byte(ic.PullSecret)), - RootCaCert: base64.StdEncoding.EncodeToString(dependencies[m.rootCA].Contents[certIndex].Data), - ServiceaccountKey: base64.StdEncoding.EncodeToString(dependencies[m.serviceAccountKeyPair].Contents[keyIndex].Data), - ServiceaccountPub: base64.StdEncoding.EncodeToString(dependencies[m.serviceAccountKeyPair].Contents[certIndex].Data), - ServiceServingCaCert: base64.StdEncoding.EncodeToString(dependencies[m.serviceServingCA].Contents[certIndex].Data), - ServiceServingCaKey: base64.StdEncoding.EncodeToString(dependencies[m.serviceServingCA].Contents[keyIndex].Data), + ClusterapiCaCert: base64.StdEncoding.EncodeToString(clusterAPIServerCertKey.Cert()), + ClusterapiCaKey: base64.StdEncoding.EncodeToString(clusterAPIServerCertKey.Key()), + EtcdCaCert: base64.StdEncoding.EncodeToString(etcdCA.Cert()), + EtcdClientCert: base64.StdEncoding.EncodeToString(etcdClientCertKey.Cert()), + EtcdClientKey: base64.StdEncoding.EncodeToString(etcdClientCertKey.Key()), + KubeCaCert: base64.StdEncoding.EncodeToString(kubeCA.Cert()), + KubeCaKey: base64.StdEncoding.EncodeToString(kubeCA.Key()), + McsTLSCert: base64.StdEncoding.EncodeToString(mcsCertKey.Cert()), + McsTLSKey: base64.StdEncoding.EncodeToString(mcsCertKey.Key()), + OidcCaCert: base64.StdEncoding.EncodeToString(kubeCA.Cert()), + OpenshiftApiserverCert: base64.StdEncoding.EncodeToString(openshiftAPIServerCertKey.Cert()), + OpenshiftApiserverKey: base64.StdEncoding.EncodeToString(openshiftAPIServerCertKey.Key()), + OpenshiftLoopbackKubeconfig: base64.StdEncoding.EncodeToString(adminKubeconfig.Files()[0].Data), + PullSecret: base64.StdEncoding.EncodeToString([]byte(installConfig.Config.PullSecret)), + RootCaCert: base64.StdEncoding.EncodeToString(rootCA.Cert()), + ServiceaccountKey: base64.StdEncoding.EncodeToString(serviceAccountKeyPair.Private()), + ServiceaccountPub: base64.StdEncoding.EncodeToString(serviceAccountKeyPair.Public()), + ServiceServingCaCert: base64.StdEncoding.EncodeToString(serviceServingCA.Cert()), + ServiceServingCaKey: base64.StdEncoding.EncodeToString(serviceServingCA.Key()), TectonicNetworkOperatorImage: "quay.io/coreos/tectonic-network-operator-dev:3b6952f5a1ba89bb32dd0630faddeaf2779c9a85", - WorkerIgnConfig: base64.StdEncoding.EncodeToString(dependencies[m.workerIgnition].Contents[0].Data), - CVOClusterID: ic.ClusterID, + WorkerIgnConfig: base64.StdEncoding.EncodeToString(workerIgnition.Files()[0].Data), + CVOClusterID: installConfig.Config.ClusterID, } assetData := map[string][]byte{ @@ -185,15 +207,15 @@ func (m *manifests) generateBootKubeManifests(dependencies map[asset.Asset]*asse "operatorstatus-crd.yaml": []byte(bootkube.OperatorstatusCrd), } - var assetContents []asset.Content + files := make([]*asset.File, 0, len(assetData)) for name, data := range assetData { - assetContents = append(assetContents, asset.Content{ - Name: filepath.Join(manifestDir, name), - Data: data, + files = append(files, &asset.File{ + Filename: filepath.Join(manifestDir, name), + Data: data, }) } - return assetContents + return files } func applyTemplateData(template *template.Template, templateData interface{}) []byte { diff --git a/pkg/asset/manifests/stock.go b/pkg/asset/manifests/stock.go deleted file mode 100644 index 1b402243334..00000000000 --- a/pkg/asset/manifests/stock.go +++ /dev/null @@ -1,111 +0,0 @@ -package manifests - -import ( - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/ignition/machine" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/asset/kubeconfig" - "github.com/openshift/installer/pkg/asset/tls" -) - -// Stock is the stock of operator assets that can be generated. -type Stock interface { - // Manifests returns the cluster manifests - Manifests() asset.Asset - - // ClusterVersionOperator returns the cvo asset object - ClusterVersionOperator() asset.Asset - - // KubeCoreOperator returns the kco asset object - KubeCoreOperator() asset.Asset - - // NetworkOperator returns the network operator asset object - NetworkOperator() asset.Asset - - // KubeAddonOperator returns the addon asset object - KubeAddonOperator() asset.Asset - - // Mao returns the machine api operator asset object - Mao() asset.Asset - - // Tectonic returns the tectonic manfests asset object - Tectonic() asset.Asset -} - -// StockImpl implements the Stock interface for manifests -type StockImpl struct { - manifests asset.Asset - clusterVersionOperator asset.Asset - kubeCoreOperator asset.Asset - networkOperator asset.Asset - addonOperator asset.Asset - mao asset.Asset - tectonic asset.Asset -} - -var _ Stock = (*StockImpl)(nil) - -// EstablishStock establishes the stock of assets. -func (s *StockImpl) EstablishStock(stock installconfig.Stock, tlsStock tls.Stock, kubeConfigStock kubeconfig.Stock, machineStock machine.Stock) { - s.manifests = &manifests{ - assetStock: s, - installConfig: stock.InstallConfig(), - rootCA: tlsStock.RootCA(), - etcdCA: tlsStock.EtcdCA(), - ingressCertKey: tlsStock.IngressCertKey(), - kubeCA: tlsStock.KubeCA(), - aggregatorCA: tlsStock.AggregatorCA(), - serviceServingCA: tlsStock.ServiceServingCA(), - clusterAPIServerCertKey: tlsStock.ClusterAPIServerCertKey(), - etcdClientCertKey: tlsStock.EtcdClientCertKey(), - apiServerCertKey: tlsStock.APIServerCertKey(), - openshiftAPIServerCertKey: tlsStock.OpenshiftAPIServerCertKey(), - apiServerProxyCertKey: tlsStock.APIServerProxyCertKey(), - kubeletCertKey: tlsStock.KubeletCertKey(), - mcsCertKey: tlsStock.MCSCertKey(), - serviceAccountKeyPair: tlsStock.ServiceAccountKeyPair(), - kubeconfig: kubeConfigStock.KubeconfigAdmin(), - workerIgnition: machineStock.WorkerIgnition(), - } - s.kubeCoreOperator = &kubeCoreOperator{ - installConfigAsset: stock.InstallConfig(), - } - s.addonOperator = &kubeAddonOperator{ - installConfigAsset: stock.InstallConfig(), - } - s.networkOperator = &networkOperator{ - installConfigAsset: stock.InstallConfig(), - } - s.mao = &machineAPIOperator{ - installConfigAsset: stock.InstallConfig(), - aggregatorCA: tlsStock.AggregatorCA(), - } - s.tectonic = &tectonic{ - installConfig: stock.InstallConfig(), - ingressCertKey: tlsStock.IngressCertKey(), - kubeCA: tlsStock.KubeCA(), - } - // TODO: - //s.clusterVersionOperator = &clusterVersionOperator{} -} - -// Manifests returns the manifests asset -func (s *StockImpl) Manifests() asset.Asset { return s.manifests } - -// ClusterVersionOperator returns the cvo asset object -func (s *StockImpl) ClusterVersionOperator() asset.Asset { return s.clusterVersionOperator } - -// KubeCoreOperator returns the kco asset object -func (s *StockImpl) KubeCoreOperator() asset.Asset { return s.kubeCoreOperator } - -// NetworkOperator returns the network operator asset object -func (s *StockImpl) NetworkOperator() asset.Asset { return s.networkOperator } - -// KubeAddonOperator returns the addon operator asset object -func (s *StockImpl) KubeAddonOperator() asset.Asset { return s.addonOperator } - -// Mao returns the machine API operator asset object -func (s *StockImpl) Mao() asset.Asset { return s.mao } - -// Tectonic returns the tectonic manifests asset object -func (s *StockImpl) Tectonic() asset.Asset { return s.tectonic } diff --git a/pkg/asset/manifests/tectonic.go b/pkg/asset/manifests/tectonic.go index c8d4e5bc9d4..bbee48962b8 100644 --- a/pkg/asset/manifests/tectonic.go +++ b/pkg/asset/manifests/tectonic.go @@ -5,54 +5,51 @@ import ( "encoding/base64" "path/filepath" - "github.com/pkg/errors" - "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/installconfig" content "github.com/openshift/installer/pkg/asset/manifests/content/tectonic" + "github.com/openshift/installer/pkg/asset/tls" ) -// tectonic generates the dependent resource manifests for tectonic (as against bootkube) -type tectonic struct { - installConfig asset.Asset - ingressCertKey asset.Asset - kubeCA asset.Asset +// Tectonic generates the dependent resource manifests for tectonic (as against bootkube) +type Tectonic struct { + files []*asset.File } -var _ asset.Asset = (*tectonic)(nil) +var _ asset.WritableAsset = (*Tectonic)(nil) // Name returns a human friendly name for the operator -func (t *tectonic) Name() string { +func (t *Tectonic) Name() string { return "Tectonic Manifests" } // Dependencies returns all of the dependencies directly needed by the -// tectonic asset -func (t *tectonic) Dependencies() []asset.Asset { +// Tectonic asset +func (t *Tectonic) Dependencies() []asset.Asset { return []asset.Asset{ - t.installConfig, - t.ingressCertKey, - t.kubeCA, + &installconfig.InstallConfig{}, + &tls.IngressCertKey{}, + &tls.KubeCA{}, } } // Generate generates the respective operator config.yml files -func (t *tectonic) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { - ic, err := installconfig.GetInstallConfig(t.installConfig, dependencies) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } - ingressContents := dependencies[t.ingressCertKey].Contents +func (t *Tectonic) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + ingressCertKey := &tls.IngressCertKey{} + kubeCA := &tls.KubeCA{} + dependencies.Get(installConfig, ingressCertKey, kubeCA) + templateData := &tectonicTemplateData{ - IngressCaCert: base64.StdEncoding.EncodeToString(dependencies[t.kubeCA].Contents[certIndex].Data), + IngressCaCert: base64.StdEncoding.EncodeToString(kubeCA.Cert()), IngressKind: "haproxy-router", - IngressStatusPassword: ic.Admin.Password, // FIXME: generate a new random one instead? - IngressTLSBundle: base64.StdEncoding.EncodeToString(bytes.Join([][]byte{ingressContents[certIndex].Data, ingressContents[keyIndex].Data}, []byte{})), - IngressTLSCert: base64.StdEncoding.EncodeToString(ingressContents[certIndex].Data), - IngressTLSKey: base64.StdEncoding.EncodeToString(ingressContents[keyIndex].Data), + IngressStatusPassword: installConfig.Config.Admin.Password, // FIXME: generate a new random one instead? + IngressTLSBundle: base64.StdEncoding.EncodeToString(bytes.Join([][]byte{ingressCertKey.Cert(), ingressCertKey.Key()}, []byte{})), + IngressTLSCert: base64.StdEncoding.EncodeToString(ingressCertKey.Cert()), + IngressTLSKey: base64.StdEncoding.EncodeToString(ingressCertKey.Key()), KubeAddonOperatorImage: "quay.io/coreos/kube-addon-operator-dev:3b6952f5a1ba89bb32dd0630faddeaf2779c9a85", KubeCoreOperatorImage: "quay.io/coreos/kube-core-operator-dev:3b6952f5a1ba89bb32dd0630faddeaf2779c9a85", - PullSecret: base64.StdEncoding.EncodeToString([]byte(ic.PullSecret)), + PullSecret: base64.StdEncoding.EncodeToString([]byte(installConfig.Config.PullSecret)), TectonicIngressControllerOperatorImage: "quay.io/coreos/tectonic-ingress-controller-operator-dev:3b6952f5a1ba89bb32dd0630faddeaf2779c9a85", TectonicVersion: "1.8.4-tectonic.2", } @@ -77,13 +74,18 @@ func (t *tectonic) Generate(dependencies map[asset.Asset]*asset.State) (*asset.S "99_tectonic-system-03-pull.json": applyTemplateData(content.PullTectonicSystem, templateData), } - var assetContents []asset.Content + t.files = make([]*asset.File, 0, len(assetData)) for name, data := range assetData { - assetContents = append(assetContents, asset.Content{ - Name: filepath.Join("tectonic", name), - Data: data, + t.files = append(t.files, &asset.File{ + Filename: filepath.Join("tectonic", name), + Data: data, }) } - return &asset.State{Contents: assetContents}, nil + return nil +} + +// Files returns the files generated by the asset. +func (t *Tectonic) Files() []*asset.File { + return t.files } diff --git a/pkg/asset/manifests/utils.go b/pkg/asset/manifests/utils.go index dc54ab13d16..d277efa1ab6 100644 --- a/pkg/asset/manifests/utils.go +++ b/pkg/asset/manifests/utils.go @@ -1,7 +1,8 @@ package manifests import ( - "github.com/ghodss/yaml" + "fmt" + "github.com/openshift/installer/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -18,8 +19,8 @@ type metadata struct { Namespace string `json:"namespace,omitempty"` } -func configMap(namespace, name string, data genericData) (string, error) { - configurationObject := configurationObject{ +func configMap(namespace, name string, data genericData) *configurationObject { + return &configurationObject{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "ConfigMap", @@ -30,21 +31,6 @@ func configMap(namespace, name string, data genericData) (string, error) { }, Data: data, } - - str, err := marshalYAML(configurationObject) - if err != nil { - return "", err - } - return str, nil -} - -func marshalYAML(obj interface{}) (string, error) { - data, err := yaml.Marshal(&obj) - if err != nil { - return "", err - } - - return string(data), nil } // Converts a platform to the cloudProvider that k8s understands @@ -57,3 +43,7 @@ func tectonicCloudProvider(platform types.Platform) string { } return "" } + +func getAPIServerURL(ic *types.InstallConfig) string { + return fmt.Sprintf("https://%s-api.%s:6443", ic.ObjectMeta.Name, ic.BaseDomain) +} diff --git a/pkg/asset/metadata/metadata.go b/pkg/asset/metadata/metadata.go index af44fe38dcf..4772d83ba13 100644 --- a/pkg/asset/metadata/metadata.go +++ b/pkg/asset/metadata/metadata.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/cluster" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/types" ) @@ -19,11 +20,11 @@ const ( // Metadata depends on cluster and installconfig, type Metadata struct { - installConfig asset.Asset - cluster asset.Asset + metadata *types.ClusterMetadata + file *asset.File } -var _ asset.Asset = (*Metadata)(nil) +var _ asset.WritableAsset = (*Metadata)(nil) // Name returns the human-friendly name of the asset. func (m *Metadata) Name() string { @@ -32,52 +33,57 @@ func (m *Metadata) Name() string { // Dependencies returns the dependency of the MetaData. func (m *Metadata) Dependencies() []asset.Asset { - return []asset.Asset{m.installConfig, m.cluster} + return []asset.Asset{ + &installconfig.InstallConfig{}, + &cluster.Cluster{}, + } } // Generate generates the metadata.yaml file. -func (m *Metadata) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { - installCfg, err := installconfig.GetInstallConfig(m.installConfig, parents) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } +func (m *Metadata) Generate(parents asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + cluster := &cluster.Cluster{} + parents.Get(installConfig, cluster) - cm := &types.ClusterMetadata{ - ClusterName: installCfg.ObjectMeta.Name, + m.metadata = &types.ClusterMetadata{ + ClusterName: installConfig.Config.ObjectMeta.Name, } switch { - case installCfg.Platform.AWS != nil: - cm.ClusterPlatformMetadata.AWS = &types.ClusterAWSPlatformMetadata{ - Region: installCfg.Platform.AWS.Region, + case installConfig.Config.Platform.AWS != nil: + m.metadata.ClusterPlatformMetadata.AWS = &types.ClusterAWSPlatformMetadata{ + Region: installConfig.Config.Platform.AWS.Region, Identifier: map[string]string{ - "tectonicClusterID": installCfg.ClusterID, + "tectonicClusterID": installConfig.Config.ClusterID, }, } - case installCfg.Platform.OpenStack != nil: - cm.ClusterPlatformMetadata.OpenStack = &types.ClusterOpenStackPlatformMetadata{ - Region: installCfg.Platform.OpenStack.Region, + case installConfig.Config.Platform.OpenStack != nil: + m.metadata.ClusterPlatformMetadata.OpenStack = &types.ClusterOpenStackPlatformMetadata{ + Region: installConfig.Config.Platform.OpenStack.Region, Identifier: map[string]string{ - "tectonicClusterID": installCfg.ClusterID, + "tectonicClusterID": installConfig.Config.ClusterID, }, } - case installCfg.Platform.Libvirt != nil: - cm.ClusterPlatformMetadata.Libvirt = &types.ClusterLibvirtPlatformMetadata{ - URI: installCfg.Platform.Libvirt.URI, + case installConfig.Config.Platform.Libvirt != nil: + m.metadata.ClusterPlatformMetadata.Libvirt = &types.ClusterLibvirtPlatformMetadata{ + URI: installConfig.Config.Platform.Libvirt.URI, } default: - return nil, fmt.Errorf("no known platform") + return fmt.Errorf("no known platform") } - data, err := json.Marshal(cm) + data, err := json.Marshal(m.metadata) if err != nil { - return nil, errors.Wrap(err, "failed to Marshal ClusterMetadata") + return errors.Wrap(err, "failed to Marshal ClusterMetadata") } - return &asset.State{ - Contents: []asset.Content{ - { - Name: MetadataFilename, - Data: []byte(data), - }, - }, - }, nil + m.file = &asset.File{ + Filename: MetadataFilename, + Data: data, + } + + return nil +} + +// Files returns the files generated by the asset. +func (m *Metadata) Files() []*asset.File { + return []*asset.File{m.file} } diff --git a/pkg/asset/metadata/stock.go b/pkg/asset/metadata/stock.go deleted file mode 100644 index e4e142fc902..00000000000 --- a/pkg/asset/metadata/stock.go +++ /dev/null @@ -1,31 +0,0 @@ -package metadata - -import ( - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/cluster" - "github.com/openshift/installer/pkg/asset/installconfig" -) - -// Stock is the stock of the cluster assets that can be generated. -type Stock interface { - // Metadata is the asset that generates the metadata.json file - Metadata() asset.Asset -} - -// StockImpl is the implementation of the cluster asset stock. -type StockImpl struct { - metadata asset.Asset -} - -var _ Stock = (*StockImpl)(nil) - -// EstablishStock establishes the stock of assets in the specified directory. -func (s *StockImpl) EstablishStock(installConfigStock installconfig.Stock, clusterStock cluster.Stock) { - s.metadata = &Metadata{ - installConfig: installConfigStock.InstallConfig(), - cluster: clusterStock.Cluster(), - } -} - -// Metadata returns the terraform tfvar asset. -func (s *StockImpl) Metadata() asset.Asset { return s.metadata } diff --git a/pkg/asset/parents.go b/pkg/asset/parents.go new file mode 100644 index 00000000000..12d45062116 --- /dev/null +++ b/pkg/asset/parents.go @@ -0,0 +1,24 @@ +package asset + +import ( + "reflect" +) + +// Parents is the collection of assets upon which another asset is directly +// dependent. +type Parents map[reflect.Type]Asset + +// Add adds the specified assets to the parents collection. +func (p Parents) Add(assets ...Asset) { + for _, a := range assets { + p[reflect.TypeOf(a)] = a + } +} + +// Get populates the state of the specified assets with the state stored in the +// parents collection. +func (p Parents) Get(assets ...Asset) { + for _, a := range assets { + reflect.ValueOf(a).Elem().Set(reflect.ValueOf(p[reflect.TypeOf(a)]).Elem()) + } +} diff --git a/pkg/asset/parents_test.go b/pkg/asset/parents_test.go new file mode 100644 index 00000000000..58606fb5592 --- /dev/null +++ b/pkg/asset/parents_test.go @@ -0,0 +1,33 @@ +package asset + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type parentsAsset struct { + x int +} + +func (a *parentsAsset) Name() string { + return "parents-asset" +} + +func (a *parentsAsset) Dependencies() []Asset { + return []Asset{} +} + +func (a *parentsAsset) Generate(Parents) error { + return nil +} + +func TestParentsGetPointer(t *testing.T) { + origAsset := &parentsAsset{x: 1} + parents := Parents{} + parents.Add(origAsset) + + retrievedAsset := &parentsAsset{} + parents.Get(retrievedAsset) + assert.Equal(t, 1, retrievedAsset.x) +} diff --git a/pkg/asset/stock/doc.go b/pkg/asset/stock/doc.go deleted file mode 100644 index 38923c414df..00000000000 --- a/pkg/asset/stock/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package stock defines a stock type that holds that holds the instantiated assets. -package stock diff --git a/pkg/asset/stock/stock.go b/pkg/asset/stock/stock.go deleted file mode 100644 index 129e0ec27cb..00000000000 --- a/pkg/asset/stock/stock.go +++ /dev/null @@ -1,72 +0,0 @@ -package stock - -import ( - "github.com/openshift/installer/pkg/asset/cluster" - "github.com/openshift/installer/pkg/asset/ignition/bootstrap" - "github.com/openshift/installer/pkg/asset/ignition/machine" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/asset/kubeconfig" - "github.com/openshift/installer/pkg/asset/manifests" - "github.com/openshift/installer/pkg/asset/metadata" - "github.com/openshift/installer/pkg/asset/tls" -) - -// Stock is the stock of assets that can be generated. -type Stock struct { - installConfigStock - kubeconfigStock - tlsStock - bootstrapStock - machineStock - clusterStock - manifestsStock - metadataStock -} - -type installConfigStock struct { - installconfig.StockImpl -} - -type tlsStock struct { - tls.StockImpl -} - -type kubeconfigStock struct { - kubeconfig.StockImpl -} - -type bootstrapStock struct { - bootstrap.StockImpl -} - -type machineStock struct { - machine.StockImpl -} - -type clusterStock struct { - cluster.StockImpl -} - -type manifestsStock struct { - manifests.StockImpl -} - -type metadataStock struct { - metadata.StockImpl -} - -var _ installconfig.Stock = (*Stock)(nil) - -// EstablishStock establishes the stock of assets. -func EstablishStock() *Stock { - s := &Stock{} - s.installConfigStock.EstablishStock() - s.tlsStock.EstablishStock(&s.installConfigStock) - s.kubeconfigStock.EstablishStock(&s.installConfigStock, &s.tlsStock) - s.machineStock.EstablishStock(s, s) - s.manifestsStock.EstablishStock(&s.installConfigStock, s, s, s) - s.bootstrapStock.EstablishStock(s, s, s, s) - s.clusterStock.EstablishStock(s, s, s, s) - s.metadataStock.EstablishStock(s, s) - return s -} diff --git a/pkg/asset/store.go b/pkg/asset/store.go index eaf159306e2..7de7930fa04 100644 --- a/pkg/asset/store.go +++ b/pkg/asset/store.go @@ -1,6 +1,8 @@ package asset import ( + "reflect" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -9,49 +11,50 @@ import ( type Store interface { // Fetch retrieves the state of the given asset, generating it and its // dependencies if necessary. - Fetch(Asset) (*State, error) + Fetch(Asset) error } // StoreImpl is the implementation of Store. type StoreImpl struct { - assets map[Asset]*State + assets map[reflect.Type]Asset } // Fetch retrieves the state of the given asset, generating it and its // dependencies if necessary. -func (s *StoreImpl) Fetch(asset Asset) (*State, error) { +func (s *StoreImpl) Fetch(asset Asset) error { return s.fetch(asset, "") } -func (s *StoreImpl) fetch(asset Asset, indent string) (*State, error) { +func (s *StoreImpl) fetch(asset Asset, indent string) error { logrus.Debugf("%sFetching %s...", indent, asset.Name()) - state, ok := s.assets[asset] + storedAsset, ok := s.assets[reflect.TypeOf(asset)] if ok { logrus.Debugf("%sFound %s...", indent, asset.Name()) - return state, nil + reflect.ValueOf(asset).Elem().Set(reflect.ValueOf(storedAsset).Elem()) + return nil } dependencies := asset.Dependencies() - dependenciesStates := make(map[Asset]*State, len(dependencies)) + parents := make(Parents, len(dependencies)) if len(dependencies) > 0 { logrus.Debugf("%sGenerating dependencies of %s...", indent, asset.Name()) } for _, d := range dependencies { - ds, err := s.fetch(d, indent+" ") + err := s.fetch(d, indent+" ") if err != nil { - return nil, errors.Wrapf(err, "failed to fetch dependency for %s", asset.Name()) + return errors.Wrapf(err, "failed to fetch dependency for %s", asset.Name()) } - dependenciesStates[d] = ds + parents.Add(d) } logrus.Debugf("%sGenerating %s...", indent, asset.Name()) - state, err := asset.Generate(dependenciesStates) + err := asset.Generate(parents) if err != nil { - return nil, errors.Wrapf(err, "failed to generate asset %s", asset.Name()) + return errors.Wrapf(err, "failed to generate asset %s", asset.Name()) } if s.assets == nil { - s.assets = make(map[Asset]*State) + s.assets = make(map[reflect.Type]Asset) } - s.assets[asset] = state - return state, nil + s.assets[reflect.TypeOf(asset)] = asset + return nil } diff --git a/pkg/asset/store_test.go b/pkg/asset/store_test.go index 839f9bc4d37..49ed8e078fd 100644 --- a/pkg/asset/store_test.go +++ b/pkg/asset/store_test.go @@ -1,6 +1,7 @@ package asset import ( + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -10,34 +11,71 @@ type generationLog struct { log []string } -func (l *generationLog) logGeneration(a *testAsset) { - l.log = append(l.log, a.name) +func (l *generationLog) logGeneration(a Asset) { + l.log = append(l.log, a.Name()) } -type testAsset struct { +type testStoreAsset interface { + Asset + SetDependencies([]Asset) +} + +type testStoreAssetImpl struct { name string dependencies []Asset generationLog *generationLog } -func (a *testAsset) Dependencies() []Asset { +func (a *testStoreAssetImpl) Dependencies() []Asset { return a.dependencies } -func (a *testAsset) Generate(map[Asset]*State) (*State, error) { +func (a *testStoreAssetImpl) Generate(Parents) error { a.generationLog.logGeneration(a) - return nil, nil + return nil +} + +func (a *testStoreAssetImpl) Name() string { + return a.name } -func (a *testAsset) Name() string { - return "Test Asset" +func (a *testStoreAssetImpl) SetDependencies(dependencies []Asset) { + a.dependencies = dependencies } -func newTestAsset(gl *generationLog, name string) *testAsset { - return &testAsset{ +type testStoreAssetA struct { + testStoreAssetImpl +} + +type testStoreAssetB struct { + testStoreAssetImpl +} + +type testStoreAssetC struct { + testStoreAssetImpl +} + +type testStoreAssetD struct { + testStoreAssetImpl +} + +func newTestStoreAsset(gl *generationLog, name string) testStoreAsset { + ta := testStoreAssetImpl{ name: name, generationLog: gl, } + switch name { + case "a": + return &testStoreAssetA{ta} + case "b": + return &testStoreAssetB{ta} + case "c": + return &testStoreAssetC{ta} + case "d": + return &testStoreAssetD{ta} + default: + return nil + } } // TestStoreFetch tests the Fetch method of StoreImpl. @@ -156,24 +194,24 @@ func TestStoreFetch(t *testing.T) { log: []string{}, } store := &StoreImpl{ - assets: map[Asset]*State{}, + assets: Parents{}, } - assets := make(map[string]*testAsset, len(tc.assets)) + assets := make(map[string]testStoreAsset, len(tc.assets)) for name := range tc.assets { - assets[name] = newTestAsset(gl, name) + assets[name] = newTestStoreAsset(gl, name) } for name, deps := range tc.assets { dependencies := make([]Asset, len(deps)) for i, d := range deps { dependencies[i] = assets[d] } - assets[name].dependencies = dependencies + assets[name].SetDependencies(dependencies) } for _, assetName := range tc.existingAssets { asset := assets[assetName] - store.assets[asset] = nil + store.assets[reflect.TypeOf(asset)] = asset } - _, err := store.Fetch(assets[tc.target]) + err := store.Fetch(assets[tc.target]) assert.NoError(t, err, "error fetching asset") assert.EqualValues(t, tc.expectedGenerationLog, gl.log) }) diff --git a/pkg/asset/tls/admincertkey.go b/pkg/asset/tls/admincertkey.go new file mode 100644 index 00000000000..5866a8f19d1 --- /dev/null +++ b/pkg/asset/tls/admincertkey.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +//AdminCertKey is the asset that generates the admin key/cert pair. +type AdminCertKey struct { + CertKey +} + +var _ asset.WritableAsset = (*AdminCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *AdminCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &KubeCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *AdminCertKey) Generate(dependencies asset.Parents) error { + kubeCA := &KubeCA{} + dependencies.Get(kubeCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + } + + return a.CertKey.Generate(cfg, kubeCA, "admin", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *AdminCertKey) Name() string { + return "Certificate (system:admin)" +} diff --git a/pkg/asset/tls/aggregatorca.go b/pkg/asset/tls/aggregatorca.go new file mode 100644 index 00000000000..632c4b159c5 --- /dev/null +++ b/pkg/asset/tls/aggregatorca.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +// AggregatorCA is the asset that generates the aggregator-ca key/cert pair. +type AggregatorCA struct { + CertKey +} + +var _ asset.Asset = (*AggregatorCA)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *AggregatorCA) Dependencies() []asset.Asset { + return []asset.Asset{ + &RootCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *AggregatorCA) Generate(dependencies asset.Parents) error { + rootCA := &RootCA{} + dependencies.Get(rootCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "aggregator", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + IsCA: true, + } + + return a.CertKey.Generate(cfg, rootCA, "aggregator-ca", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *AggregatorCA) Name() string { + return "Certificate (aggregator)" +} diff --git a/pkg/asset/tls/apiservercertkey.go b/pkg/asset/tls/apiservercertkey.go new file mode 100644 index 00000000000..459d132257d --- /dev/null +++ b/pkg/asset/tls/apiservercertkey.go @@ -0,0 +1,63 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + "net" + + "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" +) + +// APIServerCertKey is the asset that generates the API server key/cert pair. +type APIServerCertKey struct { + CertKey +} + +var _ asset.Asset = (*APIServerCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *APIServerCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &KubeCA{}, + &installconfig.InstallConfig{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *APIServerCertKey) Generate(dependencies asset.Parents) error { + kubeCA := &KubeCA{} + installConfig := &installconfig.InstallConfig{} + dependencies.Get(kubeCA, installConfig) + + apiServerAddress, err := cidrhost(installConfig.Config.Networking.ServiceCIDR.IPNet, 1) + if err != nil { + return errors.Wrap(err, "failed to get API Server address from InstallConfig") + } + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kube-apiserver", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + DNSNames: []string{ + apiAddress(installConfig.Config), + "kubernetes", "kubernetes.default", + "kubernetes.default.svc", + "kubernetes.default.svc.cluster.local", + "localhost", + }, + IPAddresses: []net.IP{net.ParseIP(apiServerAddress), net.ParseIP("127.0.0.1")}, + } + + return a.CertKey.Generate(cfg, kubeCA, "apiserver", AppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *APIServerCertKey) Name() string { + return "Certificate (kube-apiaserver)" +} diff --git a/pkg/asset/tls/apiserverproxycertkey.go b/pkg/asset/tls/apiserverproxycertkey.go new file mode 100644 index 00000000000..368760b7f19 --- /dev/null +++ b/pkg/asset/tls/apiserverproxycertkey.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +// APIServerProxyCertKey is the asset that generates the API server proxy key/cert pair. +type APIServerProxyCertKey struct { + CertKey +} + +var _ asset.Asset = (*APIServerProxyCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *APIServerProxyCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &AggregatorCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *APIServerProxyCertKey) Generate(dependencies asset.Parents) error { + aggregatorCA := &AggregatorCA{} + dependencies.Get(aggregatorCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kube-apiserver-proxy", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + } + + return a.CertKey.Generate(cfg, aggregatorCA, "apiserver-proxy", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *APIServerProxyCertKey) Name() string { + return "Certificate (kube-apiserver-proxy)" +} diff --git a/pkg/asset/tls/certkey.go b/pkg/asset/tls/certkey.go index 607d9c8f5af..2badf605e0a 100644 --- a/pkg/asset/tls/certkey.go +++ b/pkg/asset/tls/certkey.go @@ -1,165 +1,104 @@ package tls import ( + "bytes" "crypto/rsa" "crypto/x509" - "crypto/x509/pkix" - "fmt" - "net" - "path/filepath" - "time" - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/installconfig" - "github.com/openshift/installer/pkg/types" "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/asset" ) +// AppendParentChoice dictates whether the parent's cert is to be added to the +// cert. +type AppendParentChoice bool + +const ( + // AppendParent indicates that the parent's cert should be added. + AppendParent AppendParentChoice = true + // DoNotAppendParent indicates that the parent's cert should not be added. + DoNotAppendParent AppendParentChoice = false +) + +// CertKeyInterface contains a private key and the associated cert. +type CertKeyInterface interface { + // Cert returns the certificate. + Cert() []byte + // Key returns the private key. + Key() []byte +} + // CertKey contains the private key and the cert that's // signed by the parent CA. type CertKey struct { - installConfig asset.Asset - - // Common fields. - Subject pkix.Name - KeyUsages x509.KeyUsage - ExtKeyUsages []x509.ExtKeyUsage - Validity time.Duration - KeyFileName string - CertFileName string - ParentCA asset.Asset - - IsCA bool - AppendParent bool // Whether append the parent CA in the cert. - - // Some certs might need to set Subject, DNSNames and IPAddresses. - GenDNSNames func(*types.InstallConfig) ([]string, error) - GenIPAddresses func(*types.InstallConfig) ([]net.IP, error) - GenSubject func(*types.InstallConfig) (pkix.Name, error) + cert []byte + key []byte + files []*asset.File } -var _ asset.Asset = (*CertKey)(nil) - -// Dependencies returns the dependency of the the cert/key pair, which includes -// the parent CA, and install config if it depends on the install config for -// DNS names, etc. -func (c *CertKey) Dependencies() []asset.Asset { - parents := []asset.Asset{c.ParentCA} - - // Require the InstallConfig if we need additional info from install config. - if c.GenDNSNames != nil || c.GenIPAddresses != nil || c.GenSubject != nil { - parents = append(parents, c.installConfig) - } - - return parents +// Cert returns the certificate. +func (c *CertKey) Cert() []byte { + return c.cert } -// Generate generates the cert/key pair based on its dependencies. -func (c *CertKey) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { - cfg := &CertCfg{ - Subject: c.Subject, - KeyUsages: c.KeyUsages, - ExtKeyUsages: c.ExtKeyUsages, - Validity: c.Validity, - IsCA: c.IsCA, - } - - if c.GenSubject != nil || c.GenDNSNames != nil || c.GenIPAddresses != nil { - installConfig, err := installconfig.GetInstallConfig(c.installConfig, parents) - if err != nil { - return nil, errors.Wrap(err, "failed to get InstallConfig from parents") - } - - if c.GenSubject != nil { - cfg.Subject, err = c.GenSubject(installConfig) - if err != nil { - return nil, errors.Wrap(err, "failed to generate Subject") - } - } - if c.GenDNSNames != nil { - cfg.DNSNames, err = c.GenDNSNames(installConfig) - if err != nil { - return nil, errors.Wrap(err, "failed to generate DNSNames") - } - } - if c.GenIPAddresses != nil { - cfg.IPAddresses, err = c.GenIPAddresses(installConfig) - if err != nil { - return nil, errors.Wrap(err, "failed to generate IPAddresses") - } - } - } +// Key returns the private key. +func (c *CertKey) Key() []byte { + return c.key +} +// Generate generates a cert/key pair signed by the specified parent CA. +func (c *CertKey) Generate( + cfg *CertCfg, + parentCA CertKeyInterface, + filenameBase string, + appendParent AppendParentChoice, +) error { var key *rsa.PrivateKey var crt *x509.Certificate var err error - state, ok := parents[c.ParentCA] - if !ok { - return nil, errors.Errorf("failed to get parent CA %v in the parent asset states", c.ParentCA) + caKey, err := PemToPrivateKey(parentCA.Key()) + if err != nil { + return errors.Wrap(err, "failed to parse rsa private key") } - caKey, caCert, err := parseCAFromAssetState(state) + caCert, err := PemToCertificate(parentCA.Cert()) if err != nil { - return nil, errors.Wrap(err, "failed to parse CA from asset") + return errors.Wrap(err, "failed to parse x509 certificate") } key, crt, err = GenerateCert(caKey, caCert, cfg) if err != nil { - return nil, errors.Wrap(err, "failed to generate cert/key pair") + return errors.Wrap(err, "failed to generate cert/key pair") } - keyData := []byte(PrivateKeyToPem(key)) - certData := []byte(CertToPem(crt)) - if c.AppendParent { - certData = append(certData, '\n') - certData = append(certData, []byte(CertToPem(caCert))...) + c.key = PrivateKeyToPem(key) + c.cert = CertToPem(crt) + + if appendParent { + c.cert = bytes.Join([][]byte{c.cert, CertToPem(caCert)}, []byte("\n")) } - return &asset.State{ - Contents: []asset.Content{ - { - Name: assetFilePath(c.KeyFileName), - Data: keyData, - }, - { - Name: assetFilePath(c.CertFileName), - Data: certData, - }, - }, - }, nil -} + c.generateFiles(filenameBase) -// Name returns the human-friendly name of the asset. -func (c *CertKey) Name() string { - return fmt.Sprintf("Certificate (%s)", c.Subject.CommonName) + return nil } -func parseCAFromAssetState(ca *asset.State) (*rsa.PrivateKey, *x509.Certificate, error) { - var key *rsa.PrivateKey - var cert *x509.Certificate - var err error - - if len(ca.Contents) != 2 { - return nil, nil, errors.Errorf("expected key and cert in the contents of CA, got: %v", ca) - } +// Files returns the files generated by the asset. +func (c *CertKey) Files() []*asset.File { + return c.files +} - for _, c := range ca.Contents { - switch filepath.Ext(c.Name) { - case ".key": - key, err = PemToPrivateKey(c.Data) - if err != nil { - return nil, nil, errors.Wrap(err, "failed to parse rsa private key") - } - case ".crt": - cert, err = PemToCertificate(c.Data) - if err != nil { - return nil, nil, errors.Wrap(err, "failed to parse x509 certificate") - } - default: - return nil, nil, errors.Errorf("unexpected content name: %v", c.Name) - } +func (c *CertKey) generateFiles(filenameBase string) { + c.files = []*asset.File{ + { + Filename: assetFilePath(filenameBase + ".key"), + Data: c.key, + }, + { + Filename: assetFilePath(filenameBase + ".crt"), + Data: c.cert, + }, } - - return key, cert, nil } diff --git a/pkg/asset/tls/certkey_test.go b/pkg/asset/tls/certkey_test.go index b68c59c8deb..ecfb4167dd1 100644 --- a/pkg/asset/tls/certkey_test.go +++ b/pkg/asset/tls/certkey_test.go @@ -6,126 +6,50 @@ import ( "net" "testing" - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/types" "github.com/stretchr/testify/assert" ) -type fakeInstallConfig bool - -var _ asset.Asset = fakeInstallConfig(false) - -func (f fakeInstallConfig) Dependencies() []asset.Asset { - return nil -} - -func (f fakeInstallConfig) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { - return &asset.State{ - Contents: []asset.Content{ - { - Name: "fakeInstallConfig", - Data: []byte{}, - }, - }, - }, nil -} - -func (f fakeInstallConfig) Name() string { - return "Fake Install Config" -} - func TestCertKeyGenerate(t *testing.T) { - root := &RootCA{} - rootState, err := root.Generate(nil) - if err != nil { - t.Fatal(err) - } - - var installConfig fakeInstallConfig - installConfigState, err := installConfig.Generate(nil) - if err != nil { - t.Fatal(err) - } - - testGenSubject := func(*types.InstallConfig) (pkix.Name, error) { - return pkix.Name{CommonName: "test", OrganizationalUnit: []string{"openshift"}}, nil - } - - testGenDNSNames := func(*types.InstallConfig) ([]string, error) { - return []string{"test.openshift.io"}, nil - } - - testGenIPAddresses := func(*types.InstallConfig) ([]net.IP, error) { - return []net.IP{net.ParseIP("10.0.0.1")}, nil - } - tests := []struct { - name string - certKey *CertKey - errString string - parents map[asset.Asset]*asset.State + name string + certCfg *CertCfg + filenameBase string + certFileName string + appendParent AppendParentChoice + errString string }{ { name: "simple ca", - certKey: &CertKey{ - installConfig: installConfig, - Subject: pkix.Name{CommonName: "test0-ca", OrganizationalUnit: []string{"openshift"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: ValidityTenYears, - KeyFileName: "test0-ca.key", - CertFileName: "test0-ca.crt", - IsCA: true, - ParentCA: root, - }, - parents: map[asset.Asset]*asset.State{ - root: rootState, + certCfg: &CertCfg{ + Subject: pkix.Name{CommonName: "test0-ca", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, }, + filenameBase: "test0-ca", + appendParent: DoNotAppendParent, }, { name: "more complicated ca", - certKey: &CertKey{ - installConfig: installConfig, - Subject: pkix.Name{CommonName: "test1-ca", OrganizationalUnit: []string{"openshift"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: ValidityTenYears, - KeyFileName: "test1-ca.key", - CertFileName: "test1-ca.crt", - IsCA: true, - ParentCA: root, - AppendParent: true, - GenSubject: testGenSubject, - GenDNSNames: testGenDNSNames, - GenIPAddresses: testGenIPAddresses, + certCfg: &CertCfg{ + Subject: pkix.Name{CommonName: "test1-ca", OrganizationalUnit: []string{"openshift"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + DNSNames: []string{"test.openshift.io"}, + IPAddresses: []net.IP{net.ParseIP("10.0.0.1")}, }, - parents: map[asset.Asset]*asset.State{ - root: rootState, - installConfig: installConfigState, - }, - }, - { - name: "can't find parents", - certKey: &CertKey{ - installConfig: installConfig, - Subject: pkix.Name{CommonName: "test1-ca", OrganizationalUnit: []string{"openshift"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: ValidityTenYears, - KeyFileName: "test2-ca.key", - CertFileName: "test2-ca.crt", - IsCA: true, - ParentCA: root, - AppendParent: true, - GenSubject: testGenSubject, - GenDNSNames: testGenDNSNames, - GenIPAddresses: testGenIPAddresses, - }, - errString: "failed to get InstallConfig from parents: tls.fakeInstallConfig does not exist in parents", - parents: nil, + filenameBase: "test1-ca", + appendParent: AppendParent, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - st, err := tt.certKey.Generate(tt.parents) + rootCA := &RootCA{} + err := rootCA.Generate(nil) + assert.NoError(t, err, "failed to generate root CA") + + certKey := &CertKey{} + err = certKey.Generate(tt.certCfg, rootCA, tt.filenameBase, tt.appendParent) if err != nil { assert.EqualErrorf(t, err, tt.errString, tt.name) return @@ -133,24 +57,30 @@ func TestCertKeyGenerate(t *testing.T) { t.Errorf("expect error %v, saw nil", err) } - assert.Equal(t, assetFilePath(tt.certKey.KeyFileName), st.Contents[0].Name, "unexpected key file name") - assert.Equal(t, assetFilePath(tt.certKey.CertFileName), st.Contents[1].Name, "unexpected cert file name") + actualFiles := certKey.Files() + + assert.Equal(t, 2, len(actualFiles), "unexpected number of files") + assert.Equal(t, assetFilePath(tt.filenameBase+".key"), actualFiles[0].Filename, "unexpected key file name") + assert.Equal(t, assetFilePath(tt.filenameBase+".crt"), actualFiles[1].Filename, "unexpected cert file name") + + assert.Equal(t, certKey.Key(), actualFiles[0].Data, "key file data does not match key") + assert.Equal(t, certKey.Cert(), actualFiles[1].Data, "cert file does not match cert") // Briefly check the certs. certPool := x509.NewCertPool() - if !certPool.AppendCertsFromPEM(st.Contents[1].Data) { + if !certPool.AppendCertsFromPEM(certKey.Cert()) { t.Error("failed to append certs from PEM") } opts := x509.VerifyOptions{ Roots: certPool, - DNSName: tt.certKey.Subject.CommonName, + DNSName: tt.certCfg.Subject.CommonName, } - if tt.certKey.GenDNSNames != nil { + if tt.certCfg.DNSNames != nil { opts.DNSName = "test.openshift.io" } - cert, err := PemToCertificate(st.Contents[1].Data) + cert, err := PemToCertificate(certKey.Cert()) assert.NoError(t, err, tt.name) _, err = cert.Verify(opts) diff --git a/pkg/asset/tls/clusterapiservercertkey.go b/pkg/asset/tls/clusterapiservercertkey.go new file mode 100644 index 00000000000..dcb1469ea4b --- /dev/null +++ b/pkg/asset/tls/clusterapiservercertkey.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +// ClusterAPIServerCertKey is the asset that generates the cluster API server key/cert pair. +type ClusterAPIServerCertKey struct { + CertKey +} + +var _ asset.Asset = (*ClusterAPIServerCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *ClusterAPIServerCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &AggregatorCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *ClusterAPIServerCertKey) Generate(dependencies asset.Parents) error { + aggregatorCA := &AggregatorCA{} + dependencies.Get(aggregatorCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "clusterapi.openshift-cluster-api.svc", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + IsCA: true, + } + + return a.CertKey.Generate(cfg, aggregatorCA, "cluster-apiserver-ca", AppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *ClusterAPIServerCertKey) Name() string { + return "Certificate (clusterapi.openshift-cluster-api.svc)" +} diff --git a/pkg/asset/tls/etcdca.go b/pkg/asset/tls/etcdca.go new file mode 100644 index 00000000000..ca1f58884e6 --- /dev/null +++ b/pkg/asset/tls/etcdca.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +// EtcdCA is the asset that generates the etcd-ca key/cert pair. +type EtcdCA struct { + CertKey +} + +var _ asset.Asset = (*EtcdCA)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *EtcdCA) Dependencies() []asset.Asset { + return []asset.Asset{ + &RootCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *EtcdCA) Generate(dependencies asset.Parents) error { + rootCA := &RootCA{} + dependencies.Get(rootCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "etcd", OrganizationalUnit: []string{"etcd"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + IsCA: true, + } + + return a.CertKey.Generate(cfg, rootCA, "etcd-client-ca", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *EtcdCA) Name() string { + return "Certificate (etcd)" +} diff --git a/pkg/asset/tls/etcdclientcertkey.go b/pkg/asset/tls/etcdclientcertkey.go new file mode 100644 index 00000000000..d9c90258d17 --- /dev/null +++ b/pkg/asset/tls/etcdclientcertkey.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +// EtcdClientCertKey is the asset that generates the etcd client key/cert pair. +type EtcdClientCertKey struct { + CertKey +} + +var _ asset.Asset = (*EtcdClientCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *EtcdClientCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &EtcdCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *EtcdClientCertKey) Generate(dependencies asset.Parents) error { + etcdCA := &EtcdCA{} + dependencies.Get(etcdCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "etcd", OrganizationalUnit: []string{"etcd"}}, + KeyUsages: x509.KeyUsageKeyEncipherment, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + } + + return a.CertKey.Generate(cfg, etcdCA, "etcd-client", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *EtcdClientCertKey) Name() string { + return "Certificate (etcd)" +} diff --git a/pkg/asset/tls/helper.go b/pkg/asset/tls/helper.go index 40d31bf69ad..de02b26029c 100644 --- a/pkg/asset/tls/helper.go +++ b/pkg/asset/tls/helper.go @@ -1,12 +1,12 @@ package tls import ( - "crypto/x509/pkix" "fmt" "net" "path/filepath" "github.com/apparentlymart/go-cidr/cidr" + "github.com/openshift/installer/pkg/types" ) @@ -18,8 +18,8 @@ func assetFilePath(filename string) string { return filepath.Join(tlsDir, filename) } -func getBaseAddress(cfg *types.InstallConfig) string { - return fmt.Sprintf("%s.%s", cfg.ObjectMeta.Name, cfg.BaseDomain) +func apiAddress(cfg *types.InstallConfig) string { + return fmt.Sprintf("%s-api.%s", cfg.ObjectMeta.Name, cfg.BaseDomain) } func cidrhost(network net.IPNet, hostNum int) (string, error) { @@ -30,60 +30,3 @@ func cidrhost(network net.IPNet, hostNum int) (string, error) { return ip.String(), nil } - -func genSubjectForIngressCertKey(cfg *types.InstallConfig) (pkix.Name, error) { - return pkix.Name{CommonName: getBaseAddress(cfg), Organization: []string{"ingress"}}, nil -} - -func genDNSNamesForIngressCertKey(cfg *types.InstallConfig) ([]string, error) { - baseAddress := getBaseAddress(cfg) - return []string{ - baseAddress, - fmt.Sprintf("*.%s", baseAddress), - }, nil -} - -func genDNSNamesForAPIServerCertKey(cfg *types.InstallConfig) ([]string, error) { - return []string{ - fmt.Sprintf("%s-api.%s", cfg.ObjectMeta.Name, cfg.BaseDomain), - "kubernetes", "kubernetes.default", - "kubernetes.default.svc", - "kubernetes.default.svc.cluster.local", - "localhost", - }, nil -} - -func genIPAddressesForAPIServerCertKey(cfg *types.InstallConfig) ([]net.IP, error) { - apiServerAddress, err := cidrhost(cfg.Networking.ServiceCIDR.IPNet, 1) - if err != nil { - return nil, err - } - return []net.IP{net.ParseIP(apiServerAddress), net.ParseIP("127.0.0.1")}, nil -} - -func genDNSNamesForOpenshiftAPIServerCertKey(cfg *types.InstallConfig) ([]string, error) { - return []string{ - fmt.Sprintf("%s-api.%s", cfg.ObjectMeta.Name, cfg.BaseDomain), - "openshift-apiserver", - "openshift-apiserver.kube-system", - "openshift-apiserver.kube-system.svc", - "openshift-apiserver.kube-system.svc.cluster.local", - "localhost", "127.0.0.1", - }, nil -} - -func genIPAddressesForOpenshiftAPIServerCertKey(cfg *types.InstallConfig) ([]net.IP, error) { - apiServerAddress, err := cidrhost(cfg.Networking.ServiceCIDR.IPNet, 1) - if err != nil { - return nil, err - } - return []net.IP{net.ParseIP(apiServerAddress)}, nil -} - -func genDNSNamesForMCSCertKey(cfg *types.InstallConfig) ([]string, error) { - return []string{fmt.Sprintf("%s-api.%s", cfg.ObjectMeta.Name, cfg.BaseDomain)}, nil -} - -func genSubjectForMCSCertKey(cfg *types.InstallConfig) (pkix.Name, error) { - return pkix.Name{CommonName: fmt.Sprintf("%s-api.%s", cfg.ObjectMeta.Name, cfg.BaseDomain)}, nil -} diff --git a/pkg/asset/tls/ingresscertkey.go b/pkg/asset/tls/ingresscertkey.go new file mode 100644 index 00000000000..69141ae2ad0 --- /dev/null +++ b/pkg/asset/tls/ingresscertkey.go @@ -0,0 +1,54 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + "fmt" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" +) + +// IngressCertKey is the asset that generates the ingress key/cert pair. +type IngressCertKey struct { + CertKey +} + +var _ asset.Asset = (*IngressCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *IngressCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &KubeCA{}, + &installconfig.InstallConfig{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *IngressCertKey) Generate(dependencies asset.Parents) error { + kubeCA := &KubeCA{} + installConfig := &installconfig.InstallConfig{} + dependencies.Get(kubeCA, installConfig) + + baseAddress := fmt.Sprintf("%s.%s", installConfig.Config.ObjectMeta.Name, installConfig.Config.BaseDomain) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: baseAddress, Organization: []string{"ingress"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + DNSNames: []string{ + baseAddress, + fmt.Sprintf("*.%s", baseAddress), + }, + } + + return a.CertKey.Generate(cfg, kubeCA, "ingress", AppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *IngressCertKey) Name() string { + return "Certificate (ingress)" +} diff --git a/pkg/asset/tls/keypair.go b/pkg/asset/tls/keypair.go index 40ea9351ef0..79e243eee67 100644 --- a/pkg/asset/tls/keypair.go +++ b/pkg/asset/tls/keypair.go @@ -1,53 +1,65 @@ package tls import ( - "fmt" - "github.com/openshift/installer/pkg/asset" "github.com/pkg/errors" ) -// KeyPair implements the Asset interface and -// generates an RSA public/private key pair. -type KeyPair struct { - PrivKeyFileName string - PubKeyFileName string +// KeyPairInterface contains a private key and a public key. +type KeyPairInterface interface { + // Private returns the private key. + Private() []byte + // Public returns the public key. + Public() []byte } -var _ asset.Asset = (*KeyPair)(nil) - -// Dependencies returns the dependency of an rsa private / public key pair. -func (k *KeyPair) Dependencies() []asset.Asset { - return []asset.Asset{} +// KeyPair contains a private key and a public key. +type KeyPair struct { + private []byte + public []byte + files []*asset.File } // Generate generates the rsa private / public key pair. -func (k *KeyPair) Generate(map[asset.Asset]*asset.State) (*asset.State, error) { +func (k *KeyPair) Generate(filenameBase string) error { key, err := PrivateKey() if err != nil { - return nil, errors.Wrap(err, "failed to generate private key") + return errors.Wrap(err, "failed to generate private key") } pubkeyData, err := PublicKeyToPem(&key.PublicKey) if err != nil { - return nil, errors.Wrap(err, "failed to get public key data from private key") + return errors.Wrap(err, "failed to get public key data from private key") } - return &asset.State{ - Contents: []asset.Content{ - { - Name: assetFilePath(k.PrivKeyFileName), - Data: []byte(PrivateKeyToPem(key)), - }, - { - Name: assetFilePath(k.PubKeyFileName), - Data: []byte(pubkeyData), - }, + k.private = PrivateKeyToPem(key) + k.public = pubkeyData + + k.files = []*asset.File{ + { + Filename: assetFilePath(filenameBase + ".key"), + Data: k.private, + }, + { + Filename: assetFilePath(filenameBase + ".pub"), + Data: k.public, }, - }, nil + } + + return nil +} + +// Public returns the public key. +func (k *KeyPair) Public() []byte { + return k.public +} + +// Private returns the private key. +func (k *KeyPair) Private() []byte { + return k.private } -// Name returns the human-friendly name of the asset. -func (k *KeyPair) Name() string { - return fmt.Sprintf("Key Pair (%s)", k.PubKeyFileName) +// Files returns the files generated by the asset. +func (k *KeyPair) Files() []*asset.File { + return k.files } diff --git a/pkg/asset/tls/kubeca.go b/pkg/asset/tls/kubeca.go new file mode 100644 index 00000000000..d4818803174 --- /dev/null +++ b/pkg/asset/tls/kubeca.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +// KubeCA is the asset that generates the kube-ca key/cert pair. +type KubeCA struct { + CertKey +} + +var _ asset.Asset = (*KubeCA)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *KubeCA) Dependencies() []asset.Asset { + return []asset.Asset{ + &RootCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *KubeCA) Generate(dependencies asset.Parents) error { + rootCA := &RootCA{} + dependencies.Get(rootCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "kube-ca", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + IsCA: true, + } + + return a.CertKey.Generate(cfg, rootCA, "kube-ca", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *KubeCA) Name() string { + return "Certificate (kube-ca)" +} diff --git a/pkg/asset/tls/kubeletcertkey.go b/pkg/asset/tls/kubeletcertkey.go new file mode 100644 index 00000000000..b6d41889c96 --- /dev/null +++ b/pkg/asset/tls/kubeletcertkey.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +// KubeletCertKey is the asset that generates the kubelet key/cert pair. +type KubeletCertKey struct { + CertKey +} + +var _ asset.Asset = (*KubeletCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *KubeletCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &KubeCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *KubeletCertKey) Generate(dependencies asset.Parents) error { + kubeCA := &KubeCA{} + dependencies.Get(kubeCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "system:serviceaccount:kube-system:default", Organization: []string{"system:serviceaccounts:kube-system"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + Validity: ValidityThirtyMinutes, + } + + return a.CertKey.Generate(cfg, kubeCA, "kubelet", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *KubeletCertKey) Name() string { + return "Certificate (system:serviceaccount:kube-system:default)" +} diff --git a/pkg/asset/tls/mcscertkey.go b/pkg/asset/tls/mcscertkey.go new file mode 100644 index 00000000000..9463d6c2673 --- /dev/null +++ b/pkg/asset/tls/mcscertkey.go @@ -0,0 +1,49 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" +) + +// MCSCertKey is the asset that generates the MCS key/cert pair. +type MCSCertKey struct { + CertKey +} + +var _ asset.Asset = (*MCSCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *MCSCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &RootCA{}, + &installconfig.InstallConfig{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *MCSCertKey) Generate(dependencies asset.Parents) error { + rootCA := &RootCA{} + installConfig := &installconfig.InstallConfig{} + dependencies.Get(rootCA, installConfig) + + hostname := apiAddress(installConfig.Config) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: hostname}, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + Validity: ValidityTenYears, + DNSNames: []string{hostname}, + } + + return a.CertKey.Generate(cfg, rootCA, "machine-config-server", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *MCSCertKey) Name() string { + return "Certificate (mcs)" +} diff --git a/pkg/asset/tls/openshiftapiservercertkey.go b/pkg/asset/tls/openshiftapiservercertkey.go new file mode 100644 index 00000000000..c9caf9f7bb4 --- /dev/null +++ b/pkg/asset/tls/openshiftapiservercertkey.go @@ -0,0 +1,64 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + "net" + + "github.com/pkg/errors" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" +) + +// OpenshiftAPIServerCertKey is the asset that generates the Openshift API server key/cert pair. +type OpenshiftAPIServerCertKey struct { + CertKey +} + +var _ asset.Asset = (*OpenshiftAPIServerCertKey)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *OpenshiftAPIServerCertKey) Dependencies() []asset.Asset { + return []asset.Asset{ + &AggregatorCA{}, + &installconfig.InstallConfig{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *OpenshiftAPIServerCertKey) Generate(dependencies asset.Parents) error { + aggregatorCA := &AggregatorCA{} + installConfig := &installconfig.InstallConfig{} + dependencies.Get(aggregatorCA, installConfig) + + apiServerAddress, err := cidrhost(installConfig.Config.Networking.ServiceCIDR.IPNet, 1) + if err != nil { + return errors.Wrap(err, "failed to get API Server address from InstallConfig") + } + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "openshift-apiserver", Organization: []string{"kube-master"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + Validity: ValidityTenYears, + DNSNames: []string{ + apiAddress(installConfig.Config), + "openshift-apiserver", + "openshift-apiserver.kube-system", + "openshift-apiserver.kube-system.svc", + "openshift-apiserver.kube-system.svc.cluster.local", + "localhost", "127.0.0.1", + }, + IPAddresses: []net.IP{net.ParseIP(apiServerAddress)}, + } + + return a.CertKey.Generate(cfg, aggregatorCA, "openshift-apiserver", AppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *OpenshiftAPIServerCertKey) Name() string { + return "Certificate (openshift-apiserver)" +} diff --git a/pkg/asset/tls/root.go b/pkg/asset/tls/root.go index 32c989f4db5..8dadabc67cc 100644 --- a/pkg/asset/tls/root.go +++ b/pkg/asset/tls/root.go @@ -10,9 +10,11 @@ import ( // RootCA contains the private key and the cert that's // self-signed as the root CA. -type RootCA struct{} +type RootCA struct { + CertKey +} -var _ asset.Asset = (*CertKey)(nil) +var _ asset.WritableAsset = (*RootCA)(nil) // Dependencies returns the dependency of the root-ca, which is empty. func (c *RootCA) Dependencies() []asset.Asset { @@ -20,7 +22,7 @@ func (c *RootCA) Dependencies() []asset.Asset { } // Generate generates the root-ca key and cert pair. -func (c *RootCA) Generate(parents map[asset.Asset]*asset.State) (*asset.State, error) { +func (c *RootCA) Generate(parents asset.Parents) error { cfg := &CertCfg{ Subject: pkix.Name{CommonName: "root-ca", OrganizationalUnit: []string{"openshift"}}, KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, @@ -30,21 +32,15 @@ func (c *RootCA) Generate(parents map[asset.Asset]*asset.State) (*asset.State, e key, crt, err := GenerateRootCertKey(cfg) if err != nil { - return nil, errors.Wrap(err, "failed to generate RootCA") + return errors.Wrap(err, "failed to generate RootCA") } - return &asset.State{ - Contents: []asset.Content{ - { - Name: assetFilePath("root-ca.key"), - Data: []byte(PrivateKeyToPem(key)), - }, - { - Name: assetFilePath("root-ca.crt"), - Data: []byte(CertToPem(crt)), - }, - }, - }, nil + c.key = PrivateKeyToPem(key) + c.cert = CertToPem(crt) + + c.generateFiles("root-ca") + + return nil } // Name returns the human-friendly name of the asset. diff --git a/pkg/asset/tls/serviceaccountkeypair.go b/pkg/asset/tls/serviceaccountkeypair.go new file mode 100644 index 00000000000..8e0dca2d2bc --- /dev/null +++ b/pkg/asset/tls/serviceaccountkeypair.go @@ -0,0 +1,29 @@ +package tls + +import ( + "github.com/openshift/installer/pkg/asset" +) + +// ServiceAccountKeyPair is the asset that generates the service-account public/private key pair. +type ServiceAccountKeyPair struct { + KeyPair +} + +var _ asset.Asset = (*ServiceAccountKeyPair)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *ServiceAccountKeyPair) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *ServiceAccountKeyPair) Generate(dependencies asset.Parents) error { + return a.KeyPair.Generate("service-account") +} + +// Name returns the human-friendly name of the asset. +func (a *ServiceAccountKeyPair) Name() string { + return "Key Pair (service-account.pub)" +} diff --git a/pkg/asset/tls/serviceservingca.go b/pkg/asset/tls/serviceservingca.go new file mode 100644 index 00000000000..714140cc338 --- /dev/null +++ b/pkg/asset/tls/serviceservingca.go @@ -0,0 +1,44 @@ +package tls + +import ( + "crypto/x509" + "crypto/x509/pkix" + + "github.com/openshift/installer/pkg/asset" +) + +// ServiceServingCA is the asset that generates the service-serving-ca key/cert pair. +type ServiceServingCA struct { + CertKey +} + +var _ asset.Asset = (*ServiceServingCA)(nil) + +// Dependencies returns the dependency of the the cert/key pair, which includes +// the parent CA, and install config if it depends on the install config for +// DNS names, etc. +func (a *ServiceServingCA) Dependencies() []asset.Asset { + return []asset.Asset{ + &RootCA{}, + } +} + +// Generate generates the cert/key pair based on its dependencies. +func (a *ServiceServingCA) Generate(dependencies asset.Parents) error { + rootCA := &RootCA{} + dependencies.Get(rootCA) + + cfg := &CertCfg{ + Subject: pkix.Name{CommonName: "service-serving", OrganizationalUnit: []string{"bootkube"}}, + KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + Validity: ValidityTenYears, + IsCA: true, + } + + return a.CertKey.Generate(cfg, rootCA, "service-serving-ca", DoNotAppendParent) +} + +// Name returns the human-friendly name of the asset. +func (a *ServiceServingCA) Name() string { + return "Certificate (service-serving)" +} diff --git a/pkg/asset/tls/stock.go b/pkg/asset/tls/stock.go deleted file mode 100644 index 09473335b09..00000000000 --- a/pkg/asset/tls/stock.go +++ /dev/null @@ -1,293 +0,0 @@ -package tls - -import ( - "crypto/x509" - "crypto/x509/pkix" - - "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/installconfig" -) - -const ( - // KeyIndex is the index into a CertKey asset's contents at which the key - // can be found. - KeyIndex = 0 - - // CertIndex is the index into a CertKey asset's contents at which the - // certificate can be found. - CertIndex = 1 -) - -// Stock is the stock of TLS assets that can be generated. -type Stock interface { - // RootCA is the asset that generates the root-ca key/cert pair. - RootCA() asset.Asset - // KubeCA is the asset that generates the kube-ca key/cert pair. - KubeCA() asset.Asset - // EtcdCA is the asset that generates the etcd-ca key/cert pair. - EtcdCA() asset.Asset - // AggregatorCA is the asset that generates the aggregator-ca key/cert pair. - AggregatorCA() asset.Asset - // ServiceServingCA is the asset that generates the service-serving-ca key/cert pair. - ServiceServingCA() asset.Asset - // EtcdClientCertKey is the asset that generates the etcd client key/cert pair. - EtcdClientCertKey() asset.Asset - // AdminCertKey is the asset that generates the admin key/cert pair. - AdminCertKey() asset.Asset - // IngressCertKey is the asset that generates the ingress key/cert pair. - IngressCertKey() asset.Asset - // APIServerCertKey is the asset that generates the API server key/cert pair. - APIServerCertKey() asset.Asset - // OpenshiftAPIServerCertKey is the asset that generates the Openshift API server key/cert pair. - OpenshiftAPIServerCertKey() asset.Asset - // APIServerProxyCertKey is the asset that generates the API server proxy key/cert pair. - APIServerProxyCertKey() asset.Asset - // KubeletCertKey is the asset that generates the kubelet key/cert pair. - KubeletCertKey() asset.Asset - // MCSCertKey is the asset that generates the MCS key/cert pair. - MCSCertKey() asset.Asset - // ClusterAPIServerCertKey is the asset that generates the cluster API server key/cert pair. - ClusterAPIServerCertKey() asset.Asset - // ServiceAccountKeyPair is the asset that generates the service-account public/private key pair. - ServiceAccountKeyPair() asset.Asset -} - -// StockImpl implements the Stock interface for tls assets. -type StockImpl struct { - rootCA asset.Asset - kubeCA asset.Asset - etcdCA asset.Asset - aggregatorCA asset.Asset - serviceServingCA asset.Asset - etcdClientCertKey asset.Asset - adminCertKey asset.Asset - ingressCertKey asset.Asset - apiServerCertKey asset.Asset - openshiftAPIServerCertKey asset.Asset - apiServerProxyCertKey asset.Asset - kubeletCertKey asset.Asset - mcsCertKey asset.Asset - clusterAPIServerCertKey asset.Asset - serviceAccountKeyPair asset.Asset -} - -var _ Stock = (*StockImpl)(nil) - -// EstablishStock establishes the stock of assets. -func (s *StockImpl) EstablishStock(stock installconfig.Stock) { - s.rootCA = &RootCA{} - s.kubeCA = &CertKey{ - installConfig: stock.InstallConfig(), - Subject: pkix.Name{CommonName: "kube-ca", OrganizationalUnit: []string{"bootkube"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: ValidityTenYears, - KeyFileName: "kube-ca.key", - CertFileName: "kube-ca.crt", - - IsCA: true, - ParentCA: s.rootCA, - } - - s.etcdCA = &CertKey{ - installConfig: stock.InstallConfig(), - Subject: pkix.Name{CommonName: "etcd", OrganizationalUnit: []string{"etcd"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: ValidityTenYears, - KeyFileName: "etcd-client-ca.key", - CertFileName: "etcd-client-ca.crt", - - IsCA: true, - ParentCA: s.rootCA, - } - - s.aggregatorCA = &CertKey{ - installConfig: stock.InstallConfig(), - Subject: pkix.Name{CommonName: "aggregator", OrganizationalUnit: []string{"bootkube"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: ValidityTenYears, - KeyFileName: "aggregator-ca.key", - CertFileName: "aggregator-ca.crt", - - IsCA: true, - ParentCA: s.rootCA, - } - - s.serviceServingCA = &CertKey{ - installConfig: stock.InstallConfig(), - Subject: pkix.Name{CommonName: "service-serving", OrganizationalUnit: []string{"bootkube"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: ValidityTenYears, - KeyFileName: "service-serving-ca.key", - CertFileName: "service-serving-ca.crt", - - IsCA: true, - ParentCA: s.rootCA, - } - - s.etcdClientCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - Subject: pkix.Name{CommonName: "etcd", OrganizationalUnit: []string{"etcd"}}, - KeyUsages: x509.KeyUsageKeyEncipherment, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - Validity: ValidityTenYears, - KeyFileName: "etcd-client.key", - CertFileName: "etcd-client.crt", - - ParentCA: s.etcdCA, - } - - s.adminCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - Subject: pkix.Name{CommonName: "system:admin", Organization: []string{"system:masters"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - Validity: ValidityTenYears, - KeyFileName: "admin.key", - CertFileName: "admin.crt", - - ParentCA: s.kubeCA, - } - - s.ingressCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - Validity: ValidityTenYears, - KeyFileName: "ingress.key", - CertFileName: "ingress.crt", - - ParentCA: s.kubeCA, - AppendParent: true, - GenSubject: genSubjectForIngressCertKey, - GenDNSNames: genDNSNamesForIngressCertKey, - } - - s.apiServerCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - Subject: pkix.Name{CommonName: "kube-apiserver", Organization: []string{"kube-master"}}, - Validity: ValidityTenYears, - KeyFileName: "apiserver.key", - CertFileName: "apiserver.crt", - - ParentCA: s.kubeCA, - AppendParent: true, - GenDNSNames: genDNSNamesForAPIServerCertKey, - GenIPAddresses: genIPAddressesForAPIServerCertKey, - } - - s.openshiftAPIServerCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - Subject: pkix.Name{CommonName: "openshift-apiserver", Organization: []string{"kube-master"}}, - Validity: ValidityTenYears, - KeyFileName: "openshift-apiserver.key", - CertFileName: "openshift-apiserver.crt", - - ParentCA: s.aggregatorCA, - AppendParent: true, - GenDNSNames: genDNSNamesForOpenshiftAPIServerCertKey, - GenIPAddresses: genIPAddressesForOpenshiftAPIServerCertKey, - } - - s.apiServerProxyCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - Subject: pkix.Name{CommonName: "kube-apiserver-proxy", Organization: []string{"kube-master"}}, - Validity: ValidityTenYears, - KeyFileName: "apiserver-proxy.key", - CertFileName: "apiserver-proxy.crt", - - ParentCA: s.aggregatorCA, - } - - s.kubeletCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - Subject: pkix.Name{CommonName: "system:serviceaccount:kube-system:default", Organization: []string{"system:serviceaccounts:kube-system"}}, - Validity: ValidityThirtyMinutes, - KeyFileName: "kubelet.key", - CertFileName: "kubelet.crt", - - ParentCA: s.kubeCA, - } - - s.mcsCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - ExtKeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - Validity: ValidityTenYears, - KeyFileName: "machine-config-server.key", - CertFileName: "machine-config-server.crt", - - ParentCA: s.rootCA, - GenDNSNames: genDNSNamesForMCSCertKey, - GenSubject: genSubjectForMCSCertKey, - } - - s.clusterAPIServerCertKey = &CertKey{ - installConfig: stock.InstallConfig(), - Subject: pkix.Name{CommonName: "clusterapi.openshift-cluster-api.svc", OrganizationalUnit: []string{"bootkube"}}, - KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - Validity: ValidityTenYears, - KeyFileName: "cluster-apiserver-ca.key", - CertFileName: "cluster-apiserver-ca.crt", - IsCA: true, - - ParentCA: s.aggregatorCA, - AppendParent: true, - } - - s.serviceAccountKeyPair = &KeyPair{ - PrivKeyFileName: "service-account.key", - PubKeyFileName: "service-account.pub", - } -} - -// RootCA is the asset that generates the root-ca key/cert pair. -func (s *StockImpl) RootCA() asset.Asset { return s.rootCA } - -// KubeCA is the asset that generates the kube-ca key/cert pair. -func (s *StockImpl) KubeCA() asset.Asset { return s.kubeCA } - -// EtcdCA is the asset that generates the etcd-ca key/cert pair. -func (s *StockImpl) EtcdCA() asset.Asset { return s.etcdCA } - -// AggregatorCA is the asset that generates the aggregator-ca key/cert pair. -func (s *StockImpl) AggregatorCA() asset.Asset { return s.aggregatorCA } - -// ServiceServingCA is the asset that generates the service-serving-ca key/cert pair. -func (s *StockImpl) ServiceServingCA() asset.Asset { return s.serviceServingCA } - -// EtcdClientCertKey is the asset that generates the etcd client key/cert pair. -func (s *StockImpl) EtcdClientCertKey() asset.Asset { return s.etcdClientCertKey } - -// AdminCertKey is the asset that generates the admin key/cert pair. -func (s *StockImpl) AdminCertKey() asset.Asset { return s.adminCertKey } - -// IngressCertKey is the asset that generates the ingress key/cert pair. -func (s *StockImpl) IngressCertKey() asset.Asset { return s.ingressCertKey } - -// APIServerCertKey is the asset that generates the API server key/cert pair. -func (s *StockImpl) APIServerCertKey() asset.Asset { return s.apiServerCertKey } - -// OpenshiftAPIServerCertKey is the asset that generates the Openshift API server key/cert pair. -func (s *StockImpl) OpenshiftAPIServerCertKey() asset.Asset { return s.openshiftAPIServerCertKey } - -// APIServerProxyCertKey is the asset that generates the API server proxy key/cert pair. -func (s *StockImpl) APIServerProxyCertKey() asset.Asset { return s.apiServerProxyCertKey } - -// KubeletCertKey is the asset that generates the kubelet key/cert pair. -func (s *StockImpl) KubeletCertKey() asset.Asset { return s.kubeletCertKey } - -// MCSCertKey is the asset that generates the MCS key/cert pair. -func (s *StockImpl) MCSCertKey() asset.Asset { return s.mcsCertKey } - -// ClusterAPIServerCertKey is the asset that generates the cluster API server key/cert pair. -func (s *StockImpl) ClusterAPIServerCertKey() asset.Asset { return s.clusterAPIServerCertKey } - -// ServiceAccountKeyPair is the asset that generates the service-account public/private key pair. -func (s *StockImpl) ServiceAccountKeyPair() asset.Asset { return s.serviceAccountKeyPair } diff --git a/pkg/asset/tls/utils.go b/pkg/asset/tls/utils.go index c0414d8e90a..c374fcda086 100644 --- a/pkg/asset/tls/utils.go +++ b/pkg/asset/tls/utils.go @@ -9,7 +9,7 @@ import ( ) // PrivateKeyToPem converts an rsa.PrivateKey object to pem string -func PrivateKeyToPem(key *rsa.PrivateKey) string { +func PrivateKeyToPem(key *rsa.PrivateKey) []byte { keyInBytes := x509.MarshalPKCS1PrivateKey(key) keyinPem := pem.EncodeToMemory( &pem.Block{ @@ -17,36 +17,36 @@ func PrivateKeyToPem(key *rsa.PrivateKey) string { Bytes: keyInBytes, }, ) - return string(keyinPem) + return keyinPem } // CertToPem converts an x509.Certificate object to a pem string -func CertToPem(cert *x509.Certificate) string { +func CertToPem(cert *x509.Certificate) []byte { certInPem := pem.EncodeToMemory( &pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, }, ) - return string(certInPem) + return certInPem } // CSRToPem converts an x509.CertificateRequest to a pem string -func CSRToPem(cert *x509.CertificateRequest) string { +func CSRToPem(cert *x509.CertificateRequest) []byte { certInPem := pem.EncodeToMemory( &pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: cert.Raw, }, ) - return string(certInPem) + return certInPem } // PublicKeyToPem converts an rsa.PublicKey object to pem string -func PublicKeyToPem(key *rsa.PublicKey) (string, error) { +func PublicKeyToPem(key *rsa.PublicKey) ([]byte, error) { keyInBytes, err := x509.MarshalPKIXPublicKey(key) if err != nil { - return "", errors.Wrap(err, "failed to MarshalPKIXPublicKey") + return nil, errors.Wrap(err, "failed to MarshalPKIXPublicKey") } keyinPem := pem.EncodeToMemory( &pem.Block{ @@ -54,17 +54,23 @@ func PublicKeyToPem(key *rsa.PublicKey) (string, error) { Bytes: keyInBytes, }, ) - return string(keyinPem), nil + return keyinPem, nil } // PemToPrivateKey converts a data block to rsa.PrivateKey. func PemToPrivateKey(data []byte) (*rsa.PrivateKey, error) { block, _ := pem.Decode(data) + if block == nil { + return nil, errors.Errorf("could not find a PEM block in the private key") + } return x509.ParsePKCS1PrivateKey(block.Bytes) } // PemToCertificate converts a data block to x509.Certificate. func PemToCertificate(data []byte) (*x509.Certificate, error) { block, _ := pem.Decode(data) + if block == nil { + return nil, errors.Errorf("could not find a PEM block in the certificate") + } return x509.ParseCertificate(block.Bytes) } diff --git a/pkg/asset/userprovided.go b/pkg/asset/userprovided.go index c6093e2ffb5..0251982125b 100644 --- a/pkg/asset/userprovided.go +++ b/pkg/asset/userprovided.go @@ -8,59 +8,43 @@ import ( survey "gopkg.in/AlecAivazis/survey.v1" ) -// UserProvided generates an asset that is supplied by a user. -type UserProvided struct { - AssetName string - Question *survey.Question - EnvVarName string - PathEnvVarName string +// GenerateUserProvidedAsset queries for input from the user. +func GenerateUserProvidedAsset(inputName string, question *survey.Question, envVarName string) (string, error) { + return generateUserProvidedAsset(inputName, question, envVarName, "") } -var _ Asset = (*UserProvided)(nil) - -// Dependencies returns no dependencies. -func (a *UserProvided) Dependencies() []Asset { - return []Asset{} +// GenerateUserProvidedAssetForPath queries for input from the user. The input can +// be read from a file specified in an environment variable. +func GenerateUserProvidedAssetForPath(inputName string, question *survey.Question, envVarName, pathEnvVarName string) (string, error) { + return generateUserProvidedAsset(inputName, question, envVarName, pathEnvVarName) } -// Generate queries for input from the user. -func (a *UserProvided) Generate(map[Asset]*State) (state *State, err error) { +func generateUserProvidedAsset(inputName string, question *survey.Question, envVarName, pathEnvVarName string) (response string, err error) { defer func() { if err != nil { - err = errors.Wrapf(err, "failed to acquire user-provided input %s", a.AssetName) + err = errors.Wrapf(err, "failed to acquire user-provided input %s", inputName) } }() - var response string - - if value, ok := os.LookupEnv(a.EnvVarName); ok { + if value, ok := os.LookupEnv(envVarName); ok { response = value - } else if path, ok := os.LookupEnv(a.PathEnvVarName); ok { + } else if path, ok := os.LookupEnv(pathEnvVarName); ok { value, err := ioutil.ReadFile(path) if err != nil { - return nil, errors.Wrapf(err, "failed to read file from %s", a.PathEnvVarName) + return "", errors.Wrapf(err, "failed to read file from %s", pathEnvVarName) } response = string(value) } if response == "" { - if err := survey.Ask([]*survey.Question{a.Question}, &response); err != nil { - return nil, errors.Wrap(err, "failed to Ask") + if err := survey.Ask([]*survey.Question{question}, &response); err != nil { + return "", errors.Wrap(err, "failed to Ask") } - } else if a.Question.Validate != nil { - if err := a.Question.Validate(response); err != nil { - return nil, errors.Wrap(err, "validation failed") + } else if question.Validate != nil { + if err := question.Validate(response); err != nil { + return "", errors.Wrap(err, "validation failed") } } - return &State{ - Contents: []Content{{ - Data: []byte(response), - }}, - }, nil -} - -// Name returns the human-friendly name of the asset. -func (a *UserProvided) Name() string { - return a.AssetName + return response, nil }