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 }