launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "launchpad.net/juju-core/charm" 15 "launchpad.net/juju-core/environs" 16 "launchpad.net/juju-core/environs/config" 17 "launchpad.net/juju-core/environs/configstore" 18 "launchpad.net/juju-core/errors" 19 "launchpad.net/juju-core/juju/osenv" 20 "launchpad.net/juju-core/log" 21 "launchpad.net/juju-core/state" 22 "launchpad.net/juju-core/utils" 23 "launchpad.net/juju-core/utils/ssh" 24 ) 25 26 // Conn holds a connection to a juju environment and its 27 // associated state. 28 type Conn struct { 29 Environ environs.Environ 30 State *state.State 31 } 32 33 var redialStrategy = utils.AttemptStrategy{ 34 Total: 60 * time.Second, 35 Delay: 250 * time.Millisecond, 36 } 37 38 // NewConn returns a new Conn that uses the 39 // given environment. The environment must have already 40 // been bootstrapped. 41 func NewConn(environ environs.Environ) (*Conn, error) { 42 info, _, err := environ.StateInfo() 43 if err != nil { 44 return nil, err 45 } 46 password := environ.Config().AdminSecret() 47 if password == "" { 48 return nil, fmt.Errorf("cannot connect without admin-secret") 49 } 50 err = environs.CheckEnvironment(environ) 51 if err != nil { 52 return nil, err 53 } 54 55 info.Password = password 56 opts := state.DefaultDialOpts() 57 st, err := state.Open(info, opts) 58 if errors.IsUnauthorizedError(err) { 59 log.Noticef("juju: authorization error while connecting to state server; retrying") 60 // We can't connect with the administrator password,; 61 // perhaps this was the first connection and the 62 // password has not been changed yet. 63 info.Password = utils.UserPasswordHash(password, utils.CompatSalt) 64 65 // We try for a while because we might succeed in 66 // connecting to mongo before the state has been 67 // initialized and the initial password set. 68 for a := redialStrategy.Start(); a.Next(); { 69 st, err = state.Open(info, opts) 70 if !errors.IsUnauthorizedError(err) { 71 break 72 } 73 } 74 if err != nil { 75 return nil, err 76 } 77 if err := st.SetAdminMongoPassword(password); err != nil { 78 return nil, err 79 } 80 } else if err != nil { 81 return nil, err 82 } 83 conn := &Conn{ 84 Environ: environ, 85 State: st, 86 } 87 if err := conn.updateSecrets(); err != nil { 88 conn.Close() 89 return nil, fmt.Errorf("unable to push secrets: %v", err) 90 } 91 return conn, nil 92 } 93 94 // NewConnFromState returns a Conn that uses an Environ 95 // made by reading the environment configuration. 96 // The resulting Conn uses the given State - closing 97 // it will close that State. 98 func NewConnFromState(st *state.State) (*Conn, error) { 99 cfg, err := st.EnvironConfig() 100 if err != nil { 101 return nil, err 102 } 103 environ, err := environs.New(cfg) 104 if err != nil { 105 return nil, err 106 } 107 return &Conn{ 108 Environ: environ, 109 State: st, 110 }, nil 111 } 112 113 // NewConnFromName returns a Conn pointing at the environName environment, or the 114 // default environment if not specified. 115 func NewConnFromName(environName string) (*Conn, error) { 116 store, err := configstore.Default() 117 if err != nil { 118 return nil, err 119 } 120 environ, err := environs.NewFromName(environName, store) 121 if err != nil { 122 return nil, err 123 } 124 return NewConn(environ) 125 } 126 127 // Close terminates the connection to the environment and releases 128 // any associated resources. 129 func (c *Conn) Close() error { 130 return c.State.Close() 131 } 132 133 // updateSecrets writes secrets into the environment when there are none. 134 // This is done because environments such as ec2 offer no way to securely 135 // deliver the secrets onto the machine, so the bootstrap is done with the 136 // whole environment configuration but without secrets, and then secrets 137 // are delivered on the first communication with the running environment. 138 func (c *Conn) updateSecrets() error { 139 secrets, err := c.Environ.Provider().SecretAttrs(c.Environ.Config()) 140 if err != nil { 141 return err 142 } 143 cfg, err := c.State.EnvironConfig() 144 if err != nil { 145 return err 146 } 147 attrs := cfg.AllAttrs() 148 for k, v := range secrets { 149 if _, exists := attrs[k]; exists { 150 // Environment already has secrets. Won't send again. 151 return nil 152 } else { 153 attrs[k] = v 154 } 155 } 156 newcfg, err := config.New(config.NoDefaults, attrs) 157 if err != nil { 158 return err 159 } 160 return c.State.SetEnvironConfig(newcfg, cfg) 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 log.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 log.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 }