func (op *operation) UpdateMetadata(opMetadata interface{}) error { if op.status != shared.Pending && op.status != shared.Running { return fmt.Errorf("Only pending or running operations can be updated") } if op.readonly { return fmt.Errorf("Read-only operations can't be updated") } newMetadata, err := shared.ParseMetadata(opMetadata) if err != nil { return err } op.lock.Lock() op.updatedAt = time.Now() op.metadata = newMetadata op.lock.Unlock() shared.Debugf("Updated metadata for %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) return nil }
func operationCreate(opClass operationClass, opResources map[string][]string, opMetadata interface{}, onRun func(*operation) error, onCancel func(*operation) error, onConnect func(*operation, *http.Request, http.ResponseWriter) error) (*operation, error) { // Main attributes op := operation{} op.id = uuid.NewRandom().String() op.class = opClass op.createdAt = time.Now() op.updatedAt = op.createdAt op.status = shared.Pending op.url = fmt.Sprintf("/%s/operations/%s", shared.APIVersion, op.id) op.resources = opResources op.chanDone = make(chan error) newMetadata, err := shared.ParseMetadata(opMetadata) if err != nil { return nil, err } op.metadata = newMetadata // Callback functions op.onRun = onRun op.onCancel = onCancel op.onConnect = onConnect // Sanity check if op.class != operationClassWebsocket && op.onConnect != nil { return nil, fmt.Errorf("Only websocket operations can have a Connect hook") } if op.class == operationClassWebsocket && op.onConnect == nil { return nil, fmt.Errorf("Websocket operations must have a Connect hook") } if op.class == operationClassToken && op.onRun != nil { return nil, fmt.Errorf("Token operations can't have a Run hook") } if op.class == operationClassToken && op.onCancel != nil { return nil, fmt.Errorf("Token operations can't have a Cancel hook") } operationsLock.Lock() operations[op.id] = &op operationsLock.Unlock() shared.Debugf("New %s operation: %s", op.class.String(), op.id) _, md, _ := op.Render() eventSend("operation", md) return &op, nil }
// Exec runs a command inside the LXD container. For "interactive" use such as // `lxc exec ...`, one should pass a controlHandler that talks over the control // socket and handles things like SIGWINCH. If running non-interactive, passing // a nil controlHandler will cause Exec to return when all of the command // output is sent to the output buffers. func (c *Client) Exec(name string, cmd []string, env map[string]string, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, controlHandler func(*Client, *websocket.Conn)) (int, error) { body := shared.Jmap{ "command": cmd, "wait-for-websocket": true, "interactive": controlHandler != nil, "environment": env, } resp, err := c.post(fmt.Sprintf("containers/%s/exec", name), body, Async) if err != nil { return -1, err } var fds shared.Jmap op, err := resp.MetadataAsOperation() if err == nil && op.Metadata != nil { fds, err = op.Metadata.GetMap("fds") if err != nil { return -1, err } } else { // FIXME: This is a backward compatibility codepath md := execMd{} if err := json.Unmarshal(resp.Metadata, &md); err != nil { return -1, err } fds, err = shared.ParseMetadata(md.FDs) if err != nil { return -1, err } } if controlHandler != nil { var control *websocket.Conn if wsControl, ok := fds["control"]; ok { control, err = c.websocket(resp.Operation, wsControl.(string)) if err != nil { return -1, err } defer control.Close() go controlHandler(c, control) } conn, err := c.websocket(resp.Operation, fds["0"].(string)) if err != nil { return -1, err } shared.WebsocketSendStream(conn, stdin) <-shared.WebsocketRecvStream(stdout, conn) conn.Close() } else { conns := make([]*websocket.Conn, 3) dones := make([]chan bool, 3) conns[0], err = c.websocket(resp.Operation, fds[strconv.Itoa(0)].(string)) if err != nil { return -1, err } defer conns[0].Close() dones[0] = shared.WebsocketSendStream(conns[0], stdin) outputs := []io.WriteCloser{stdout, stderr} for i := 1; i < 3; i++ { conns[i], err = c.websocket(resp.Operation, fds[strconv.Itoa(i)].(string)) if err != nil { return -1, err } defer conns[i].Close() dones[i] = shared.WebsocketRecvStream(outputs[i-1], conns[i]) } /* * We'll get a read signal from each of stdout, stderr when they've * both died. We need to wait for these in addition to the operation, * because the server may indicate that the operation is done before we * can actually read the last bits of data off these sockets and print * it to the screen. * * We don't wait for stdin here, because if we're interactive, the user * may not have closed it (e.g. if the command exits but the user * didn't ^D). */ for i := 1; i < 3; i++ { <-dones[i] } // Once we're done, we explicitly close stdin, to signal the websockets // we're done. stdin.Close() } // Now, get the operation's status too. op, err = c.WaitFor(resp.Operation) if err != nil { return -1, err } if op.StatusCode == shared.Failure { return -1, fmt.Errorf(op.Err) } if op.StatusCode != shared.Success { return -1, fmt.Errorf(i18n.G("got bad op status %s"), op.Status) } if op.Metadata == nil { return -1, fmt.Errorf(i18n.G("no metadata received")) } return op.Metadata.GetInt("return") }