Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cmd/node-joiner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ func main() {
Use: "add-nodes",
Short: "Generates an ISO that could be used to boot the configured nodes to let them join an existing cluster",
RunE: func(cmd *cobra.Command, args []string) error {
return nodejoiner.NewAddNodesCommand(wd)
kubeConfig, err := cmd.Flags().GetString("kubeconfig")
if err != nil {
return err
}
return nodejoiner.NewAddNodesCommand(wd, kubeConfig)
},
}

Expand All @@ -34,6 +38,7 @@ func main() {
rootCmd := &cobra.Command{
Use: "node-joiner",
}
rootCmd.PersistentFlags().String("kubeconfig", "", "Path to the kubeconfig file.")

rootCmd.AddCommand(nodesAddCmd)
rootCmd.AddCommand(nodesMonitorCmd)
Expand Down
80 changes: 80 additions & 0 deletions pkg/asset/agent/joiner/addnodesconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package joiner

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/openshift/installer/pkg/asset"
)

const (
addNodesParamsFile = ".addnodesparams"
)

// AddNodesConfig is used to store the current configuration
// for the command.
type AddNodesConfig struct {
File *asset.File
Params Params
}

// Params is used to store the command line parameters.
type Params struct {
Kubeconfig string `json:"kubeconfig,omitempty"`
}

// Save stores the current parameters on disk.
func (p *Params) Save(assetsDir string) error {
data, err := json.Marshal(p)
if err != nil {
return err
}

fileName := filepath.Join(assetsDir, addNodesParamsFile)
return os.WriteFile(fileName, data, 0o600)
}

// Name returns the human-friendly name of the asset.
func (*AddNodesConfig) Name() string {
return "AddNodes Config"
}

// Dependencies returns all of the dependencies directly needed to generate
// the asset.
func (*AddNodesConfig) Dependencies() []asset.Asset {
return []asset.Asset{}
}

// Generate it's empty for this asset, always loaded from disk.
func (*AddNodesConfig) Generate(dependencies asset.Parents) error {
return nil
}

// Files returns the files generated by the asset.
func (a *AddNodesConfig) Files() []*asset.File {
if a.File != nil {
return []*asset.File{a.File}
}
return []*asset.File{}
}

// Load returns agent config asset from the disk.
func (a *AddNodesConfig) Load(f asset.FileFetcher) (bool, error) {
file, err := f.FetchByName(addNodesParamsFile)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, fmt.Errorf("failed to load %s file: %w", addNodesParamsFile, err)
}

params := &Params{}
if err := json.Unmarshal(file.Data, params); err != nil {
return false, fmt.Errorf("failed to unmarshal %s: %w", addNodesParamsFile, err)
}

a.Params = *params
return true, nil
}
135 changes: 135 additions & 0 deletions pkg/asset/agent/joiner/clusterinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package joiner

import (
"context"
"net/url"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"

configclient "github.com/openshift/client-go/config/clientset/versioned"
"github.com/openshift/installer/pkg/asset"
"github.com/openshift/installer/pkg/asset/agent/workflow"
)

// ClusterInfo it's an asset used to retrieve config info
// from an already existing cluster.
type ClusterInfo struct {
ClusterID string
APIDNSName string
PullSecret string
}

var _ asset.WritableAsset = (*ClusterInfo)(nil)

// Name returns the human-friendly name of the asset.
func (ci *ClusterInfo) Name() string {
return "Agent Installer ClusterInfo"
}

// Dependencies returns all of the dependencies directly needed to generate
// the asset.
func (*ClusterInfo) Dependencies() []asset.Asset {
return []asset.Asset{
&workflow.AgentWorkflow{},
&AddNodesConfig{},
}
}

// Generate generates the ClusterInfo.
func (ci *ClusterInfo) Generate(dependencies asset.Parents) error {
agentWorkflow := &workflow.AgentWorkflow{}
addNodesConfig := &AddNodesConfig{}
dependencies.Get(agentWorkflow, addNodesConfig)

if agentWorkflow.Workflow != workflow.AgentWorkflowTypeAddNodes {
return nil
}

config, err := ci.getRestConfig(addNodesConfig.Params.Kubeconfig)
if err != nil {
return err
}

err = ci.retrieveClusterID(config)
if err != nil {
return err
}

err = ci.retrieveAPIDNSName(config)
if err != nil {
return err
}

err = ci.retrievePullSecret(config)
if err != nil {
return err
}

return nil
}

func (ci *ClusterInfo) getRestConfig(kubeconfig string) (*rest.Config, error) {
var err error
var config *rest.Config

if kubeconfig != "" {
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
} else {
config, err = rest.InClusterConfig()
}

return config, err
}

func (ci *ClusterInfo) retrieveClusterID(config *rest.Config) error {
clientset, err := configclient.NewForConfig(config)
if err != nil {
return err
}

cv, err := clientset.ConfigV1().ClusterVersions().Get(context.Background(), "version", metav1.GetOptions{})
if err != nil {
return err
}
ci.ClusterID = string(cv.Spec.ClusterID)

return nil
}

func (ci *ClusterInfo) retrieveAPIDNSName(config *rest.Config) error {
parsedURL, err := url.Parse(config.Host)
if err != nil {
return err
}

ci.APIDNSName = parsedURL.Hostname()
return nil
}

func (ci *ClusterInfo) retrievePullSecret(config *rest.Config) error {
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}

pullSecret, err := clientset.CoreV1().Secrets("openshift-config").Get(context.Background(), "pull-secret", metav1.GetOptions{})
if err != nil {
return err
}
ci.PullSecret = string(pullSecret.Data[".dockerconfigjson"])

return nil
}

// Files returns the files generated by the asset.
func (*ClusterInfo) Files() []*asset.File {
return []*asset.File{}
}

// Load returns agent config asset from the disk.
func (*ClusterInfo) Load(f asset.FileFetcher) (bool, error) {
return false, nil
}
65 changes: 65 additions & 0 deletions pkg/asset/agent/workflow/agentworkflow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package workflow

import (
"fmt"
"os"

"github.com/openshift/installer/pkg/asset"
)

// AgentWorkflow allows other assets to check
// which is the workflow currently active.
type AgentWorkflow struct {
File *asset.File
Workflow AgentWorkflowType
}

var _ asset.WritableAsset = (*AgentWorkflow)(nil)

// Name returns a human friendly name for the asset.
func (*AgentWorkflow) Name() string {
return "Agent Workflow"
}

// Dependencies returns all of the dependencies directly needed to generate
// the asset.
func (*AgentWorkflow) Dependencies() []asset.Asset {
return []asset.Asset{}
}

// Generate generates the AgentWorkflow asset.
func (a *AgentWorkflow) Generate(dependencies asset.Parents) error {
// Set install workflow as a default
a.Workflow = AgentWorkflowTypeInstall
a.File = &asset.File{
Filename: agentWorkflowFilename,
Data: []byte(a.Workflow),
}

return nil
}

// Files returns the files generated by the asset.
func (a *AgentWorkflow) Files() []*asset.File {
if a.File != nil {
return []*asset.File{a.File}
}
return []*asset.File{}
}

// Load returns the asset from disk.
func (a *AgentWorkflow) Load(f asset.FileFetcher) (bool, error) {
file, err := f.FetchByName(agentWorkflowFilename)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, fmt.Errorf("failed to load %s file: %w", agentWorkflowFilename, err)
}

// Get the current workflow
a.Workflow = AgentWorkflowType(file.Data)
a.File = file

return true, nil
}
27 changes: 27 additions & 0 deletions pkg/asset/agent/workflow/agentworkflowaddnodes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package workflow

import "github.com/openshift/installer/pkg/asset"

// AgentWorkflowAddNodes is meant just to define
// the add nodes workflow.
type AgentWorkflowAddNodes struct {
AgentWorkflow
}

var _ asset.WritableAsset = (*AgentWorkflowAddNodes)(nil)

// Name returns a human friendly name for the asset.
func (*AgentWorkflowAddNodes) Name() string {
return "Agent Workflow Add Nodes"
}

// Generate generates the AgentWorkflow asset.
func (a *AgentWorkflowAddNodes) Generate(dependencies asset.Parents) error {
a.Workflow = AgentWorkflowTypeAddNodes
a.File = &asset.File{
Filename: agentWorkflowFilename,
Data: []byte(a.Workflow),
}

return nil
}
14 changes: 14 additions & 0 deletions pkg/asset/agent/workflow/commons.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package workflow

// AgentWorkflowType defines the supported
// agent workflows.
type AgentWorkflowType string

const (
// AgentWorkflowTypeInstall identifies the install workflow.
AgentWorkflowTypeInstall AgentWorkflowType = "install"
// AgentWorkflowTypeAddNodes identifies the add nodes workflow.
AgentWorkflowTypeAddNodes AgentWorkflowType = "addnodes"

agentWorkflowFilename = ".agentworkflow"
)
19 changes: 17 additions & 2 deletions pkg/nodejoiner/addnodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@ package nodejoiner

import (
"github.com/openshift/installer/pkg/asset"
"github.com/openshift/installer/pkg/asset/agent/joiner"
"github.com/openshift/installer/pkg/asset/agent/workflow"
"github.com/openshift/installer/pkg/asset/store"
)

// NewAddNodesCommand creates a new command for add nodes.
func NewAddNodesCommand(directory string) error {
func NewAddNodesCommand(directory string, kubeConfig string) error {
// Store the current parameters into the assets folder, so
// that they could be retrieved later by the assets
params := joiner.Params{
Kubeconfig: kubeConfig,
}
err := params.Save(directory)
if err != nil {
return err
}

fetcher := store.NewAssetsFetcher(directory)
return fetcher.FetchAndPersist([]asset.WritableAsset{})
return fetcher.FetchAndPersist([]asset.WritableAsset{
&workflow.AgentWorkflowAddNodes{},
// To be completed
})
}