github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/juju/conn.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package juju 5 6 import ( 7 stderrors "errors" 8 "fmt" 9 "io/ioutil" 10 "net/url" 11 "os" 12 "time" 13 14 "github.com/juju/charm" 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/utils" 18 19 "github.com/juju/juju/environs" 20 "github.com/juju/juju/environs/configstore" 21 "github.com/juju/juju/juju/osenv" 22 "github.com/juju/juju/mongo" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/utils/ssh" 25 ) 26 27 var logger = loggo.GetLogger("juju.conn") 28 29 // Conn holds a connection to a juju environment and its 30 // associated state. 31 type Conn struct { 32 Environ environs.Environ 33 State *state.State 34 } 35 36 var redialStrategy = utils.AttemptStrategy{ 37 Total: 60 * time.Second, 38 Delay: 250 * time.Millisecond, 39 } 40 41 // NewConn returns a new Conn that uses the 42 // given environment. The environment must have already 43 // been bootstrapped. 44 func NewConn(environ environs.Environ) (*Conn, error) { 45 info, _, err := environ.StateInfo() 46 if err != nil { 47 return nil, err 48 } 49 password := environ.Config().AdminSecret() 50 if password == "" { 51 return nil, fmt.Errorf("cannot connect without admin-secret") 52 } 53 err = environs.CheckEnvironment(environ) 54 if err != nil { 55 return nil, err 56 } 57 58 info.Password = password 59 opts := mongo.DefaultDialOpts() 60 st, err := state.Open(info, opts, environs.NewStatePolicy()) 61 if errors.IsUnauthorized(err) { 62 logger.Infof("authorization error while connecting to state server; retrying") 63 // We can't connect with the administrator password,; 64 // perhaps this was the first connection and the 65 // password has not been changed yet. 66 info.Password = utils.UserPasswordHash(password, utils.CompatSalt) 67 68 // We try for a while because we might succeed in 69 // connecting to mongo before the state has been 70 // initialized and the initial password set. 71 for a := redialStrategy.Start(); a.Next(); { 72 st, err = state.Open(info, opts, environs.NewStatePolicy()) 73 if !errors.IsUnauthorized(err) { 74 break 75 } 76 } 77 if err != nil { 78 return nil, err 79 } 80 if err := st.SetAdminMongoPassword(password); err != nil { 81 return nil, err 82 } 83 } else if err != nil { 84 return nil, err 85 } 86 conn := &Conn{ 87 Environ: environ, 88 State: st, 89 } 90 if err := conn.updateSecrets(); err != nil { 91 conn.Close() 92 return nil, fmt.Errorf("unable to push secrets: %v", err) 93 } 94 return conn, nil 95 } 96 97 // NewConnFromState returns a Conn that uses an Environ 98 // made by reading the environment configuration. 99 // The resulting Conn uses the given State - closing 100 // it will close that State. 101 func NewConnFromState(st *state.State) (*Conn, error) { 102 cfg, err := st.EnvironConfig() 103 if err != nil { 104 return nil, err 105 } 106 environ, err := environs.New(cfg) 107 if err != nil { 108 return nil, err 109 } 110 return &Conn{ 111 Environ: environ, 112 State: st, 113 }, nil 114 } 115 116 // NewConnFromName returns a Conn pointing at the environName environment, or the 117 // default environment if not specified. 118 func NewConnFromName(environName string) (*Conn, error) { 119 store, err := configstore.Default() 120 if err != nil { 121 return nil, err 122 } 123 environ, err := environs.NewFromName(environName, store) 124 if err != nil { 125 return nil, err 126 } 127 return NewConn(environ) 128 } 129 130 // Close terminates the connection to the environment and releases 131 // any associated resources. 132 func (c *Conn) Close() error { 133 return c.State.Close() 134 } 135 136 // updateSecrets writes secrets into the environment when there are none. 137 // This is done because environments such as ec2 offer no way to securely 138 // deliver the secrets onto the machine, so the bootstrap is done with the 139 // whole environment configuration but without secrets, and then secrets 140 // are delivered on the first communication with the running environment. 141 func (c *Conn) updateSecrets() error { 142 secrets, err := c.Environ.Provider().SecretAttrs(c.Environ.Config()) 143 if err != nil { 144 return err 145 } 146 cfg, err := c.State.EnvironConfig() 147 if err != nil { 148 return err 149 } 150 secretAttrs := make(map[string]interface{}) 151 attrs := cfg.AllAttrs() 152 for k, v := range secrets { 153 if _, exists := attrs[k]; exists { 154 // Environment already has secrets. Won't send again. 155 return nil 156 } else { 157 secretAttrs[k] = v 158 } 159 } 160 return c.State.UpdateEnvironConfig(secretAttrs, nil, nil) 161 } 162 163 // PutCharm uploads the given charm to provider storage, and adds a 164 // state.Charm to the state. The charm is not uploaded if a charm with 165 // the same URL already exists in the state. 166 // If bumpRevision is true, the charm must be a local directory, 167 // and the revision number will be incremented before pushing. 168 func (conn *Conn) PutCharm(curl *charm.URL, repo charm.Repository, bumpRevision bool) (*state.Charm, error) { 169 if curl.Revision == -1 { 170 rev, err := charm.Latest(repo, curl) 171 if err != nil { 172 return nil, fmt.Errorf("cannot get latest charm revision: %v", err) 173 } 174 curl = curl.WithRevision(rev) 175 } 176 ch, err := repo.Get(curl) 177 if err != nil { 178 return nil, fmt.Errorf("cannot get charm: %v", err) 179 } 180 if bumpRevision { 181 chd, ok := ch.(*charm.Dir) 182 if !ok { 183 return nil, fmt.Errorf("cannot increment revision of charm %q: not a directory", curl) 184 } 185 if err = chd.SetDiskRevision(chd.Revision() + 1); err != nil { 186 return nil, fmt.Errorf("cannot increment revision of charm %q: %v", curl, err) 187 } 188 curl = curl.WithRevision(chd.Revision()) 189 } 190 if sch, err := conn.State.Charm(curl); err == nil { 191 return sch, nil 192 } 193 return conn.addCharm(curl, ch) 194 } 195 196 func (conn *Conn) addCharm(curl *charm.URL, ch charm.Charm) (*state.Charm, error) { 197 var f *os.File 198 name := charm.Quote(curl.String()) 199 switch ch := ch.(type) { 200 case *charm.Dir: 201 var err error 202 if f, err = ioutil.TempFile("", name); err != nil { 203 return nil, err 204 } 205 defer os.Remove(f.Name()) 206 defer f.Close() 207 err = ch.BundleTo(f) 208 if err != nil { 209 return nil, fmt.Errorf("cannot bundle charm: %v", err) 210 } 211 if _, err := f.Seek(0, 0); err != nil { 212 return nil, err 213 } 214 case *charm.Bundle: 215 var err error 216 if f, err = os.Open(ch.Path); err != nil { 217 return nil, fmt.Errorf("cannot read charm bundle: %v", err) 218 } 219 defer f.Close() 220 default: 221 return nil, fmt.Errorf("unknown charm type %T", ch) 222 } 223 digest, size, err := utils.ReadSHA256(f) 224 if err != nil { 225 return nil, err 226 } 227 if _, err := f.Seek(0, 0); err != nil { 228 return nil, err 229 } 230 stor := conn.Environ.Storage() 231 logger.Infof("writing charm to storage [%d bytes]", size) 232 if err := stor.Put(name, f, size); err != nil { 233 return nil, fmt.Errorf("cannot put charm: %v", err) 234 } 235 ustr, err := stor.URL(name) 236 if err != nil { 237 return nil, fmt.Errorf("cannot get storage URL for charm: %v", err) 238 } 239 u, err := url.Parse(ustr) 240 if err != nil { 241 return nil, fmt.Errorf("cannot parse storage URL: %v", err) 242 } 243 logger.Infof("adding charm to state") 244 sch, err := conn.State.AddCharm(ch, curl, u, digest) 245 if err != nil { 246 return nil, fmt.Errorf("cannot add charm: %v", err) 247 } 248 return sch, nil 249 } 250 251 // InitJujuHome initializes the charm, environs/config and utils/ssh packages 252 // to use default paths based on the $JUJU_HOME or $HOME environment variables. 253 // This function should be called before calling NewConn or Conn.Deploy. 254 func InitJujuHome() error { 255 jujuHome := osenv.JujuHomeDir() 256 if jujuHome == "" { 257 return stderrors.New( 258 "cannot determine juju home, required environment variables are not set") 259 } 260 osenv.SetJujuHome(jujuHome) 261 charm.CacheDir = osenv.JujuHomePath("charmcache") 262 if err := ssh.LoadClientKeys(osenv.JujuHomePath("ssh")); err != nil { 263 return fmt.Errorf("cannot load ssh client keys: %v", err) 264 } 265 return nil 266 }