github.com/caos/orbos@v1.5.14-0.20221103111702-e6cd0cea7ad4/pkg/orb/orb.go (about)

     1  package orb
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"gopkg.in/yaml.v3"
    14  
    15  	"github.com/caos/orbos/internal/helpers"
    16  	"github.com/caos/orbos/internal/ssh"
    17  	"github.com/caos/orbos/internal/stores/github"
    18  	"github.com/caos/orbos/mntr"
    19  	"github.com/caos/orbos/pkg/git"
    20  	"github.com/caos/orbos/pkg/secret"
    21  )
    22  
    23  var alphanum = regexp.MustCompile("[^a-zA-Z0-9]+")
    24  
    25  type Orb struct {
    26  	id        string `yaml:"-"`
    27  	Path      string `yaml:"-"`
    28  	URL       string
    29  	Repokey   string
    30  	Masterkey string
    31  }
    32  
    33  func (o *Orb) IsConnectable() (err error) {
    34  	defer func() {
    35  		if err != nil {
    36  			err = mntr.ToUserError(fmt.Errorf("repository is not connectable: %w", err))
    37  		}
    38  	}()
    39  	if o.URL == "" {
    40  		err = helpers.Concat(err, errors.New("repository url is missing"))
    41  	}
    42  
    43  	if o.Repokey == "" {
    44  		err = helpers.Concat(err, errors.New("repository key is missing"))
    45  	}
    46  	return err
    47  }
    48  
    49  func IsComplete(o *Orb) (err error) {
    50  
    51  	defer func() {
    52  		if err != nil {
    53  			err = mntr.ToUserError(fmt.Errorf("orbconfig is incomplete: %w", err))
    54  		}
    55  	}()
    56  
    57  	if o == nil {
    58  		return errors.New("path not provided")
    59  	}
    60  
    61  	if o.Masterkey == "" {
    62  		err = helpers.Concat(err, errors.New("master key is missing"))
    63  	}
    64  
    65  	if o.Path == "" {
    66  		err = helpers.Concat(err, errors.New("file path is missing"))
    67  	}
    68  
    69  	return helpers.Concat(err, o.IsConnectable())
    70  }
    71  
    72  func ParseOrbConfig(orbConfigPath string) (orb *Orb, err error) {
    73  
    74  	defer func() {
    75  		if err != nil {
    76  			err = mntr.ToUserError(fmt.Errorf("parsing orbconfig failed: %w", err))
    77  		}
    78  	}()
    79  
    80  	gitOrbConfig, err := ioutil.ReadFile(orbConfigPath)
    81  
    82  	if err != nil {
    83  		return nil, fmt.Errorf("unable to read orbconfig: %w", err)
    84  	}
    85  
    86  	orb = &Orb{}
    87  	if err := yaml.Unmarshal(gitOrbConfig, orb); err != nil {
    88  		return nil, fmt.Errorf("unable to unmarshal yaml: %w", err)
    89  	}
    90  
    91  	orb.Path = orbConfigPath
    92  	secret.Masterkey = orb.Masterkey
    93  	return orb, nil
    94  }
    95  
    96  func (o *Orb) writeBackOrbConfig() error {
    97  
    98  	data, err := yaml.Marshal(o)
    99  	if err != nil {
   100  		panic(err)
   101  	}
   102  
   103  	if err := ioutil.WriteFile(o.Path, data, os.ModePerm); err != nil {
   104  		return mntr.ToUserError(fmt.Errorf("writing orbconfig failed: %w", err))
   105  	}
   106  	return nil
   107  }
   108  
   109  func Reconfigure(
   110  	ctx context.Context,
   111  	monitor mntr.Monitor,
   112  	orbConfig *Orb,
   113  	newRepoURL,
   114  	newMasterKey,
   115  	newRepoKey string,
   116  	gitClient *git.Client,
   117  	clientID,
   118  	clientSecret string) (err error) {
   119  
   120  	defer func() {
   121  		if err != nil {
   122  			err = fmt.Errorf("reconfiguring orb failed: %w", err)
   123  		}
   124  	}()
   125  
   126  	if orbConfig.URL == "" && newRepoURL == "" {
   127  		return mntr.ToUserError(errors.New("repository url is neighter passed by flag repourl nor written in orbconfig"))
   128  	}
   129  
   130  	// TODO: Remove?
   131  	if orbConfig.URL != "" && newRepoURL != "" && orbConfig.URL != newRepoURL {
   132  		return mntr.ToUserError(fmt.Errorf("repository url %s is not reconfigurable", orbConfig.URL))
   133  	}
   134  
   135  	if orbConfig.Masterkey == "" && newMasterKey == "" {
   136  		return mntr.ToUserError(errors.New("master key is neighter passed by flag masterkey nor written in orbconfig"))
   137  	}
   138  
   139  	var changes bool
   140  	if newMasterKey != "" {
   141  		monitor.Info("Changing masterkey in current orbconfig")
   142  		if orbConfig.Masterkey == "" {
   143  			secret.Masterkey = newMasterKey
   144  		}
   145  		orbConfig.Masterkey = newMasterKey
   146  		changes = true
   147  	}
   148  	if newRepoURL != "" {
   149  		monitor.Info("Changing repository url in current orbconfig")
   150  		defer func() {
   151  			if err == nil {
   152  				monitor.WithField("url", newRepoURL).CaptureMessage("New Repository URL configured")
   153  			}
   154  		}()
   155  		orbConfig.URL = newRepoURL
   156  		changes = true
   157  	}
   158  
   159  	if newRepoKey != "" {
   160  		monitor.Info("Changing used key to connect to repository in current orbconfig")
   161  		orbConfig.Repokey = newRepoKey
   162  		changes = true
   163  	}
   164  
   165  	configureGit := func(mustConfigure bool) error {
   166  		if err := gitClient.Configure(orbConfig.URL, []byte(orbConfig.Repokey)); err != nil {
   167  			if mustConfigure {
   168  				panic(err)
   169  			}
   170  			return err
   171  		}
   172  		if err := gitClient.Clone(); err != nil {
   173  			// this is considered a user error, therefore no panic
   174  			return err
   175  		}
   176  		// this is considered a user error, therefore no panic
   177  		return gitClient.Check()
   178  	}
   179  
   180  	// If the repokey already has read/write permissions, don't generate a new one.
   181  	// This ensures git providers other than github keep being supported
   182  	// Only if you're not trying to set a new key, as you don't want to generate a new key then
   183  	if err := configureGit(false); err != nil {
   184  		if newRepoKey == "" && strings.HasPrefix(orbConfig.URL, "git@github.com") {
   185  			monitor.Info("Starting connection with git-repository")
   186  			dir := filepath.Dir(orbConfig.Path)
   187  
   188  			deployKeyPrivLocal, deployKeyPub := ssh.Generate()
   189  			g := github.New(monitor).LoginOAuth(ctx, dir, clientID, clientSecret)
   190  			if err := g.GetStatus(); err != nil {
   191  				return fmt.Errorf("github oauth login failed: %w", err)
   192  			}
   193  			repo, err := g.GetRepositorySSH(orbConfig.URL)
   194  			if err != nil {
   195  				return fmt.Errorf("failed to get github repository: %w", err)
   196  			}
   197  
   198  			if err := g.EnsureNoDeployKey(repo).GetStatus(); err != nil {
   199  				return fmt.Errorf("failed to clear deploy keys in repository: %w", err)
   200  			}
   201  
   202  			if err := g.CreateDeployKey(repo, deployKeyPub).GetStatus(); err != nil {
   203  				return fmt.Errorf("failed to create deploy keys in repository: %w", err)
   204  			}
   205  			orbConfig.Repokey = deployKeyPrivLocal
   206  
   207  			if err := configureGit(true); err != nil {
   208  				return err
   209  			}
   210  			changes = true
   211  		} else {
   212  			return err
   213  		}
   214  	}
   215  	if changes {
   216  		monitor.Info("Writing local orbconfig")
   217  		if err := orbConfig.writeBackOrbConfig(); err != nil {
   218  			return err
   219  		}
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func (o *Orb) ID() (id string, err error) {
   226  
   227  	defer func() {
   228  		err = mntr.ToUserError(err)
   229  	}()
   230  
   231  	if err := IsComplete(o); err != nil {
   232  		return "", err
   233  	}
   234  
   235  	if o.id != "" {
   236  		return o.id, nil
   237  	}
   238  
   239  	o.id = alphanum.ReplaceAllString(strings.TrimSuffix(strings.TrimPrefix(o.URL, "git@"), ".git"), "-")
   240  	return o.id, nil
   241  }