Example #1
0
func (k *Klient) send(queryString, method string, req, resp interface{}) (string, error) {
	queryString, err := utils.QueryString(queryString)
	if err != nil {
		return "", err
	}

	k.Log.Debug("calling %q method on %q with %+v", method, queryString, req)

	kref, err := klient.ConnectTimeout(k.Kite, queryString, k.dialTimeout())
	if err != nil {
		k.Log.Debug("connecting to %q klient failed: %s", queryString, err)

		return "", err
	}
	defer kref.Close()

	r, err := kref.Client.TellWithTimeout(method, k.timeout(), req)
	if err != nil {
		k.Log.Debug("sending request to %q klient failed: %s", queryString, err)

		return "", err
	}

	if err := r.Unmarshal(resp); err != nil {
		return "", errors.New("reading response from klient failed: " + err.Error())
	}

	k.Log.Debug("received %+v response from %q (%q)", resp, method, queryString)

	return kref.URL(), nil
}
Example #2
0
func (p *Planner) checkSingleKlient(k *kite.Kite, label, kiteID string) *DialState {
	kiteID, err := utils.QueryString(kiteID)
	if err != nil {
		return &DialState{
			Label:  label,
			KiteID: kiteID,
			State:  "dial",
			Err:    err,
		}
	}

	start := time.Now()

	c, err := klient.NewWithTimeout(k, kiteID, p.klientTimeout())
	if err == klient.ErrDialingFailed {
		return &DialState{
			Label:   label,
			KiteID:  kiteID,
			KiteURL: c.Client.URL,
			State:   "dial",
			Err:     err,
		}
	}

	if err != nil {
		return &DialState{
			Label:  label,
			KiteID: kiteID,
			State:  "kontrol",
			Err:    err,
		}
	}

	defer c.Close()

	left := p.klientTimeout() - time.Now().Sub(start)

	err = c.PingTimeout(max(left, klient.DefaultTimeout))
	if err != nil {
		return &DialState{
			Label:  label,
			KiteID: kiteID,
			State:  "ping",
			Err:    err,
		}
	}

	if p.OnDial != nil {
		err = p.OnDial(c.Client)
	}

	return &DialState{
		Label:   label,
		KiteID:  kiteID,
		KiteURL: c.Client.URL,
		State:   "provider",
		Err:     err,
	}
}
Example #3
0
// Valid implements the kloud.Validator interface.
func (meta *Cred) Valid() error {
	if meta.QueryString == "" {
		return errors.New("vagrant meta: query string is empty")
	}

	query, err := utils.QueryString(meta.QueryString)
	if err != nil {
		return err
	}

	meta.QueryString = query

	return nil
}
Example #4
0
// GetGroupForKite reverse looks up a team name for the given kiteID
// by looking up a kiteID among jMachine.queryString fields.
//
// TODO(rjeczalik): This method does client-side filtering,
// due to lack of indexes on the following fields:
//
//   - registerUrl
//   - queryString
//   - ipAddress
//
// After the indexes are in place, the LookupGroup should
// be reworked to look up on jMachine.queryString only.
func LookupGroup(opts *LookupGroupOptions) (*models.Group, error) {
	var m []struct {
		RegisterURL string `bson:"registerUrl"`
		QueryString string `bson:"queryString"`
		IPAddress   string `bson:"ipAddress"`
		Groups      []struct {
			ID bson.ObjectId `bson:"id"`
		} `bson:"groups"`
	}

	id, err := GetUserID(opts.Username)
	if err != nil {
		return nil, err
	}

	fn := func(c *mgo.Collection) error {
		return c.Find(bson.M{"users.id": id}).Select(lookupGroupFields).All(&m)
	}

	if err := Mongo.Run(MachinesColl, fn); err != nil && err != mgo.ErrNotFound {
		return nil, err
	}

	// Look for questString.
	if qs, err := utils.QueryString(opts.KiteID); err == nil && qs != "" {
		for i := range m {
			if len(m[i].Groups) == 0 {
				continue
			}

			if m[i].QueryString == qs {
				return GetGroupById(m[i].Groups[0].ID.Hex())
			}
		}
	}

	if host, err := parseHost(opts.ClientURL); err == nil && host != "" {
		// Look up for ipAddress.
		for i := range m {
			if len(m[i].Groups) == 0 {
				continue
			}

			mHost := m[i].IPAddress
			if host, _, err := net.SplitHostPort(mHost); err == nil {
				mHost = host
			}

			if mHost == host {
				return GetGroupById(m[i].Groups[0].ID.Hex())
			}
		}

		// Look up for registerUrl.
		for i := range m {
			if len(m[i].Groups) == 0 {
				continue
			}

			if mHost, err := parseHost(m[i].RegisterURL); err == nil && mHost == host {
				return GetGroupById(m[i].Groups[0].ID.Hex())
			}
		}
	}

	// KD does not have a jMachine document (TODO - #8514), instead
	// we return a group in a best-effor manner - we look up
	// the most recently accessed session for the user,
	// and give the the group attached to it.
	switch strings.ToLower(opts.Environment) {
	case "managed", "devmanaged":
		session, err := GetMostRecentSession(opts.Username)
		if err != nil {
			return nil, err
		}

		return GetGroup(session.GroupName)
	}

	return nil, mgo.ErrNotFound
}
Example #5
0
func (k *Klient) cmd(queryString, method, boxPath string) error {
	queryString, err := utils.QueryString(queryString)
	if err != nil {
		return err
	}

	k.Log.Debug("calling %q command on %q with %q", method, queryString, boxPath)

	kref, err := klient.ConnectTimeout(k.Kite, queryString, k.dialTimeout())
	if err != nil {
		k.Log.Debug("connecting to %q klient failed: %s", queryString, err)

		return err
	}

	done := make(chan error, 1)

	success := dnode.Callback(func(*dnode.Partial) {
		done <- nil
	})

	failure := dnode.Callback(func(r *dnode.Partial) {
		msg, err := r.One().String()
		if err != nil {
			err = errors.New("unknown failure")
		} else {
			err = errors.New(msg)
		}
		done <- err
	})

	lost := make(chan struct{})
	beat := make(chan struct{})
	stop := make(chan struct{})
	defer close(stop)

	go func() {
		// Heartbeat timer is initially stopped since at this
		// point we do not know whether klient supports heartbeats.
		// On first heartbeat we activate the timer and set the ch.
		var (
			t  *time.Timer
			ch <-chan time.Time
		)

		for {
			select {
			case <-stop:
				return
			case <-ch:
				lost <- struct{}{}
			case <-beat:
				if t == nil {
					t = time.NewTimer(defaultDialTimeout)
					ch = t.C
					defer t.Stop()
				}

				t.Reset(defaultDialTimeout)
			}
		}
	}()

	heartbeat := dnode.Callback(func(r *dnode.Partial) {
		beat <- struct{}{}
	})

	req := &Command{
		FilePath:  boxPath,
		Success:   success,
		Failure:   failure,
		Heartbeat: heartbeat,
	}

	if k.Debug {
		log := k.Log.New(method)
		req.Output = dnode.Callback(func(r *dnode.Partial) {
			log.Debug("%s", r.One().MustString())
		})
	}

	if _, err = kref.Client.TellWithTimeout(method, k.timeout(), req); err != nil {
		return errors.New("sending request to klient failed: " + err.Error())
	}

	select {
	case err := <-done:
		return err
	case <-time.After(k.timeout()):
		return fmt.Errorf("timed out calling %q on %q", method, queryString)
	case <-lost:
		return errors.New("connection to your KD Daemon was lost due to inactivity")
	}
}
Example #6
0
// InjectVagrantData sets default properties for vagrant_instance Terraform template.
func (s *Stack) ApplyTemplate(c *stack.Credential) (*stack.Template, error) {
	t := s.Builder.Template
	cred := c.Credential.(*Cred)

	var res VagrantResource

	if err := t.DecodeResource(&res); err != nil {
		return nil, err
	}

	if len(res.Build) == 0 {
		return nil, errors.New("no vagrant instances specified")
	}

	uids := s.Builder.MachineUIDs()

	s.Log.Debug("machine uids (%d): %v", len(uids), uids)

	klientURL, err := s.Session.Userdata.LookupKlientURL()
	if err != nil {
		return nil, err
	}

	// queryString is taken from jCredentialData.meta.queryString,
	// for debugging purposes it can be overwritten in the template,
	// however if template has multiple machines, all of them
	// are required to overwrite the queryString to the same value
	// to match current implementation of terraformplugins/vagrant
	// provider.
	//
	// Otherwise we fail early to show problem with the template.
	var queryString string
	for resourceName, box := range res.Build {
		kiteKey, err := s.BuildKiteKey(resourceName, s.Req.Username)
		if err != nil {
			return nil, err
		}

		// set kontrolURL if not provided via template
		kontrolURL := s.Session.Userdata.Keycreator.KontrolURL
		if k, ok := box["kontrolURL"].(string); ok {
			kontrolURL = k
		} else {
			box["kontrolURL"] = kontrolURL
		}

		if q, ok := box["queryString"].(string); ok {
			q, err := utils.QueryString(q)
			if err != nil {
				return nil, fmt.Errorf("%s: error reading queryString: %s", resourceName, err)
			}
			if queryString != "" && queryString != q {
				return nil, fmt.Errorf("mismatched queryString provided for multiple instances; want %q, got %q", queryString, q)
			}
			queryString = q
		} else {
			box["queryString"] = "${var.vagrant_queryString}"
			queryString = cred.QueryString
		}

		// set default filePath to relative <stackdir>/<boxname>; for
		// default configured klient it resolves to ~/.vagrant.d/<stackdir>/<boxname>
		if _, ok := box["filePath"]; !ok {
			uid, ok := uids[resourceName]
			if !ok {
				// For Plan call we return random uid as it won't be returned
				// as a part of meta; the filePath is inserted into meta by
				// the apply method.
				uid = resourceName + "-" + utils.RandString(6)
			}
			box["filePath"] = "koding/${var.koding_group_slug}/" + uid
		}

		// set default CPU number
		if _, ok := box["cpus"]; !ok {
			box["cpus"] = "${var.vagrant_cpus}"
		}

		// set default RAM in MiB
		if _, ok := box["memory"]; !ok {
			box["memory"] = "${var.vagrant_memory}"
		}

		// set default box type
		if _, ok := box["box"]; !ok {
			box["box"] = "${var.vagrant_box}"
		}

		var ports []interface{}

		switch p := box["forwarded_ports"].(type) {
		case []interface{}:
			ports = p
		case []map[string]interface{}:
			ports = make([]interface{}, len(p))

			for i := range p {
				ports[i] = p[i]
			}
		}

		// klient kite port
		kitePort := &vagrantapi.ForwardedPort{
			HostPort:  2200,
			GuestPort: 56789,
		}

		// tlsproxy port
		kitesPort := &vagrantapi.ForwardedPort{
			HostPort:  2201,
			GuestPort: 56790,
		}

		ports = append(ports, kitePort, kitesPort)

		box["forwarded_ports"] = ports
		box["username"] = s.Req.Username

		tunnel := s.newTunnel(resourceName)

		s.Builder.InterpolateField(box, resourceName, "user_data")

		if b, ok := box["debug"].(bool); ok && b {
			s.Debug = true
		}

		data := puser.Value{
			Username:        s.Req.Username,
			Groups:          []string{"sudo"},
			Hostname:        s.Req.Username, // no typo here. hostname = username
			KiteKey:         kiteKey,
			LatestKlientURL: klientURL,
			TunnelName:      tunnel.Name,
			TunnelKiteURL:   tunnel.KiteURL,
			KontrolURL:      kontrolURL,
		}

		// pass the values as a JSON encoded as base64. Our script will decode
		// and unmarshall and use it inside the Vagrant box
		val, err := json.Marshal(&data)
		if err != nil {
			return nil, err
		}

		// Compressing the provision data isn't doing any serious optimizations,
		// it's just here so the debug output does not take half a screen.
		//
		// The provisionclient handles both compressed and uncompressed JSONs.
		var buf bytes.Buffer
		if cw, err := gzip.NewWriterLevel(&buf, 9); err == nil {
			if _, err = io.Copy(cw, bytes.NewReader(val)); err == nil && cw.Close() == nil {
				s.Log.Debug("using compressed provision data: %d vs %d", len(val), len(buf.Bytes()))

				val = buf.Bytes()
			}
		}

		box["provisionData"] = base64.StdEncoding.EncodeToString(val)
		res.Build[resourceName] = box
	}

	t.Resource["vagrant_instance"] = res.Build

	if err := t.Flush(); err != nil {
		return nil, err
	}

	content, err := t.JsonOutput()
	if err != nil {
		return nil, err
	}

	return &stack.Template{
		Content: content,
	}, nil
}