diff --git a/cmd/openshift-install/main.go b/cmd/openshift-install/main.go index 07e32e1575e..1033edbd5e9 100644 --- a/cmd/openshift-install/main.go +++ b/cmd/openshift-install/main.go @@ -13,6 +13,7 @@ import ( var ( installConfigCommand = kingpin.Command("install-config", "Generate the Install Config asset") ignitionConfigsCommand = kingpin.Command("ignition-configs", "Generate the Ignition Config assets") + operatorsCommand = kingpin.Command("operators", "Generate the Operator assets") dirFlag = kingpin.Flag("dir", "assets directory").Default(".").String() logLevel = kingpin.Flag("log-level", "log level (e.g. \"debug\")").Default("info").Enum("debug", "info", "warn", "error", "fatal", "panic") @@ -33,6 +34,10 @@ func main() { assetStock.MasterIgnition(), assetStock.WorkerIgnition(), } + case operatorsCommand.FullCommand(): + targetAssets = []asset.Asset{ + assetStock.Operators(), + } } l, err := log.ParseLevel(*logLevel) diff --git a/pkg/asset/operators/kube-addon-operator.go b/pkg/asset/operators/kube-addon-operator.go new file mode 100644 index 00000000000..6f1ca0aa747 --- /dev/null +++ b/pkg/asset/operators/kube-addon-operator.go @@ -0,0 +1,110 @@ +package operators + +import ( + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "path/filepath" + + "github.com/ghodss/yaml" + + 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" +) + +// kubeAddonOperator generates the network-operator-*.yml files +type kubeAddonOperator struct { + installConfigAsset asset.Asset + installConfig *types.InstallConfig + directory string +} + +var _ asset.Asset = (*kubeAddonOperator)(nil) + +// Dependencies returns all of the dependencies directly needed by an +// kubeAddonOperator asset. +func (kao *kubeAddonOperator) Dependencies() []asset.Asset { + return []asset.Asset{ + kao.installConfigAsset, + } +} + +// 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, err + } + kao.installConfig = ic + + // installconfig is ready, we can create the addon config from it now + addonConfig, err := kao.addonConfig() + if err != nil { + return nil, err + } + + addonManifest, err := kao.manifest() + if err != nil { + return nil, err + } + state := &asset.State{ + Contents: []asset.Content{ + { + Name: filepath.Join(kao.directory, "kube-addon-operator-config.yml"), + Data: addonConfig, + }, + { + Name: filepath.Join(kao.directory, "kube-addon-operator-manifests.yml"), + Data: []byte(addonManifest), + }, + }, + } + 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() + registrySecret, err := generateRandomID(16) + if err != nil { + return nil, err + } + addonConfig.RegistryHTTPSecret = registrySecret + return yaml.Marshal(addonConfig) +} + +func (kao *kubeAddonOperator) manifest() (string, error) { + return "", nil +} + +func (kao *kubeAddonOperator) getAPIServerURL() string { + return fmt.Sprintf("https://%s-api.%s:6443", kao.installConfig.ClusterName, kao.installConfig.BaseDomain) +} + +// generateRandomID reproduce tf random_id behaviour +// TODO: re-evaluate solution +func generateRandomID(byteLength int) (string, error) { + bytes := make([]byte, byteLength) + + n, err := rand.Reader.Read(bytes) + if n != byteLength { + return "", errors.New("generated insufficient random bytes") + } + if err != nil { + return "", err + } + + b64Str := base64.RawURLEncoding.EncodeToString(bytes) + + return b64Str, nil +} diff --git a/pkg/asset/operators/kube-core-operator.go b/pkg/asset/operators/kube-core-operator.go new file mode 100644 index 00000000000..c231c0f846b --- /dev/null +++ b/pkg/asset/operators/kube-core-operator.go @@ -0,0 +1,129 @@ +package operators + +import ( + "fmt" + "net" + "path/filepath" + + "github.com/ghodss/yaml" + + "github.com/apparentlymart/go-cidr/cidr" + kubecore "github.com/coreos/tectonic-config/config/kube-core" + "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" +) + +const ( + authConfigOIDCClientID = "tectonic-kubectl" + authConfigOIDCGroupsClaim = "groups" + authConfigOIDCUsernameClaim = "email" + networkConfigAdvertiseAddress = "0.0.0.0" +) + +// kubeCoreOperator generates the kube-core-operator.yaml files +type kubeCoreOperator struct { + installConfigAsset asset.Asset + installConfig *types.InstallConfig + directory string +} + +var _ asset.Asset = (*kubeCoreOperator)(nil) + +// Dependencies returns all of the dependencies directly needed by an +// kubeCoreOperator asset. +func (kco *kubeCoreOperator) Dependencies() []asset.Asset { + return []asset.Asset{ + kco.installConfigAsset, + } +} + +// 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, err + } + kco.installConfig = ic + + // installconfig is ready, we can create the core config from it now + coreConfig, err := kco.coreConfig() + if err != nil { + return nil, err + } + + data, err := yaml.Marshal(coreConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal core config: %v", err) + } + state := &asset.State{ + Contents: []asset.Content{ + { + Name: filepath.Join(kco.directory, "kube-core-operator-config.yml"), + Data: data, + }, + }, + } + return state, nil +} + +func (kco *kubeCoreOperator) coreConfig() (*kubecore.OperatorConfig, error) { + coreConfig := kubecore.OperatorConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kubecore.APIVersion, + Kind: kubecore.Kind, + }, + } + coreConfig.ClusterConfig.APIServerURL = kco.getAPIServerURL() + coreConfig.AuthConfig.OIDCClientID = authConfigOIDCClientID + coreConfig.AuthConfig.OIDCIssuerURL = kco.getOicdIssuerURL() + coreConfig.AuthConfig.OIDCGroupsClaim = authConfigOIDCGroupsClaim + coreConfig.AuthConfig.OIDCUsernameClaim = authConfigOIDCUsernameClaim + + svcCidr := kco.installConfig.Networking.ServiceCIDR + ip, err := cidr.Host(&net.IPNet{IP: svcCidr.IP, Mask: svcCidr.Mask}, 10) + if err != nil { + return nil, err + } + 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 = kco.getEtcdServersURLs() + + return &coreConfig, nil +} + +func (kco *kubeCoreOperator) getAPIServerURL() string { + return fmt.Sprintf("https://%s-api.%s:6443", kco.installConfig.ClusterName, kco.installConfig.BaseDomain) +} + +func (kco *kubeCoreOperator) getEtcdServersURLs() string { + return fmt.Sprintf("https://%s-etcd.%s:2379", kco.installConfig.ClusterName, kco.installConfig.BaseDomain) +} + +func (kco *kubeCoreOperator) getOicdIssuerURL() string { + return fmt.Sprintf("https://%s.%s/identity", kco.installConfig.ClusterName, kco.installConfig.BaseDomain) +} + +func (kco *kubeCoreOperator) getBaseAddress() string { + return fmt.Sprintf("%s.%s", kco.installConfig.ClusterName, kco.installConfig.BaseDomain) +} + +// Converts a platform to the cloudProvider that k8s understands +func k8sCloudProvider(platform types.Platform) string { + if platform.AWS != nil { + return "aws" + } + if platform.Libvirt != nil { + //return "libvirt" + } + return "" +} diff --git a/pkg/asset/operators/network-operator.go b/pkg/asset/operators/network-operator.go new file mode 100644 index 00000000000..c7cd46e7a05 --- /dev/null +++ b/pkg/asset/operators/network-operator.go @@ -0,0 +1,88 @@ +package operators + +import ( + "path/filepath" + + "github.com/ghodss/yaml" + + "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" + //"github.com/openshift/openshift-network-operator/pkg/apis/networkoperator" +) + +const ( + defaultMTU = "1450" +) + +// networkOperator generates the network-operator-*.yml files +type networkOperator struct { + installConfigAsset asset.Asset + installConfig *types.InstallConfig + directory string +} + +var _ asset.Asset = (*networkOperator)(nil) + +// Dependencies returns all of the dependencies directly needed by an +// networkOperator asset. +func (no *networkOperator) Dependencies() []asset.Asset { + return []asset.Asset{ + no.installConfigAsset, + } +} + +// 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, err + } + no.installConfig = ic + + // installconfig is ready, we can create the core config from it now + netConfig, err := no.netConfig() + if err != nil { + return nil, err + } + + netManifest, err := no.manifest() + if err != nil { + return nil, err + } + state := &asset.State{ + Contents: []asset.Content{ + { + Name: filepath.Join(no.directory, "network-operator-config.yml"), + Data: netConfig, + }, + { + Name: filepath.Join(no.directory, "network-operator-manifests.yml"), + Data: netManifest, + }, + }, + } + return state, nil +} + +func (no *networkOperator) netConfig() ([]byte, error) { + networkConfig := tectonicnetwork.OperatorConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: tectonicnetwork.APIVersion, + Kind: tectonicnetwork.Kind, + }, + } + + networkConfig.PodCIDR = no.installConfig.Networking.PodCIDR.String() + networkConfig.CalicoConfig.MTU = defaultMTU + networkConfig.NetworkProfile = tectonicnetwork.NetworkType(no.installConfig.Networking.Type) + + return yaml.Marshal(networkConfig) +} + +func (no *networkOperator) manifest() ([]byte, error) { + return []byte(""), nil +} diff --git a/pkg/asset/operators/operators.go b/pkg/asset/operators/operators.go new file mode 100644 index 00000000000..3086f2cd7df --- /dev/null +++ b/pkg/asset/operators/operators.go @@ -0,0 +1,70 @@ +// Package operators deals with creating assets for all operators to be installed for the cluster +package operators + +import ( + "github.com/openshift/installer/pkg/asset" + "path/filepath" +) + +// operators generates the dependent operator config.yaml files +type operators struct { + assetStock Stock + directory string +} + +var _ asset.Asset = (*operators)(nil) + +type genericData map[string]string + +// Dependencies returns all of the dependencies directly needed by an +// operators asset. +func (o *operators) Dependencies() []asset.Asset { + return []asset.Asset{ + o.assetStock.KubeCoreOperator(), + o.assetStock.NetworkOperator(), + o.assetStock.Tnco(), + o.assetStock.KubeAddonOperator(), + } +} + +// Generate generates the respective operator config.yml files +func (o *operators) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { + //cvo := dependencies[o.assetStock.ClusterVersionOperator()].Contents[0] + kco := dependencies[o.assetStock.KubeCoreOperator()].Contents[0] + no := dependencies[o.assetStock.NetworkOperator()].Contents[0] + tnco := dependencies[o.assetStock.Tnco()].Contents[0] + //ingress := dependencies[o.assetStock.IngressOperator()].Contents[0] + addon := dependencies[o.assetStock.KubeAddonOperator()].Contents[0] + + // kco+no+tnco 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), + "tnco-config": string(tnco.Data), + }) + if err != nil { + return nil, err + } + + // addon goes to openshift system + openshiftSys, err := configMap("openshift-system", "cluster-config-v1", genericData{ + "addon-config": string(addon.Data), + }) + if err != nil { + return nil, err + } + + state := &asset.State{ + Contents: []asset.Content{ + { + Name: filepath.Join(o.directory, "cluster-config.yml"), + Data: []byte(kubeSys), + }, + { + Name: filepath.Join(o.directory, "openshift-config.yml"), + Data: []byte(openshiftSys), + }, + }, + } + return state, nil +} diff --git a/pkg/asset/operators/stock.go b/pkg/asset/operators/stock.go new file mode 100644 index 00000000000..62ed389f13a --- /dev/null +++ b/pkg/asset/operators/stock.go @@ -0,0 +1,80 @@ +package operators + +import ( + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" +) + +// Stock is the stock of operator assets that can be generated. +type Stock interface { + // 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 + + // Tnco returns the TNCO asset object + Tnco() asset.Asset + + // KubeAddonOperator returns the addon asset object + KubeAddonOperator() asset.Asset +} + +// StockImpl implements the Stock interface for operators +type StockImpl struct { + operators asset.Asset + clusterVersionOperator asset.Asset + kubeCoreOperator asset.Asset + networkOperator asset.Asset + tnco asset.Asset + addonOperator asset.Asset +} + +var _ Stock = (*StockImpl)(nil) + +// EstablishStock establishes the stock of assets in the specified directory. +func (s *StockImpl) EstablishStock(rootDir string, stock installconfig.Stock) { + s.operators = &operators{ + assetStock: s, + directory: rootDir, + } + s.kubeCoreOperator = &kubeCoreOperator{ + installConfigAsset: stock.InstallConfig(), + directory: rootDir, + } + s.addonOperator = &kubeAddonOperator{ + installConfigAsset: stock.InstallConfig(), + directory: rootDir, + } + s.tnco = &tectonicNodeControllerOperator{ + installConfigAsset: stock.InstallConfig(), + directory: rootDir, + } + s.networkOperator = &networkOperator{ + installConfigAsset: stock.InstallConfig(), + directory: rootDir, + } + // TODO: + //s.clusterVersionOperator = &clusterVersionOperator{} +} + +// Operators returns the operators asset +func (s *StockImpl) Operators() asset.Asset { return s.operators } + +// 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 } + +// Tnco returns the tnc operator asset object +func (s *StockImpl) Tnco() asset.Asset { return s.tnco } diff --git a/pkg/asset/operators/tnco.go b/pkg/asset/operators/tnco.go new file mode 100644 index 00000000000..ebeb036b93b --- /dev/null +++ b/pkg/asset/operators/tnco.go @@ -0,0 +1,90 @@ +package operators + +import ( + "net" + "path/filepath" + + "github.com/ghodss/yaml" + + "github.com/apparentlymart/go-cidr/cidr" + tnc "github.com/coreos/tectonic-config/config/tectonic-node-controller" + "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" +) + +// tectonicNodeControllerOperator generates the tnco-operator.yaml files +type tectonicNodeControllerOperator struct { + installConfigAsset asset.Asset + installConfig *types.InstallConfig + directory string +} + +var _ asset.Asset = (*tectonicNodeControllerOperator)(nil) + +// Dependencies returns all of the dependencies directly needed by an +// tectonicNodeControllerOperator asset. +func (tnco *tectonicNodeControllerOperator) Dependencies() []asset.Asset { + return []asset.Asset{ + tnco.installConfigAsset, + } +} + +// Generate generates the tnco-operator-config.yml files +func (tnco *tectonicNodeControllerOperator) Generate(dependencies map[asset.Asset]*asset.State) (*asset.State, error) { + ic, err := installconfig.GetInstallConfig(tnco.installConfigAsset, dependencies) + if err != nil { + return nil, err + } + tnco.installConfig = ic + + // installconfig is ready, we can create the tnco config from it now + tncoConfig, err := tnco.tncoConfig() + if err != nil { + return nil, err + } + + state := &asset.State{ + Contents: []asset.Content{ + { + Name: filepath.Join(tnco.directory, "tnco-config.yml"), + Data: tncoConfig, + }, + }, + } + return state, nil +} + +func (tnco *tectonicNodeControllerOperator) tncoConfig() ([]byte, error) { + tncoConfig := tnc.OperatorConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: tnc.TNCOConfigAPIVersion, + Kind: tnc.TNCOConfigKind, + }, + } + + tncoConfig.ControllerConfig = tnc.ControllerConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: tnc.TNCConfigAPIVersion, + Kind: tnc.TNCConfigKind, + }, + } + + svcCidr := tnco.installConfig.Networking.ServiceCIDR + ip, err := cidr.Host(&net.IPNet{IP: svcCidr.IP, Mask: svcCidr.Mask}, 10) + if err != nil { + return nil, err + } + tncoConfig.ControllerConfig.ClusterDNSIP = ip.String() + + tncoConfig.ControllerConfig.Platform = tectonicCloudProvider(tnco.installConfig.Platform) + tncoConfig.ControllerConfig.CloudProviderConfig = "" // TODO(yifan): Get CloudProviderConfig. + tncoConfig.ControllerConfig.ClusterName = tnco.installConfig.ClusterName + tncoConfig.ControllerConfig.BaseDomain = tnco.installConfig.BaseDomain + tncoConfig.ControllerConfig.EtcdInitialCount = 1 // TODO (rchopra): confirmed? + tncoConfig.ControllerConfig.AdditionalConfigs = []string{} // TODO(yifan): Get additional configs. + tncoConfig.ControllerConfig.NodePoolUpdateLimit = nil // TODO(yifan): Get the node pool update limit. + + return yaml.Marshal(tncoConfig) +} diff --git a/pkg/asset/operators/utils.go b/pkg/asset/operators/utils.go new file mode 100644 index 00000000000..2c8eff132bd --- /dev/null +++ b/pkg/asset/operators/utils.go @@ -0,0 +1,59 @@ +package operators + +import ( + "github.com/ghodss/yaml" + "github.com/openshift/installer/pkg/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type configurationObject struct { + metav1.TypeMeta + + Metadata metadata `json:"metadata,omitempty"` + Data genericData `json:"data,omitempty"` +} + +type metadata struct { + Name string `json:"name,omitempty"` + Namespace string `json:"namespace,omitempty"` +} + +func configMap(namespace, name string, data genericData) (string, error) { + configurationObject := configurationObject{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + Metadata: metadata{ + Name: name, + Namespace: namespace, + }, + 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 +func tectonicCloudProvider(platform types.Platform) string { + if platform.AWS != nil { + return "aws" + } + if platform.Libvirt != nil { + return "libvirt" + } + return "" +} diff --git a/pkg/asset/stock/stock.go b/pkg/asset/stock/stock.go index 9b5c49434ba..93adb37ee71 100644 --- a/pkg/asset/stock/stock.go +++ b/pkg/asset/stock/stock.go @@ -7,6 +7,7 @@ import ( "github.com/openshift/installer/pkg/asset/ignition" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/kubeconfig" + "github.com/openshift/installer/pkg/asset/operators" "github.com/openshift/installer/pkg/asset/tls" ) @@ -16,6 +17,7 @@ type Stock struct { kubeconfigStock tlsStock ignitionStock + operatorStock } type installConfigStock struct { @@ -34,6 +36,10 @@ type ignitionStock struct { ignition.StockImpl } +type operatorStock struct { + operators.StockImpl +} + var _ installconfig.Stock = (*Stock)(nil) // EstablishStock establishes the stock of assets in the specified directory. @@ -44,6 +50,7 @@ func EstablishStock(directory string) *Stock { s.tlsStock.EstablishStock(directory, &s.installConfigStock) s.kubeconfigStock.EstablishStock(directory, &s.installConfigStock, &s.tlsStock) s.ignitionStock.EstablishStock(directory, s, s, s) + s.operatorStock.EstablishStock(directory, &s.installConfigStock) return s }