diff -Nru lxd-2.7/client.go lxd-2.8/client.go --- lxd-2.7/client.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/client.go 2017-01-25 02:50:49.000000000 +0000 @@ -24,6 +24,7 @@ "github.com/gorilla/websocket" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/ioprogress" "github.com/lxc/lxd/shared/simplestreams" "github.com/lxc/lxd/shared/version" @@ -44,14 +45,6 @@ simplestreams *simplestreams.SimpleStreams } -type ResponseType string - -const ( - Sync ResponseType = "sync" - Async ResponseType = "async" - Error ResponseType = "error" -) - var ( // LXDErrors are special errors; the client library hoists error codes // to these errors internally so that user code can compare against @@ -63,61 +56,17 @@ } ) -type Response struct { - Type ResponseType `json:"type"` - - /* Valid only for Sync responses */ - Status string `json:"status"` - StatusCode int `json:"status_code"` - - /* Valid only for Async responses */ - Operation string `json:"operation"` - - /* Valid only for Error responses */ - Code int `json:"error_code"` - Error string `json:"error"` - - /* Valid for Sync and Error responses */ - Metadata json.RawMessage `json:"metadata"` -} - -func (r *Response) MetadataAsMap() (*shared.Jmap, error) { - ret := shared.Jmap{} - if err := json.Unmarshal(r.Metadata, &ret); err != nil { - return nil, err - } - return &ret, nil -} - -func (r *Response) MetadataAsOperation() (*shared.Operation, error) { - op := shared.Operation{} - if err := json.Unmarshal(r.Metadata, &op); err != nil { - return nil, err - } - - return &op, nil -} - -func (r *Response) MetadataAsStringSlice() ([]string, error) { - sl := []string{} - if err := json.Unmarshal(r.Metadata, &sl); err != nil { - return nil, err - } - - return sl, nil -} - // ParseResponse parses a lxd style response out of an http.Response. Note that // this does _not_ automatically convert error responses to golang errors. To // do that, use ParseError. Internal client library uses should probably use // HoistResponse, unless they are interested in accessing the underlying Error // response (e.g. to inspect the error code). -func ParseResponse(r *http.Response) (*Response, error) { +func ParseResponse(r *http.Response) (*api.Response, error) { if r == nil { return nil, fmt.Errorf("no response!") } defer r.Body.Close() - ret := Response{} + ret := api.Response{} s, err := ioutil.ReadAll(r.Body) if err != nil { @@ -134,13 +83,13 @@ // HoistResponse hoists a regular http response into a response of type rtype // or returns a golang error. -func HoistResponse(r *http.Response, rtype ResponseType) (*Response, error) { +func HoistResponse(r *http.Response, rtype api.ResponseType) (*api.Response, error) { resp, err := ParseResponse(r) if err != nil { return nil, err } - if resp.Type == Error { + if resp.Type == api.ErrorResponse { // Try and use a known error if we have one for this code. err, ok := LXDErrors[resp.Code] if !ok { @@ -392,13 +341,13 @@ return addresses, nil } -func (c *Client) get(base string) (*Response, error) { +func (c *Client) get(base string) (*api.Response, error) { uri := c.url(version.APIVersion, base) return c.baseGet(uri) } -func (c *Client) baseGet(getUrl string) (*Response, error) { +func (c *Client) baseGet(getUrl string) (*api.Response, error) { req, err := http.NewRequest("GET", getUrl, nil) if err != nil { return nil, err @@ -411,10 +360,10 @@ return nil, err } - return HoistResponse(resp, Sync) + return HoistResponse(resp, api.SyncResponse) } -func (c *Client) doUpdateMethod(method string, base string, args interface{}, rtype ResponseType) (*Response, error) { +func (c *Client) doUpdateMethod(method string, base string, args interface{}, rtype api.ResponseType) (*api.Response, error) { uri := c.url(version.APIVersion, base) buf := bytes.Buffer{} @@ -440,19 +389,19 @@ return HoistResponse(resp, rtype) } -func (c *Client) put(base string, args interface{}, rtype ResponseType) (*Response, error) { +func (c *Client) put(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) { return c.doUpdateMethod("PUT", base, args, rtype) } -func (c *Client) patch(base string, args interface{}, rtype ResponseType) (*Response, error) { +func (c *Client) patch(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) { return c.doUpdateMethod("PATCH", base, args, rtype) } -func (c *Client) post(base string, args interface{}, rtype ResponseType) (*Response, error) { +func (c *Client) post(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) { return c.doUpdateMethod("POST", base, args, rtype) } -func (c *Client) delete(base string, args interface{}, rtype ResponseType) (*Response, error) { +func (c *Client) delete(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) { return c.doUpdateMethod("DELETE", base, args, rtype) } @@ -470,7 +419,7 @@ // because it is raw data, we need to check for http status if raw.StatusCode != 200 { - resp, err := HoistResponse(raw, Sync) + resp, err := HoistResponse(raw, api.SyncResponse) if err != nil { return nil, err } @@ -517,7 +466,7 @@ return strings.TrimSuffix(uri, "/") } -func (c *Client) GetServerConfig() (*Response, error) { +func (c *Client) GetServerConfig() (*api.Response, error) { if c.Remote.Protocol == "simplestreams" { return nil, fmt.Errorf("This function isn't supported by simplestreams remote.") } @@ -574,12 +523,12 @@ shared.LogDebugf("%s", resp) - jmap, err := resp.MetadataAsMap() + meta, err := resp.MetadataAsMap() if err != nil { return false } - auth, err := jmap.GetString("auth") + auth, err := shared.Jmap(meta).GetString("auth") if err != nil { return false } @@ -595,12 +544,12 @@ shared.LogDebugf("%s", resp) - jmap, err := resp.MetadataAsMap() + meta, err := resp.MetadataAsMap() if err != nil { return false } - public, err := jmap.GetBool("public") + public, err := shared.Jmap(meta).GetBool("public") if err != nil { return false } @@ -608,7 +557,7 @@ return public } -func (c *Client) ListContainers() ([]shared.ContainerInfo, error) { +func (c *Client) ListContainers() ([]api.Container, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -618,9 +567,9 @@ return nil, err } - var result []shared.ContainerInfo + var result []api.Container - if err := json.Unmarshal(resp.Metadata, &result); err != nil { + if err := resp.MetadataAsStruct(&result); err != nil { return nil, err } @@ -649,7 +598,7 @@ if c.Remote.Protocol != "simplestreams" && !info.Public { var secret string - resp, err := c.post("images/"+image+"/secret", nil, Async) + resp, err := c.post("images/"+image+"/secret", nil, api.AsyncResponse) if err != nil { return err } @@ -659,7 +608,7 @@ return err } - secret, err = op.Metadata.GetString("secret") + secret, err = shared.Jmap(op.Metadata).GetString("secret") if err != nil { return err } @@ -705,7 +654,7 @@ } if progressHandler != nil { - go dest.Monitor([]string{"operation"}, handler) + go dest.Monitor([]string{"operation"}, handler, nil) } fingerprint := info.Fingerprint @@ -716,7 +665,7 @@ source["server"] = sourceUrl body := shared.Jmap{"public": public, "auto_update": autoUpdate, "source": source} - resp, err := dest.post("images", body, Async) + resp, err := dest.post("images", body, api.AsyncResponse) if err != nil { continue } @@ -729,7 +678,7 @@ } if op.Metadata != nil { - value, err := op.Metadata.GetString("fingerprint") + value, err := shared.Jmap(op.Metadata).GetString("fingerprint") if err == nil { fingerprint = value } @@ -937,10 +886,10 @@ } if progressHandler != nil { - go c.Monitor([]string{"operation"}, handler) + go c.Monitor([]string{"operation"}, handler, nil) } - resp, err := c.post("images", body, Async) + resp, err := c.post("images", body, api.AsyncResponse) if err != nil { return "", err } @@ -956,7 +905,7 @@ return "", fmt.Errorf("Missing operation metadata") } - fingerprint, err := op.Metadata.GetString("fingerprint") + fingerprint, err := shared.Jmap(op.Metadata).GetString("fingerprint") if err != nil { return "", err } @@ -1049,6 +998,10 @@ } req, err = http.NewRequest("POST", uri, progress) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", w.FormDataContentType()) } else { fImage, err = os.Open(imageFile) @@ -1071,6 +1024,10 @@ } req, err = http.NewRequest("POST", uri, progress) + if err != nil { + return "", err + } + req.Header.Set("X-LXD-filename", filepath.Base(imageFile)) req.Header.Set("Content-Type", "application/octet-stream") } @@ -1109,17 +1066,17 @@ return "", err } - resp, err := HoistResponse(raw, Async) + resp, err := HoistResponse(raw, api.AsyncResponse) if err != nil { return "", err } - jmap, err := c.AsyncWaitMeta(resp) + meta, err := c.AsyncWaitMeta(resp) if err != nil { return "", err } - fingerprint, err := jmap.GetString("fingerprint") + fingerprint, err := shared.Jmap(meta).GetString("fingerprint") if err != nil { return "", err } @@ -1136,7 +1093,7 @@ return fingerprint, nil } -func (c *Client) GetImageInfo(image string) (*shared.ImageInfo, error) { +func (c *Client) GetImageInfo(image string) (*api.Image, error) { if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil { return c.simplestreams.GetImageInfo(image) } @@ -1146,24 +1103,24 @@ return nil, err } - info := shared.ImageInfo{} - if err := json.Unmarshal(resp.Metadata, &info); err != nil { + info := api.Image{} + if err := resp.MetadataAsStruct(&info); err != nil { return nil, err } return &info, nil } -func (c *Client) PutImageInfo(name string, p shared.BriefImageInfo) error { +func (c *Client) PutImageInfo(name string, p api.ImagePut) error { if c.Remote.Public { return fmt.Errorf("This function isn't supported by public remotes.") } - _, err := c.put(fmt.Sprintf("images/%s", name), p, Sync) + _, err := c.put(fmt.Sprintf("images/%s", name), p, api.SyncResponse) return err } -func (c *Client) ListImages() ([]shared.ImageInfo, error) { +func (c *Client) ListImages() ([]api.Image, error) { if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil { return c.simplestreams.ListImages() } @@ -1173,8 +1130,8 @@ return nil, err } - var result []shared.ImageInfo - if err := json.Unmarshal(resp.Metadata, &result); err != nil { + var result []api.Image + if err := resp.MetadataAsStruct(&result); err != nil { return nil, err } @@ -1186,7 +1143,7 @@ return fmt.Errorf("This function isn't supported by public remotes.") } - resp, err := c.delete(fmt.Sprintf("images/%s", image), nil, Async) + resp, err := c.delete(fmt.Sprintf("images/%s", image), nil, api.AsyncResponse) if err != nil { return err @@ -1202,7 +1159,7 @@ body := shared.Jmap{"description": desc, "target": target, "name": alias} - _, err := c.post("images/aliases", body, Sync) + _, err := c.post("images/aliases", body, api.SyncResponse) return err } @@ -1211,11 +1168,11 @@ return fmt.Errorf("This function isn't supported by public remotes.") } - _, err := c.delete(fmt.Sprintf("images/aliases/%s", alias), nil, Sync) + _, err := c.delete(fmt.Sprintf("images/aliases/%s", alias), nil, api.SyncResponse) return err } -func (c *Client) ListAliases() (shared.ImageAliases, error) { +func (c *Client) ListAliases() ([]api.ImageAliasesEntry, error) { if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil { return c.simplestreams.ListAliases() } @@ -1225,16 +1182,16 @@ return nil, err } - var result shared.ImageAliases + var result []api.ImageAliasesEntry - if err := json.Unmarshal(resp.Metadata, &result); err != nil { + if err := resp.MetadataAsStruct(&result); err != nil { return nil, err } return result, nil } -func (c *Client) CertificateList() ([]shared.CertInfo, error) { +func (c *Client) CertificateList() ([]api.Certificate, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -1244,8 +1201,8 @@ return nil, err } - var result []shared.CertInfo - if err := json.Unmarshal(resp.Metadata, &result); err != nil { + var result []api.Certificate + if err := resp.MetadataAsStruct(&result); err != nil { return nil, err } @@ -1259,7 +1216,7 @@ body := shared.Jmap{"type": "client", "password": pwd} - _, err := c.post("certificates", body, Sync) + _, err := c.post("certificates", body, api.SyncResponse) return err } @@ -1269,7 +1226,7 @@ } b64 := base64.StdEncoding.EncodeToString(cert.Raw) - _, err := c.post("certificates", shared.Jmap{"type": "client", "certificate": b64, "name": name}, Sync) + _, err := c.post("certificates", shared.Jmap{"type": "client", "certificate": b64, "name": name}, api.SyncResponse) return err } @@ -1278,7 +1235,7 @@ return fmt.Errorf("This function isn't supported by public remotes.") } - _, err := c.delete(fmt.Sprintf("certificates/%s", fingerprint), nil, Sync) + _, err := c.delete(fmt.Sprintf("certificates/%s", fingerprint), nil, api.SyncResponse) return err } @@ -1304,12 +1261,12 @@ return "" } - if resp.Type == Error { + if resp.Type == api.ErrorResponse { return "" } - var result shared.ImageAliasesEntry - if err := json.Unmarshal(resp.Metadata, &result); err != nil { + var result api.ImageAliasesEntry + if err := resp.MetadataAsStruct(&result); err != nil { return "" } return result.Target @@ -1317,7 +1274,7 @@ // Init creates a container from either a fingerprint or an alias; you must // provide at least one. -func (c *Client) Init(name string, imgremote string, image string, profiles *[]string, config map[string]string, devices shared.Devices, ephem bool) (*Response, error) { +func (c *Client) Init(name string, imgremote string, image string, profiles *[]string, config map[string]string, devices map[string]map[string]string, ephem bool) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -1365,7 +1322,7 @@ image = target - resp, err := tmpremote.post("images/"+image+"/secret", nil, Async) + resp, err := tmpremote.post("images/"+image+"/secret", nil, api.AsyncResponse) if err != nil { return nil, err } @@ -1375,7 +1332,7 @@ return nil, err } - secret, err = op.Metadata.GetString("secret") + secret, err = shared.Jmap(op.Metadata).GetString("secret") if err != nil { return nil, err } @@ -1427,7 +1384,7 @@ body["ephemeral"] = ephem } - var resp *Response + var resp *api.Response if imgremote != c.Name { var addresses []string @@ -1439,7 +1396,7 @@ for _, addr := range addresses { body["source"].(shared.Jmap)["server"] = "https://" + addr - resp, err = c.post("containers", body, Async) + resp, err = c.post("containers", body, api.AsyncResponse) if err != nil { continue } @@ -1447,7 +1404,7 @@ break } } else { - resp, err = c.post("containers", body, Async) + resp, err = c.post("containers", body, api.AsyncResponse) } if err != nil { @@ -1460,7 +1417,7 @@ return resp, nil } -func (c *Client) LocalCopy(source string, name string, config map[string]string, profiles []string, ephemeral bool) (*Response, error) { +func (c *Client) LocalCopy(source string, name string, config map[string]string, profiles []string, ephemeral bool) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -1476,10 +1433,10 @@ "ephemeral": ephemeral, } - return c.post("containers", body, Async) + return c.post("containers", body, api.AsyncResponse) } -func (c *Client) Monitor(types []string, handler func(interface{})) error { +func (c *Client) Monitor(types []string, handler func(interface{}), done chan bool) error { if c.Remote.Public { return fmt.Errorf("This function isn't supported by public remotes.") } @@ -1495,20 +1452,37 @@ } defer conn.Close() - for { - message := make(map[string]interface{}) + readCh := make(chan []byte) + errCh := make(chan error) - _, data, err := conn.ReadMessage() - if err != nil { - return err + go func() { + for { + _, data, err := conn.ReadMessage() + if err != nil { + errCh <- err + return + } + + readCh <- data } + }() - err = json.Unmarshal(data, &message) - if err != nil { + for { + select { + case <-done: + return nil + case data := <-readCh: + message := make(map[string]interface{}) + + err = json.Unmarshal(data, &message) + if err != nil { + return err + } + + handler(message) + case err := <-errCh: return err } - - handler(message) } } @@ -1538,7 +1512,7 @@ body["height"] = height } - resp, err := c.post(fmt.Sprintf("containers/%s/exec", name), body, Async) + resp, err := c.post(fmt.Sprintf("containers/%s/exec", name), body, api.AsyncResponse) if err != nil { return -1, err } @@ -1550,7 +1524,7 @@ return -1, err } - fds, err = op.Metadata.GetMap("fds") + fds, err = shared.Jmap(op.Metadata).GetMap("fds") if err != nil { return -1, err } @@ -1625,11 +1599,11 @@ return -1, err } - if op.StatusCode == shared.Failure { + if op.StatusCode == api.Failure { return -1, fmt.Errorf(op.Err) } - if op.StatusCode != shared.Success { + if op.StatusCode != api.Success { return -1, fmt.Errorf("got bad op status %s", op.Status) } @@ -1637,10 +1611,10 @@ return -1, fmt.Errorf("no metadata received") } - return op.Metadata.GetInt("return") + return shared.Jmap(op.Metadata).GetInt("return") } -func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*Response, error) { +func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -1654,10 +1628,10 @@ body["stateful"] = stateful } - return c.put(fmt.Sprintf("containers/%s/state", name), body, Async) + return c.put(fmt.Sprintf("containers/%s/state", name), body, api.AsyncResponse) } -func (c *Client) Delete(name string) (*Response, error) { +func (c *Client) Delete(name string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -1670,18 +1644,18 @@ url = fmt.Sprintf("containers/%s", name) } - return c.delete(url, nil, Async) + return c.delete(url, nil, api.AsyncResponse) } -func (c *Client) ServerStatus() (*shared.ServerState, error) { - ss := shared.ServerState{} +func (c *Client) ServerStatus() (*api.Server, error) { + ss := api.Server{} resp, err := c.GetServerConfig() if err != nil { return nil, err } - if err := json.Unmarshal(resp.Metadata, &ss); err != nil { + if err := resp.MetadataAsStruct(&ss); err != nil { return nil, err } @@ -1697,38 +1671,38 @@ return &ss, nil } -func (c *Client) ContainerInfo(name string) (*shared.ContainerInfo, error) { +func (c *Client) ContainerInfo(name string) (*api.Container, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } - ct := shared.ContainerInfo{} + ct := api.Container{} resp, err := c.get(fmt.Sprintf("containers/%s", name)) if err != nil { return nil, err } - if err := json.Unmarshal(resp.Metadata, &ct); err != nil { + if err := resp.MetadataAsStruct(&ct); err != nil { return nil, err } return &ct, nil } -func (c *Client) ContainerState(name string) (*shared.ContainerState, error) { +func (c *Client) ContainerState(name string) (*api.ContainerState, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } - ct := shared.ContainerState{} + ct := api.ContainerState{} resp, err := c.get(fmt.Sprintf("containers/%s/state", name)) if err != nil { return nil, err } - if err := json.Unmarshal(resp.Metadata, &ct); err != nil { + if err := resp.MetadataAsStruct(&ct); err != nil { return nil, err } @@ -1749,19 +1723,19 @@ return resp.Body, nil } -func (c *Client) ProfileConfig(name string) (*shared.ProfileConfig, error) { +func (c *Client) ProfileConfig(name string) (*api.Profile, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } - ct := shared.ProfileConfig{} + ct := api.Profile{} resp, err := c.get(fmt.Sprintf("profiles/%s", name)) if err != nil { return nil, err } - if err := json.Unmarshal(resp.Metadata, &ct); err != nil { + if err := resp.MetadataAsStruct(&ct); err != nil { return nil, err } @@ -1798,11 +1772,11 @@ return err } - _, err = HoistResponse(raw, Sync) + _, err = HoistResponse(raw, api.SyncResponse) return err } -func (c *Client) Mkdir(container string, p string, mode os.FileMode) error { +func (c *Client) Mkdir(container string, p string, mode os.FileMode, uid int, gid int) error { if c.Remote.Public { return fmt.Errorf("This function isn't supported by public remotes.") } @@ -1818,17 +1792,23 @@ req.Header.Set("User-Agent", version.UserAgent) req.Header.Set("X-LXD-type", "directory") req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", mode.Perm())) + if uid != -1 { + req.Header.Set("X-LXD-uid", strconv.FormatUint(uint64(uid), 10)) + } + if gid != -1 { + req.Header.Set("X-LXD-gid", strconv.FormatUint(uint64(gid), 10)) + } raw, err := c.Http.Do(req) if err != nil { return err } - _, err = HoistResponse(raw, Sync) + _, err = HoistResponse(raw, api.SyncResponse) return err } -func (c *Client) MkdirP(container string, p string, mode os.FileMode) error { +func (c *Client) MkdirP(container string, p string, mode os.FileMode, uid int, gid int) error { if c.Remote.Public { return fmt.Errorf("This function isn't supported by public remotes.") } @@ -1858,7 +1838,7 @@ for ; i <= len(parts); i++ { cur := filepath.Join(parts[:i]...) - if err := c.Mkdir(container, cur, mode); err != nil { + if err := c.Mkdir(container, cur, mode, uid, gid); err != nil { return err } } @@ -1885,7 +1865,8 @@ targetPath := path.Join(target, p[appendLen:]) if fInfo.IsDir() { - return c.Mkdir(container, targetPath, fInfo.Mode()) + mode, uid, gid := shared.GetOwnerMode(fInfo) + return c.Mkdir(container, targetPath, mode, uid, gid) } f, err := os.Open(p) @@ -1917,7 +1898,7 @@ uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header) if type_ == "directory" { - resp, err := HoistResponse(r, Sync) + resp, err := HoistResponse(r, api.SyncResponse) if err != nil { return 0, 0, 0, "", nil, nil, err } @@ -1981,7 +1962,7 @@ return nil } -func (c *Client) GetMigrationSourceWS(container string) (*Response, error) { +func (c *Client) GetMigrationSourceWS(container string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -1997,14 +1978,14 @@ url = fmt.Sprintf("containers/%s/snapshots/%s", pieces[0], pieces[1]) } - return c.post(url, body, Async) + return c.post(url, body, api.AsyncResponse) } func (c *Client) MigrateFrom(name string, operation string, certificate string, sourceSecrets map[string]string, architecture string, config map[string]string, - devices shared.Devices, profiles []string, + devices map[string]map[string]string, profiles []string, baseImage string, ephemeral bool, push bool, sourceClient *Client, - sourceOperation string) (*Response, error) { + sourceOperation string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2076,7 +2057,7 @@ // Post to target server and request and retrieve a set of // websockets + secrets matching those of the source server. - resp, err := c.post("containers", body, Async) + resp, err := c.post("containers", body, api.AsyncResponse) if err != nil { return nil, err } @@ -2086,7 +2067,8 @@ if err != nil { return nil, err } - for k, v := range *op.Metadata { + + for k, v := range op.Metadata { destSecrets[k] = v.(string) } @@ -2173,10 +2155,10 @@ return resp, nil } - return c.post("containers", body, Async) + return c.post("containers", body, api.AsyncResponse) } -func (c *Client) Rename(name string, newName string) (*Response, error) { +func (c *Client) Rename(name string, newName string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2188,17 +2170,17 @@ } if len(oldNameParts) == 1 { body := shared.Jmap{"name": newName} - return c.post(fmt.Sprintf("containers/%s", name), body, Async) + return c.post(fmt.Sprintf("containers/%s", name), body, api.AsyncResponse) } if oldNameParts[0] != newNameParts[0] { return nil, fmt.Errorf("Attempting to rename snapshot of one container into a snapshot of another container.") } body := shared.Jmap{"name": newNameParts[1]} - return c.post(fmt.Sprintf("containers/%s/snapshots/%s", oldNameParts[0], oldNameParts[1]), body, Async) + return c.post(fmt.Sprintf("containers/%s/snapshots/%s", oldNameParts[0], oldNameParts[1]), body, api.AsyncResponse) } /* Wait for an operation */ -func (c *Client) WaitFor(waitURL string) (*shared.Operation, error) { +func (c *Client) WaitFor(waitURL string) (*api.Operation, error) { if len(waitURL) < 1 { return nil, fmt.Errorf("invalid wait url %s", waitURL) } @@ -2223,45 +2205,45 @@ return err } - if op.StatusCode == shared.Success { + if op.StatusCode == api.Success { return nil } return fmt.Errorf(op.Err) } -func (c *Client) WaitForSuccessOp(waitURL string) (*shared.Operation, error) { +func (c *Client) WaitForSuccessOp(waitURL string) (*api.Operation, error) { op, err := c.WaitFor(waitURL) if err != nil { return nil, err } - if op.StatusCode == shared.Success { + if op.StatusCode == api.Success { return op, nil } return op, fmt.Errorf(op.Err) } -func (c *Client) RestoreSnapshot(container string, snapshotName string, stateful bool) (*Response, error) { +func (c *Client) RestoreSnapshot(container string, snapshotName string, stateful bool) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } body := shared.Jmap{"restore": snapshotName, "stateful": stateful} - return c.put(fmt.Sprintf("containers/%s", container), body, Async) + return c.put(fmt.Sprintf("containers/%s", container), body, api.AsyncResponse) } -func (c *Client) Snapshot(container string, snapshotName string, stateful bool) (*Response, error) { +func (c *Client) Snapshot(container string, snapshotName string, stateful bool) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } body := shared.Jmap{"name": snapshotName, "stateful": stateful} - return c.post(fmt.Sprintf("containers/%s/snapshots", container), body, Async) + return c.post(fmt.Sprintf("containers/%s/snapshots", container), body, api.AsyncResponse) } -func (c *Client) ListSnapshots(container string) ([]shared.SnapshotInfo, error) { +func (c *Client) ListSnapshots(container string) ([]api.ContainerSnapshot, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2272,16 +2254,16 @@ return nil, err } - var result []shared.SnapshotInfo + var result []api.ContainerSnapshot - if err := json.Unmarshal(resp.Metadata, &result); err != nil { + if err := resp.MetadataAsStruct(&result); err != nil { return nil, err } return result, nil } -func (c *Client) SnapshotInfo(snapName string) (*shared.SnapshotInfo, error) { +func (c *Client) SnapshotInfo(snapName string) (*api.ContainerSnapshot, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2297,9 +2279,9 @@ return nil, err } - var result shared.SnapshotInfo + var result api.ContainerSnapshot - if err := json.Unmarshal(resp.Metadata, &result); err != nil { + if err := resp.MetadataAsStruct(&result); err != nil { return nil, err } @@ -2329,7 +2311,7 @@ return resp, nil } -func (c *Client) SetServerConfig(key string, value string) (*Response, error) { +func (c *Client) SetServerConfig(key string, value string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2341,15 +2323,15 @@ ss.Config[key] = value - return c.put("", ss, Sync) + return c.put("", ss, api.SyncResponse) } -func (c *Client) UpdateServerConfig(ss shared.BriefServerState) (*Response, error) { +func (c *Client) UpdateServerConfig(ss api.ServerPut) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } - return c.put("", ss, Sync) + return c.put("", ss, api.SyncResponse) } /* @@ -2400,7 +2382,7 @@ * snapshot), we expect config to be a sync operation, so let's just * handle it here. */ - resp, err := c.put(fmt.Sprintf("containers/%s", container), st, Async) + resp, err := c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse) if err != nil { return err } @@ -2408,12 +2390,12 @@ return c.WaitForSuccess(resp.Operation) } -func (c *Client) UpdateContainerConfig(container string, st shared.BriefContainerInfo) error { +func (c *Client) UpdateContainerConfig(container string, st api.ContainerPut) error { if c.Remote.Public { return fmt.Errorf("This function isn't supported by public remotes.") } - resp, err := c.put(fmt.Sprintf("containers/%s", container), st, Async) + resp, err := c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse) if err != nil { return err } @@ -2428,7 +2410,7 @@ body := shared.Jmap{"name": p} - _, err := c.post("profiles", body, Sync) + _, err := c.post("profiles", body, api.SyncResponse) return err } @@ -2437,7 +2419,7 @@ return fmt.Errorf("This function isn't supported by public remotes.") } - _, err := c.delete(fmt.Sprintf("profiles/%s", p), nil, Sync) + _, err := c.delete(fmt.Sprintf("profiles/%s", p), nil, api.SyncResponse) return err } @@ -2471,24 +2453,20 @@ st.Config[key] = value } - _, err = c.put(fmt.Sprintf("profiles/%s", profile), st, Sync) + _, err = c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse) return err } -func (c *Client) PutProfile(name string, profile shared.ProfileConfig) error { +func (c *Client) PutProfile(name string, profile api.ProfilePut) error { if c.Remote.Public { return fmt.Errorf("This function isn't supported by public remotes.") } - if profile.Name != name { - return fmt.Errorf("Cannot change profile name") - } - - _, err := c.put(fmt.Sprintf("profiles/%s", name), profile, Sync) + _, err := c.put(fmt.Sprintf("profiles/%s", name), profile, api.SyncResponse) return err } -func (c *Client) ListProfiles() ([]shared.ProfileConfig, error) { +func (c *Client) ListProfiles() ([]api.Profile, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2498,15 +2476,15 @@ return nil, err } - profiles := []shared.ProfileConfig{} - if err := json.Unmarshal(resp.Metadata, &profiles); err != nil { + profiles := []api.Profile{} + if err := resp.MetadataAsStruct(&profiles); err != nil { return nil, err } return profiles, nil } -func (c *Client) AssignProfile(container, profile string) (*Response, error) { +func (c *Client) AssignProfile(container, profile string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2522,10 +2500,10 @@ st.Profiles = nil } - return c.put(fmt.Sprintf("containers/%s", container), st, Async) + return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse) } -func (c *Client) ContainerDeviceDelete(container, devname string) (*Response, error) { +func (c *Client) ContainerDeviceDelete(container, devname string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2535,17 +2513,17 @@ return nil, err } - for n, _ := range st.Devices { + for n := range st.Devices { if n == devname { delete(st.Devices, n) - return c.put(fmt.Sprintf("containers/%s", container), st, Async) + return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse) } } return nil, fmt.Errorf("Device doesn't exist.") } -func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []string) (*Response, error) { +func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2555,7 +2533,7 @@ return nil, err } - newdev := shared.Device{} + newdev := map[string]string{} for _, p := range props { results := strings.SplitN(p, "=", 2) if len(results) != 2 { @@ -2566,18 +2544,18 @@ newdev[k] = v } - if st.Devices != nil && st.Devices.ContainsName(devname) { + if st.Devices != nil && st.Devices[devname] != nil { return nil, fmt.Errorf("device already exists") } newdev["type"] = devtype if st.Devices == nil { - st.Devices = shared.Devices{} + st.Devices = map[string]map[string]string{} } st.Devices[devname] = newdev - return c.put(fmt.Sprintf("containers/%s", container), st, Async) + return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse) } func (c *Client) ContainerListDevices(container string) ([]string, error) { @@ -2596,7 +2574,7 @@ return devs, nil } -func (c *Client) ProfileDeviceDelete(profile, devname string) (*Response, error) { +func (c *Client) ProfileDeviceDelete(profile, devname string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2606,17 +2584,17 @@ return nil, err } - for n, _ := range st.Devices { + for n := range st.Devices { if n == devname { delete(st.Devices, n) - return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync) + return c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse) } } return nil, fmt.Errorf("Device doesn't exist.") } -func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []string) (*Response, error) { +func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []string) (*api.Response, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2626,7 +2604,7 @@ return nil, err } - newdev := shared.Device{} + newdev := map[string]string{} for _, p := range props { results := strings.SplitN(p, "=", 2) if len(results) != 2 { @@ -2636,16 +2614,19 @@ v := results[1] newdev[k] = v } - if st.Devices != nil && st.Devices.ContainsName(devname) { + + if st.Devices != nil && st.Devices[devname] != nil { return nil, fmt.Errorf("device already exists") } + newdev["type"] = devtype if st.Devices == nil { - st.Devices = shared.Devices{} + st.Devices = map[string]map[string]string{} } + st.Devices[devname] = newdev - return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync) + return c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse) } func (c *Client) ProfileListDevices(profile string) ([]string, error) { @@ -2669,7 +2650,7 @@ func WebsocketDial(dialer websocket.Dialer, url string) (*websocket.Conn, error) { conn, raw, err := dialer.Dial(url, http.Header{}) if err != nil { - _, err2 := HoistResponse(raw, Error) + _, err2 := HoistResponse(raw, api.ErrorResponse) if err2 != nil { /* The response isn't one we understand, so return * whatever the original error was. */ @@ -2692,21 +2673,21 @@ } body := shared.Jmap{"config": st.Config, "name": newname, "devices": st.Devices} - _, err = dest.post("profiles", body, Sync) + _, err = dest.post("profiles", body, api.SyncResponse) return err } -func (c *Client) AsyncWaitMeta(resp *Response) (*shared.Jmap, error) { +func (c *Client) AsyncWaitMeta(resp *api.Response) (map[string]interface{}, error) { op, err := c.WaitFor(resp.Operation) if err != nil { return nil, err } - if op.StatusCode == shared.Failure { + if op.StatusCode == api.Failure { return nil, fmt.Errorf(op.Err) } - if op.StatusCode != shared.Success { + if op.StatusCode != api.Success { return nil, fmt.Errorf("got bad op status %s", op.Status) } @@ -2728,17 +2709,17 @@ body["compression_algorithm"] = compression_algorithm } - resp, err := c.post("images", body, Async) + resp, err := c.post("images", body, api.AsyncResponse) if err != nil { return "", err } - jmap, err := c.AsyncWaitMeta(resp) + meta, err := c.AsyncWaitMeta(resp) if err != nil { return "", err } - fingerprint, err := jmap.GetString("fingerprint") + fingerprint, err := shared.Jmap(meta).GetString("fingerprint") if err != nil { return "", err } @@ -2763,38 +2744,34 @@ body := shared.Jmap{"name": name, "config": config} - _, err := c.post("networks", body, Sync) + _, err := c.post("networks", body, api.SyncResponse) return err } -func (c *Client) NetworkGet(name string) (shared.NetworkConfig, error) { +func (c *Client) NetworkGet(name string) (api.Network, error) { if c.Remote.Public { - return shared.NetworkConfig{}, fmt.Errorf("This function isn't supported by public remotes.") + return api.Network{}, fmt.Errorf("This function isn't supported by public remotes.") } resp, err := c.get(fmt.Sprintf("networks/%s", name)) if err != nil { - return shared.NetworkConfig{}, err + return api.Network{}, err } - network := shared.NetworkConfig{} - if err := json.Unmarshal(resp.Metadata, &network); err != nil { - return shared.NetworkConfig{}, err + network := api.Network{} + if err := resp.MetadataAsStruct(&network); err != nil { + return api.Network{}, err } return network, nil } -func (c *Client) NetworkPut(name string, network shared.NetworkConfig) error { +func (c *Client) NetworkPut(name string, network api.NetworkPut) error { if c.Remote.Public { return fmt.Errorf("This function isn't supported by public remotes.") } - if network.Name != name { - return fmt.Errorf("Cannot change network name") - } - - _, err := c.put(fmt.Sprintf("networks/%s", name), network, Sync) + _, err := c.put(fmt.Sprintf("networks/%s", name), network, api.SyncResponse) return err } @@ -2803,11 +2780,11 @@ return fmt.Errorf("This function isn't supported by public remotes.") } - _, err := c.delete(fmt.Sprintf("networks/%s", name), nil, Sync) + _, err := c.delete(fmt.Sprintf("networks/%s", name), nil, api.SyncResponse) return err } -func (c *Client) ListNetworks() ([]shared.NetworkConfig, error) { +func (c *Client) ListNetworks() ([]api.Network, error) { if c.Remote.Public { return nil, fmt.Errorf("This function isn't supported by public remotes.") } @@ -2817,8 +2794,8 @@ return nil, err } - networks := []shared.NetworkConfig{} - if err := json.Unmarshal(resp.Metadata, &networks); err != nil { + networks := []api.Network{} + if err := resp.MetadataAsStruct(&networks); err != nil { return nil, err } diff -Nru lxd-2.7/config.go lxd-2.8/config.go --- lxd-2.7/config.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/config.go 2017-01-25 02:50:49.000000000 +0000 @@ -116,7 +116,7 @@ // SaveConfig writes the provided configuration to the config file. func SaveConfig(c *Config, fname string) error { - for k, _ := range StaticRemotes { + for k := range StaticRemotes { delete(c.Remotes, k) } diff -Nru lxd-2.7/debian/changelog lxd-2.8/debian/changelog --- lxd-2.7/debian/changelog 2016-12-28 12:33:22.000000000 +0000 +++ lxd-2.8/debian/changelog 2017-01-27 00:41:33.000000000 +0000 @@ -1,10 +1,72 @@ -lxd (2.7-0ubuntu2~ubuntu16.04.1) xenial-backports; urgency=medium +lxd (2.8-0ubuntu1~ubuntu16.04.1) xenial-backports; urgency=medium * Backport to Xenial. * Disable shared libraries (unsupported on Xenial). * Use embedded dependencies for backport (needed for go-lxc at least). - -- Stéphane Graber Wed, 28 Dec 2016 13:33:22 +0100 + -- Stéphane Graber Thu, 26 Jan 2017 19:39:47 -0500 + +lxd (2.8-0ubuntu1) zesty; urgency=medium + + * New upstream release (2.8): + - Exec sessions being killed by a signal will now report the signal + number as part of their exit code. + - The first stage of our Go client API rework is now done with a new + api module containing all REST API definitions. + - The dnsmasq instance used for LXD managed bridges is now running + as an unprivileged user. + - VLAN device types are now properly reported in the API and client. + - The client will now show the date an image was last used at (in + lxc image info). + - LXD is now using Weblate for its translations. + + - client: Add a done signal to Monitor API + - client: Better handle http errors + - doc: Add Documentation on Network Configuration via cloud-init + - doc: Update README.md for CI and Weblate + - doc: Update README.md for godoc + - global: Fix typos + - global: "gofmt -s" run + - i18n: Improved and completed french translation + - i18n: Update message catalogs and Japanese translation + - i18n: Update translations from weblate + - lxc: Better handle timestamps + - lxc/file: Fix directory permissions on recursive push + - lxc/init: Properly replace args list + - lxc/list: Fix unused variable + - lxc/list: Sort IP addresses in output + - lxc/network: Better handle network modifications + - lxc/network: Sort UsedBy list on show + - lxc: Properly check yaml errors + - lxc/remote: Update help + - lxd/containers: Allow passing in-memory buffers to a FileResponse + - lxd/containers: Don't attempt to read xattrs from symlinks + - lxd/containers: Improve error handling and reporting during export + - lxd/containers: Report -1 (255) on signal exit during exec + - lxd/containers: Report exit code when we got killed by signal + - lxd/db: Drop unused code from db.go + - lxd/devices: Don't ignore regexp failures + - lxd/images: Close race condition in image download + - lxd/init: We need an address in CIDR notation instead of CIDR subnet + - lxd/migrate: Use the generated snapshot list + - lxd/networks: Clean up leases for static assignments + - lxd/networks: Handle empty dnsmasq pid file + - lxd/networks: Update permissions of network directories + - lxd/patches: Mark all patches as applied on create + - lxd/profiles: Fix unusued variable + - lxd/storage: Don't assume a path is a subvolume + - shared: Add Int64InSlice() + - shared: Have GetByteSizeString() take a precision argument + - shared: Byte parsing in GetByteSizeString() and ParseByteSizeString() + - shared: Move Device/Devices types to lxd package + - shared: ParseByteSizeString() deal with bytes + - shared: Remove GroupName function and add UserId one + - tests: Don't ignore errors in db tests + - tests: Fix deadcode to work with new upstream + - tests: Fix shellcheck being confused by cd + - tests: Use lxc restart whenever possible + + -- Stéphane Graber Tue, 24 Jan 2017 23:38:56 -0500 lxd (2.7-0ubuntu2) zesty; urgency=medium diff -Nru lxd-2.7/debian/.git-dpm lxd-2.8/debian/.git-dpm --- lxd-2.7/debian/.git-dpm 2016-12-21 08:47:38.000000000 +0000 +++ lxd-2.8/debian/.git-dpm 2017-01-25 04:33:36.000000000 +0000 @@ -1,8 +1,8 @@ # see git-dpm(1) from git-dpm package -5b259e3fcb914db70c68a2381ef11bbef6637382 -5b259e3fcb914db70c68a2381ef11bbef6637382 -307876fb50559741d36cf024f3e0e4f0fe656f69 -307876fb50559741d36cf024f3e0e4f0fe656f69 -lxd_2.7.orig.tar.gz -7bf8984c2d7b95111ac56af9b20c1eadf728a5dc -5138212 +c3e95d69b6dcbe0efd42678b09e925c4d8d468bf +c3e95d69b6dcbe0efd42678b09e925c4d8d468bf +c3e95d69b6dcbe0efd42678b09e925c4d8d468bf +c3e95d69b6dcbe0efd42678b09e925c4d8d468bf +lxd_2.8.orig.tar.gz +b84e4e9222a4c6b3c01e1cc1e949494b396f2b72 +5159625 diff -Nru lxd-2.7/debian/patches/0001-tests-Fix-shellcheck-being-confused-by-cd.patch lxd-2.8/debian/patches/0001-tests-Fix-shellcheck-being-confused-by-cd.patch --- lxd-2.7/debian/patches/0001-tests-Fix-shellcheck-being-confused-by-cd.patch 2016-12-21 08:47:38.000000000 +0000 +++ lxd-2.8/debian/patches/0001-tests-Fix-shellcheck-being-confused-by-cd.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -From 5b259e3fcb914db70c68a2381ef11bbef6637382 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?St=C3=A9phane=20Graber?= -Date: Wed, 21 Dec 2016 03:26:54 -0500 -Subject: tests: Fix shellcheck being confused by cd -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Signed-off-by: Stéphane Graber ---- - test/suites/filemanip.sh | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh -index 9f970e3..ab7ade6 100644 ---- a/test/suites/filemanip.sh -+++ b/test/suites/filemanip.sh -@@ -1,6 +1,8 @@ - #!/bin/sh - - test_filemanip() { -+ # Workaround for shellcheck getting confused by "cd" -+ set -e - ensure_import_testimage - ensure_has_localhost_remote "${LXD_ADDR}" - diff -Nru lxd-2.7/debian/patches/series lxd-2.8/debian/patches/series --- lxd-2.7/debian/patches/series 2016-12-21 08:47:38.000000000 +0000 +++ lxd-2.8/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -0001-tests-Fix-shellcheck-being-confused-by-cd.patch diff -Nru lxd-2.7/dist/src/github.com/dustinkirkland/golang-petname/debian/changelog lxd-2.8/dist/src/github.com/dustinkirkland/golang-petname/debian/changelog --- lxd-2.7/dist/src/github.com/dustinkirkland/golang-petname/debian/changelog 2016-12-21 00:52:55.000000000 +0000 +++ lxd-2.8/dist/src/github.com/dustinkirkland/golang-petname/debian/changelog 2017-01-25 03:19:49.000000000 +0000 @@ -1,8 +1,16 @@ -golang-petname (2.6) unreleased; urgency=medium +golang-petname (2.7) unreleased; urgency=medium * UNRELEASED - -- Dustin Kirkland Mon, 14 Nov 2016 09:35:01 -0600 + -- Dustin Kirkland Thu, 05 Jan 2017 15:50:08 -0600 + +golang-petname (2.6-0ubuntu1) zesty; urgency=medium + + [ Michael Hudson-Doyle ] + * Fix repeated use of shlibs:Depends substvar that lead to broken Depends. + - https://github.com/dustinkirkland/golang-petname/pull/2 + + -- Dustin Kirkland Thu, 05 Jan 2017 15:50:06 -0600 golang-petname (2.5-0ubuntu1) zesty; urgency=medium diff -Nru lxd-2.7/dist/src/github.com/dustinkirkland/golang-petname/debian/control lxd-2.8/dist/src/github.com/dustinkirkland/golang-petname/debian/control --- lxd-2.7/dist/src/github.com/dustinkirkland/golang-petname/debian/control 2016-12-21 00:52:55.000000000 +0000 +++ lxd-2.8/dist/src/github.com/dustinkirkland/golang-petname/debian/control 2017-01-25 03:19:49.000000000 +0000 @@ -24,7 +24,7 @@ Package: golang-petname-dev Architecture: any -Depends: ${misc:Depends}, ${shlibs:Depends} +Depends: ${misc:Depends}, ${extra:Depends} Description: golang library for generating pronouncable, memorable, pet names This package provides a library for generating "pet names", consisting of a random combination of an adverb, adjective, and proper name. diff -Nru lxd-2.7/dist/src/github.com/dustinkirkland/golang-petname/debian/rules lxd-2.8/dist/src/github.com/dustinkirkland/golang-petname/debian/rules --- lxd-2.7/dist/src/github.com/dustinkirkland/golang-petname/debian/rules 2016-12-21 00:52:55.000000000 +0000 +++ lxd-2.8/dist/src/github.com/dustinkirkland/golang-petname/debian/rules 2017-01-25 03:19:49.000000000 +0000 @@ -14,5 +14,5 @@ ifeq ($(shell ls -d /usr/share/doc/golang-any-shared-dev/),/usr/share/doc/golang-any-shared-dev/) override_dh_gencontrol: - dh_gencontrol -- -V'shlibs:Depends=libgolang-petname1' + dh_gencontrol -- -V'extra:Depends=libgolang-petname1' endif diff -Nru lxd-2.7/dist/src/github.com/gorilla/mux/doc.go lxd-2.8/dist/src/github.com/gorilla/mux/doc.go --- lxd-2.7/dist/src/github.com/gorilla/mux/doc.go 2016-12-21 00:52:57.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/mux/doc.go 2017-01-25 03:19:51.000000000 +0000 @@ -57,6 +57,11 @@ vars := mux.Vars(request) category := vars["category"] +Note that if any capturing groups are present, mux will panic() during parsing. To prevent +this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to +"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably +when capturing groups were present. + And this is all you need to know about the basic usage. More advanced options are explained below. diff -Nru lxd-2.7/dist/src/github.com/gorilla/mux/mux_test.go lxd-2.8/dist/src/github.com/gorilla/mux/mux_test.go --- lxd-2.7/dist/src/github.com/gorilla/mux/mux_test.go 2016-12-21 00:52:57.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/mux/mux_test.go 2017-01-25 03:19:51.000000000 +0000 @@ -1017,6 +1017,9 @@ func TestSubRouter(t *testing.T) { subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter() subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter() + subrouter3 := new(Route).PathPrefix("/foo").Subrouter() + subrouter4 := new(Route).PathPrefix("/foo/bar").Subrouter() + subrouter5 := new(Route).PathPrefix("/{category}").Subrouter() tests := []routeTest{ { @@ -1057,6 +1060,61 @@ pathTemplate: `/foo/{v1}/baz/{v2}`, shouldMatch: false, }, + { + route: subrouter3.Path("/"), + request: newRequest("GET", "http://localhost/foo/"), + vars: map[string]string{}, + host: "", + path: "/foo/", + pathTemplate: `/foo/`, + shouldMatch: true, + }, + { + route: subrouter3.Path(""), + request: newRequest("GET", "http://localhost/foo"), + vars: map[string]string{}, + host: "", + path: "/foo", + pathTemplate: `/foo`, + shouldMatch: true, + }, + + { + route: subrouter4.Path("/"), + request: newRequest("GET", "http://localhost/foo/bar/"), + vars: map[string]string{}, + host: "", + path: "/foo/bar/", + pathTemplate: `/foo/bar/`, + shouldMatch: true, + }, + { + route: subrouter4.Path(""), + request: newRequest("GET", "http://localhost/foo/bar"), + vars: map[string]string{}, + host: "", + path: "/foo/bar", + pathTemplate: `/foo/bar`, + shouldMatch: true, + }, + { + route: subrouter5.Path("/"), + request: newRequest("GET", "http://localhost/baz/"), + vars: map[string]string{"category": "baz"}, + host: "", + path: "/baz/", + pathTemplate: `/{category}/`, + shouldMatch: true, + }, + { + route: subrouter5.Path(""), + request: newRequest("GET", "http://localhost/baz"), + vars: map[string]string{"category": "baz"}, + host: "", + path: "/baz", + pathTemplate: `/{category}`, + shouldMatch: true, + }, } for _, test := range tests { @@ -1331,6 +1389,16 @@ } } +// See: https://github.com/gorilla/mux/issues/200 +func TestPanicOnCapturingGroups(t *testing.T) { + defer func() { + if recover() == nil { + t.Errorf("(Test that capturing groups now fail fast) Expected panic, however test completed sucessfully.\n") + } + }() + NewRouter().NewRoute().Path("/{type:(promo|special)}/{promoId}.json") +} + // ---------------------------------------------------------------------------- // Helpers // ---------------------------------------------------------------------------- diff -Nru lxd-2.7/dist/src/github.com/gorilla/mux/README.md lxd-2.8/dist/src/github.com/gorilla/mux/README.md --- lxd-2.7/dist/src/github.com/gorilla/mux/README.md 2016-12-21 00:52:57.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/mux/README.md 2017-01-25 03:19:51.000000000 +0000 @@ -23,6 +23,7 @@ * [Install](#install) * [Examples](#examples) * [Matching Routes](#matching-routes) +* [Listing Routes](#listing-routes) * [Static Files](#static-files) * [Registered URLs](#registered-urls) * [Full Example](#full-example) @@ -65,8 +66,11 @@ The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`: ```go -vars := mux.Vars(request) -category := vars["category"] +func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Category: %v\n", vars["category"]) +} ``` And this is all you need to know about the basic usage. More advanced options are explained below. @@ -164,6 +168,42 @@ s.HandleFunc("/{key}/details", ProductDetailsHandler) ``` +### Listing Routes + +Routes on a mux can be listed using the Router.Walk method—useful for generating documentation: + +```go +package main + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" +) + +func handler(w http.ResponseWriter, r *http.Request) { + return +} + +func main() { + r := mux.NewRouter() + r.HandleFunc("/", handler) + r.HandleFunc("/products", handler) + r.HandleFunc("/articles", handler) + r.HandleFunc("/articles/{id}", handler) + r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { + t, err := route.GetPathTemplate() + if err != nil { + return err + } + fmt.Println(t) + return nil + }) + http.Handle("/", r) +} +``` + ### Static Files Note that the path provided to `PathPrefix()` represents a "wildcard": calling diff -Nru lxd-2.7/dist/src/github.com/gorilla/mux/regexp.go lxd-2.8/dist/src/github.com/gorilla/mux/regexp.go --- lxd-2.7/dist/src/github.com/gorilla/mux/regexp.go 2016-12-21 00:52:57.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/mux/regexp.go 2017-01-25 03:19:51.000000000 +0000 @@ -109,6 +109,13 @@ if errCompile != nil { return nil, errCompile } + + // Check for capturing groups which used to work in older versions + if reg.NumSubexp() != len(idxs)/2 { + panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + + "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") + } + // Done! return &routeRegexp{ template: template, diff -Nru lxd-2.7/dist/src/github.com/gorilla/mux/route.go lxd-2.8/dist/src/github.com/gorilla/mux/route.go --- lxd-2.7/dist/src/github.com/gorilla/mux/route.go 2016-12-21 00:52:57.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/mux/route.go 2017-01-25 03:19:51.000000000 +0000 @@ -153,7 +153,7 @@ } r.regexp = r.getRegexpGroup() if !matchHost && !matchQuery { - if len(tpl) == 0 || tpl[0] != '/' { + if tpl == "/" && (len(tpl) == 0 || tpl[0] != '/') { return fmt.Errorf("mux: path must start with a slash, got %q", tpl) } if r.regexp.path != nil { diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/compression.go lxd-2.8/dist/src/github.com/gorilla/websocket/compression.go --- lxd-2.7/dist/src/github.com/gorilla/websocket/compression.go 2016-12-21 00:52:47.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/compression.go 2017-01-25 03:19:42.000000000 +0000 @@ -12,31 +12,45 @@ "sync" ) +const ( + minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6 + maxCompressionLevel = flate.BestCompression + defaultCompressionLevel = 1 +) + var ( - flateWriterPool = sync.Pool{} + flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool + flateReaderPool = sync.Pool{New: func() interface{} { + return flate.NewReader(nil) + }} ) -func decompressNoContextTakeover(r io.Reader) io.Reader { +func decompressNoContextTakeover(r io.Reader) io.ReadCloser { const tail = // Add four bytes as specified in RFC "\x00\x00\xff\xff" + // Add final block to squelch unexpected EOF error from flate reader. "\x01\x00\x00\xff\xff" - return flate.NewReader(io.MultiReader(r, strings.NewReader(tail))) + + fr, _ := flateReaderPool.Get().(io.ReadCloser) + fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) + return &flateReadWrapper{fr} } -func compressNoContextTakeover(w io.WriteCloser) (io.WriteCloser, error) { +func isValidCompressionLevel(level int) bool { + return minCompressionLevel <= level && level <= maxCompressionLevel +} + +func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser { + p := &flateWriterPools[level-minCompressionLevel] tw := &truncWriter{w: w} - i := flateWriterPool.Get() - var fw *flate.Writer - var err error - if i == nil { - fw, err = flate.NewWriter(tw, 3) + fw, _ := p.Get().(*flate.Writer) + if fw == nil { + fw, _ = flate.NewWriter(tw, level) } else { - fw = i.(*flate.Writer) fw.Reset(tw) } - return &flateWrapper{fw: fw, tw: tw}, err + return &flateWriteWrapper{fw: fw, tw: tw, p: p} } // truncWriter is an io.Writer that writes all but the last four bytes of the @@ -75,24 +89,25 @@ return n + nn, err } -type flateWrapper struct { +type flateWriteWrapper struct { fw *flate.Writer tw *truncWriter + p *sync.Pool } -func (w *flateWrapper) Write(p []byte) (int, error) { +func (w *flateWriteWrapper) Write(p []byte) (int, error) { if w.fw == nil { return 0, errWriteClosed } return w.fw.Write(p) } -func (w *flateWrapper) Close() error { +func (w *flateWriteWrapper) Close() error { if w.fw == nil { return errWriteClosed } err1 := w.fw.Flush() - flateWriterPool.Put(w.fw) + w.p.Put(w.fw) w.fw = nil if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { return errors.New("websocket: internal error, unexpected bytes at end of flate stream") @@ -103,3 +118,31 @@ } return err2 } + +type flateReadWrapper struct { + fr io.ReadCloser +} + +func (r *flateReadWrapper) Read(p []byte) (int, error) { + if r.fr == nil { + return 0, io.ErrClosedPipe + } + n, err := r.fr.Read(p) + if err == io.EOF { + // Preemptively place the reader back in the pool. This helps with + // scenarios where the application does not call NextReader() soon after + // this final read. + r.Close() + } + return n, err +} + +func (r *flateReadWrapper) Close() error { + if r.fr == nil { + return io.ErrClosedPipe + } + err := r.fr.Close() + flateReaderPool.Put(r.fr) + r.fr = nil + return err +} diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/compression_test.go lxd-2.8/dist/src/github.com/gorilla/websocket/compression_test.go --- lxd-2.7/dist/src/github.com/gorilla/websocket/compression_test.go 2016-12-21 00:52:47.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/compression_test.go 2017-01-25 03:19:42.000000000 +0000 @@ -2,7 +2,9 @@ import ( "bytes" + "fmt" "io" + "io/ioutil" "testing" ) @@ -29,3 +31,50 @@ } } } + +func textMessages(num int) [][]byte { + messages := make([][]byte, num) + for i := 0; i < num; i++ { + msg := fmt.Sprintf("planet: %d, country: %d, city: %d, street: %d", i, i, i, i) + messages[i] = []byte(msg) + } + return messages +} + +func BenchmarkWriteNoCompression(b *testing.B) { + w := ioutil.Discard + c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) + messages := textMessages(100) + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.WriteMessage(TextMessage, messages[i%len(messages)]) + } + b.ReportAllocs() +} + +func BenchmarkWriteWithCompression(b *testing.B) { + w := ioutil.Discard + c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) + messages := textMessages(100) + c.enableWriteCompression = true + c.newCompressionWriter = compressNoContextTakeover + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.WriteMessage(TextMessage, messages[i%len(messages)]) + } + b.ReportAllocs() +} + +func TestValidCompressionLevel(t *testing.T) { + c := newConn(fakeNetConn{}, false, 1024, 1024) + for _, level := range []int{minCompressionLevel - 1, maxCompressionLevel + 1} { + if err := c.SetCompressionLevel(level); err == nil { + t.Errorf("no error for level %d", level) + } + } + for _, level := range []int{minCompressionLevel, maxCompressionLevel} { + if err := c.SetCompressionLevel(level); err != nil { + t.Errorf("error for level %d", level) + } + } +} diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/conn.go lxd-2.8/dist/src/github.com/gorilla/websocket/conn.go --- lxd-2.7/dist/src/github.com/gorilla/websocket/conn.go 2016-12-21 00:52:47.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/conn.go 2017-01-25 03:19:42.000000000 +0000 @@ -10,6 +10,7 @@ "errors" "io" "io/ioutil" + "math/rand" "net" "strconv" "sync" @@ -180,6 +181,11 @@ errInvalidControlFrame = errors.New("websocket: invalid control frame") ) +func newMaskKey() [4]byte { + n := rand.Uint32() + return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} +} + func hideTempErr(err error) error { if e, ok := err.(net.Error); ok && e.Temporary() { err = &netError{msg: e.Error(), timeout: e.Timeout()} @@ -235,9 +241,11 @@ writeErr error enableWriteCompression bool - newCompressionWriter func(io.WriteCloser) (io.WriteCloser, error) + compressionLevel int + newCompressionWriter func(io.WriteCloser, int) io.WriteCloser // Read fields + reader io.ReadCloser // the current reader returned to the application readErr error br *bufio.Reader readRemaining int64 // bytes remaining in current frame. @@ -253,7 +261,7 @@ messageReader *messageReader // the current low-level reader readDecompress bool // whether last read frame had RSV1 set - newDecompressionReader func(io.Reader) io.Reader + newDecompressionReader func(io.Reader) io.ReadCloser } func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { @@ -278,6 +286,7 @@ readFinal: true, writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize), enableWriteCompression: true, + compressionLevel: defaultCompressionLevel, } c.SetCloseHandler(nil) c.SetPingHandler(nil) @@ -443,11 +452,7 @@ } c.writer = mw if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { - w, err := c.newCompressionWriter(c.writer) - if err != nil { - c.writer = nil - return nil, err - } + w := c.newCompressionWriter(c.writer, c.compressionLevel) mw.compress = true c.writer = w } @@ -855,6 +860,11 @@ // permanent. Once this method returns a non-nil error, all subsequent calls to // this method return the same error. func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { + // Close previous reader, only relevant for decompression. + if c.reader != nil { + c.reader.Close() + c.reader = nil + } c.messageReader = nil c.readLength = 0 @@ -867,11 +877,11 @@ } if frameType == TextMessage || frameType == BinaryMessage { c.messageReader = &messageReader{c} - var r io.Reader = c.messageReader + c.reader = c.messageReader if c.readDecompress { - r = c.newDecompressionReader(r) + c.reader = c.newDecompressionReader(c.reader) } - return frameType, r, nil + return frameType, c.reader, nil } } @@ -933,6 +943,10 @@ return 0, err } +func (r *messageReader) Close() error { + return nil +} + // ReadMessage is a helper method for getting a reader using NextReader and // reading from that reader to a buffer. func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { @@ -969,6 +983,15 @@ // The code argument to h is the received close code or CloseNoStatusReceived // if the close message is empty. The default close handler sends a close frame // back to the peer. +// +// The application must read the connection to process close messages as +// described in the section on Control Frames above. +// +// The connection read methods return a CloseError when a close frame is +// received. Most applications should handle close messages as part of their +// normal error handling. Applications should only set a close handler when the +// application must perform some action before sending a close frame back to +// the peer. func (c *Conn) SetCloseHandler(h func(code int, text string) error) { if h == nil { h = func(code int, text string) error { @@ -991,6 +1014,9 @@ // SetPingHandler sets the handler for ping messages received from the peer. // The appData argument to h is the PING frame application data. The default // ping handler sends a pong to the peer. +// +// The application must read the connection to process ping messages as +// described in the section on Control Frames above. func (c *Conn) SetPingHandler(h func(appData string) error) { if h == nil { h = func(message string) error { @@ -1014,6 +1040,9 @@ // SetPongHandler sets the handler for pong messages received from the peer. // The appData argument to h is the PONG frame application data. The default // pong handler does nothing. +// +// The application must read the connection to process ping messages as +// described in the section on Control Frames above. func (c *Conn) SetPongHandler(h func(appData string) error) { if h == nil { h = func(string) error { return nil } @@ -1034,6 +1063,18 @@ c.enableWriteCompression = enable } +// SetCompressionLevel sets the flate compression level for subsequent text and +// binary messages. This function is a noop if compression was not negotiated +// with the peer. See the compress/flate package for a description of +// compression levels. +func (c *Conn) SetCompressionLevel(level int) error { + if !isValidCompressionLevel(level) { + return errors.New("websocket: invalid compression level") + } + c.compressionLevel = level + return nil +} + // FormatCloseMessage formats closeCode and text as a WebSocket close message. func FormatCloseMessage(closeCode int, text string) []byte { buf := make([]byte, 2+len(text)) diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/doc.go lxd-2.8/dist/src/github.com/gorilla/websocket/doc.go --- lxd-2.7/dist/src/github.com/gorilla/websocket/doc.go 2016-12-21 00:52:47.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/doc.go 2017-01-25 03:19:42.000000000 +0000 @@ -118,9 +118,10 @@ // // Applications are responsible for ensuring that no more than one goroutine // calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, -// WriteJSON) concurrently and that no more than one goroutine calls the read -// methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, -// SetPingHandler) concurrently. +// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and +// that no more than one goroutine calls the read methods (NextReader, +// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) +// concurrently. // // The Close and WriteControl methods can be called concurrently with all other // methods. @@ -150,7 +151,7 @@ // application's responsibility to check the Origin header before calling // Upgrade. // -// Compression [Experimental] +// Compression EXPERIMENTAL // // Per message compression extensions (RFC 7692) are experimentally supported // by this package in a limited capacity. Setting the EnableCompression option @@ -162,7 +163,7 @@ // Per message compression of messages written to a connection can be enabled // or disabled by calling the corresponding Conn method: // -// conn.EnableWriteCompression(true) +// conn.EnableWriteCompression(true) // // Currently this package does not support compression with "context takeover". // This means that messages must be compressed and decompressed in isolation, diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/examples/command/README.md lxd-2.8/dist/src/github.com/gorilla/websocket/examples/command/README.md --- lxd-2.7/dist/src/github.com/gorilla/websocket/examples/command/README.md 2016-12-21 00:52:47.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/examples/command/README.md 2017-01-25 03:19:42.000000000 +0000 @@ -2,7 +2,7 @@ This example connects a websocket connection to stdin and stdout of a command. Received messages are written to stdin followed by a `\n`. Each line read from -from standard out is sent as a message to the client. +standard out is sent as a message to the client. $ go get github.com/gorilla/websocket $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/command` diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/mask.go lxd-2.8/dist/src/github.com/gorilla/websocket/mask.go --- lxd-2.7/dist/src/github.com/gorilla/websocket/mask.go 2016-12-21 00:52:47.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/mask.go 2017-01-25 03:19:42.000000000 +0000 @@ -2,20 +2,14 @@ // this source code is governed by a BSD-style license that can be found in the // LICENSE file. +// +build !appengine + package websocket -import ( - "math/rand" - "unsafe" -) +import "unsafe" const wordSize = int(unsafe.Sizeof(uintptr(0))) -func newMaskKey() [4]byte { - n := rand.Uint32() - return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} -} - func maskBytes(key [4]byte, pos int, b []byte) int { // Mask one byte at a time for small buffers. diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/mask_safe.go lxd-2.8/dist/src/github.com/gorilla/websocket/mask_safe.go --- lxd-2.7/dist/src/github.com/gorilla/websocket/mask_safe.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/mask_safe.go 2017-01-25 03:19:42.000000000 +0000 @@ -0,0 +1,15 @@ +// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in the +// LICENSE file. + +// +build appengine + +package websocket + +func maskBytes(key [4]byte, pos int, b []byte) int { + for i := range b { + b[i] ^= key[pos&3] + pos++ + } + return pos & 3 +} diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/mask_test.go lxd-2.8/dist/src/github.com/gorilla/websocket/mask_test.go --- lxd-2.7/dist/src/github.com/gorilla/websocket/mask_test.go 2016-12-21 00:52:47.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/mask_test.go 2017-01-25 03:19:42.000000000 +0000 @@ -3,7 +3,7 @@ // LICENSE file. // Require 1.7 for sub-bencmarks -// +build go1.7 +// +build go1.7,!appengine package websocket diff -Nru lxd-2.7/dist/src/github.com/gorilla/websocket/server.go lxd-2.8/dist/src/github.com/gorilla/websocket/server.go --- lxd-2.7/dist/src/github.com/gorilla/websocket/server.go 2016-12-21 00:52:47.000000000 +0000 +++ lxd-2.8/dist/src/github.com/gorilla/websocket/server.go 2017-01-25 03:19:42.000000000 +0000 @@ -104,23 +104,23 @@ // response. func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { if r.Method != "GET" { - return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: method not GET") + return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET") } if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { - return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific Sec-Websocket-Extensions headers are unsupported") - } - - if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13") + return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported") } if !tokenListContainsValue(r.Header, "Connection", "upgrade") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find connection header with token 'upgrade'") + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header") } if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find upgrade header with token 'websocket'") + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header") + } + + if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { + return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") } checkOrigin := u.CheckOrigin @@ -128,12 +128,12 @@ checkOrigin = checkSameOrigin } if !checkOrigin(r) { - return u.returnError(w, r, http.StatusForbidden, "websocket: origin not allowed") + return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed") } challengeKey := r.Header.Get("Sec-Websocket-Key") if challengeKey == "" { - return u.returnError(w, r, http.StatusBadRequest, "websocket: key missing or blank") + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank") } subprotocol := u.selectSubprotocol(r, responseHeader) diff -Nru lxd-2.7/dist/src/github.com/mattn/go-sqlite3/doc.go lxd-2.8/dist/src/github.com/mattn/go-sqlite3/doc.go --- lxd-2.7/dist/src/github.com/mattn/go-sqlite3/doc.go 2016-12-21 00:52:59.000000000 +0000 +++ lxd-2.8/dist/src/github.com/mattn/go-sqlite3/doc.go 2017-01-25 03:19:54.000000000 +0000 @@ -110,5 +110,3 @@ */ package sqlite3 - -import "C" diff -Nru lxd-2.7/dist/src/github.com/mattn/go-sqlite3/sqlite3.go lxd-2.8/dist/src/github.com/mattn/go-sqlite3/sqlite3.go --- lxd-2.7/dist/src/github.com/mattn/go-sqlite3/sqlite3.go 2016-12-21 00:52:59.000000000 +0000 +++ lxd-2.8/dist/src/github.com/mattn/go-sqlite3/sqlite3.go 2017-01-25 03:19:54.000000000 +0000 @@ -429,6 +429,7 @@ if s.(*SQLiteStmt).s != nil { na := s.NumInput() if len(args) < na { + s.Close() return nil, fmt.Errorf("Not enough args to execute query. Expected %d, got %d.", na, len(args)) } for i := 0; i < na; i++ { @@ -808,13 +809,13 @@ done := make(chan struct{}) defer close(done) - go func() { + go func(db *C.sqlite3) { select { case <-ctx.Done(): - C.sqlite3_interrupt(s.c.db) + C.sqlite3_interrupt(db) case <-done: } - }() + }(s.c.db) var rowid, changes C.longlong rv := C._sqlite3_step(s.s, &rowid, &changes) diff -Nru lxd-2.7/dist/src/github.com/mattn/go-sqlite3/sqlite3_test.go lxd-2.8/dist/src/github.com/mattn/go-sqlite3/sqlite3_test.go --- lxd-2.7/dist/src/github.com/mattn/go-sqlite3/sqlite3_test.go 2016-12-21 00:52:59.000000000 +0000 +++ lxd-2.8/dist/src/github.com/mattn/go-sqlite3/sqlite3_test.go 2017-01-25 03:19:54.000000000 +0000 @@ -1184,66 +1184,6 @@ } } -type sumAggregator int64 - -func (s *sumAggregator) Step(x int64) { - *s += sumAggregator(x) -} - -func (s *sumAggregator) Done() int64 { - return int64(*s) -} - -func TestAggregatorRegistration(t *testing.T) { - customSum := func() *sumAggregator { - var ret sumAggregator - return &ret - } - - sql.Register("sqlite3_AggregatorRegistration", &SQLiteDriver{ - ConnectHook: func(conn *SQLiteConn) error { - if err := conn.RegisterAggregator("customSum", customSum, true); err != nil { - return err - } - return nil - }, - }) - db, err := sql.Open("sqlite3_AggregatorRegistration", ":memory:") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("create table foo (department integer, profits integer)") - if err != nil { - // trace feature is not implemented - t.Skip("Failed to create table:", err) - } - - _, err = db.Exec("insert into foo values (1, 10), (1, 20), (2, 42)") - if err != nil { - t.Fatal("Failed to insert records:", err) - } - - tests := []struct { - dept, sum int64 - }{ - {1, 30}, - {2, 42}, - } - - for _, test := range tests { - var ret int64 - err = db.QueryRow("select customSum(profits) from foo where department = $1 group by department", test.dept).Scan(&ret) - if err != nil { - t.Fatal("Query failed:", err) - } - if ret != test.sum { - t.Fatalf("Custom sum returned wrong value, got %d, want %d", ret, test.sum) - } - } -} - func TestDeclTypes(t *testing.T) { d := SQLiteDriver{} diff -Nru lxd-2.7/dist/src/github.com/mattn/go-sqlite3/sqlite3_trace_test.go lxd-2.8/dist/src/github.com/mattn/go-sqlite3/sqlite3_trace_test.go --- lxd-2.7/dist/src/github.com/mattn/go-sqlite3/sqlite3_trace_test.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/dist/src/github.com/mattn/go-sqlite3/sqlite3_trace_test.go 2017-01-25 03:19:54.000000000 +0000 @@ -0,0 +1,72 @@ +// Copyright (C) 2016 Yasuhiro Matsumoto . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +build trace + +package sqlite3 + +import ( + "database/sql" + "testing" +) + +type sumAggregator int64 + +func (s *sumAggregator) Step(x int64) { + *s += sumAggregator(x) +} + +func (s *sumAggregator) Done() int64 { + return int64(*s) +} + +func TestAggregatorRegistration(t *testing.T) { + customSum := func() *sumAggregator { + var ret sumAggregator + return &ret + } + + sql.Register("sqlite3_AggregatorRegistration", &SQLiteDriver{ + ConnectHook: func(conn *SQLiteConn) error { + if err := conn.RegisterAggregator("customSum", customSum, true); err != nil { + return err + } + return nil + }, + }) + db, err := sql.Open("sqlite3_AggregatorRegistration", ":memory:") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + _, err = db.Exec("create table foo (department integer, profits integer)") + if err != nil { + // trace feature is not implemented + t.Skip("Failed to create table:", err) + } + + _, err = db.Exec("insert into foo values (1, 10), (1, 20), (2, 42)") + if err != nil { + t.Fatal("Failed to insert records:", err) + } + + tests := []struct { + dept, sum int64 + }{ + {1, 30}, + {2, 42}, + } + + for _, test := range tests { + var ret int64 + err = db.QueryRow("select customSum(profits) from foo where department = $1 group by department", test.dept).Scan(&ret) + if err != nil { + t.Fatal("Query failed:", err) + } + if ret != test.sum { + t.Fatalf("Custom sum returned wrong value, got %d, want %d", ret, test.sum) + } + } +} diff -Nru lxd-2.7/dist/src/github.com/mattn/go-sqlite3/tracecallback.go lxd-2.8/dist/src/github.com/mattn/go-sqlite3/tracecallback.go --- lxd-2.7/dist/src/github.com/mattn/go-sqlite3/tracecallback.go 2016-12-21 00:52:59.000000000 +0000 +++ lxd-2.8/dist/src/github.com/mattn/go-sqlite3/tracecallback.go 2017-01-25 03:19:54.000000000 +0000 @@ -1,5 +1,4 @@ // Copyright (C) 2016 Yasuhiro Matsumoto . -// TODO: add "Gimpl do foo" team? // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. diff -Nru lxd-2.7/dist/src/github.com/mattn/go-sqlite3/tracecallback_noimpl.go lxd-2.8/dist/src/github.com/mattn/go-sqlite3/tracecallback_noimpl.go --- lxd-2.7/dist/src/github.com/mattn/go-sqlite3/tracecallback_noimpl.go 2016-12-21 00:52:59.000000000 +0000 +++ lxd-2.8/dist/src/github.com/mattn/go-sqlite3/tracecallback_noimpl.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -// +build !trace - -package sqlite3 - -import "errors" - -// RegisterAggregator register the aggregator. -func (c *SQLiteConn) RegisterAggregator(name string, impl interface{}, pure bool) error { - return errors.New("This feature is not implemented") -} diff -Nru lxd-2.7/dist/src/github.com/olekukonko/tablewriter/csv2table/csv2table.go lxd-2.8/dist/src/github.com/olekukonko/tablewriter/csv2table/csv2table.go --- lxd-2.7/dist/src/github.com/olekukonko/tablewriter/csv2table/csv2table.go 2016-12-21 00:52:52.000000000 +0000 +++ lxd-2.8/dist/src/github.com/olekukonko/tablewriter/csv2table/csv2table.go 2017-01-25 03:19:46.000000000 +0000 @@ -4,10 +4,11 @@ "encoding/csv" "flag" "fmt" - "github.com/olekukonko/tablewriter" "io" "os" "unicode/utf8" + + "github.com/olekukonko/tablewriter" ) var ( @@ -16,7 +17,7 @@ header = flag.Bool("h", true, "Set header options eg. true|false ") align = flag.String("a", "none", "Set aligmement with eg. none|left|right|center") pipe = flag.Bool("p", false, "Suport for Piping from STDIN") - border = flag.Bool("b", true, "Enable / disable table border") + border = flag.Bool("b", true, "Enable / disable table border") ) func main() { @@ -37,7 +38,7 @@ } func hasArg(name string) bool { - for _ , v := range os.Args { + for _, v := range os.Args { if name == v { return true } diff -Nru lxd-2.7/dist/src/github.com/olekukonko/tablewriter/README.md lxd-2.8/dist/src/github.com/olekukonko/tablewriter/README.md --- lxd-2.7/dist/src/github.com/olekukonko/tablewriter/README.md 2016-12-21 00:52:52.000000000 +0000 +++ lxd-2.8/dist/src/github.com/olekukonko/tablewriter/README.md 2017-01-25 03:19:46.000000000 +0000 @@ -21,6 +21,7 @@ - Make CSV Headers optional - Enable or disable table border - Set custom footer support +- Optional identical cells merging #### Example 1 - Basic @@ -158,6 +159,41 @@ | 1/4/2014 | February Extra Bandwidth | 2233 | $30.00 | ``` +#### Example 6 - Identical cells merging +```go +data := [][]string{ + []string{"1/1/2014", "Domain name", "1234", "$10.98"}, + []string{"1/1/2014", "January Hosting", "2345", "$54.95"}, + []string{"1/4/2014", "February Hosting", "3456", "$51.00"}, + []string{"1/4/2014", "February Extra Bandwidth", "4567", "$30.00"}, +} + +table := NewWriter(os.Stdout) +table.SetHeader([]string{"Date", "Description", "CV2", "Amount"}) +table.SetFooter([]string{"", "", "Total", "$146.93"}) +table.SetAutoMergeCells(true) +table.SetRowLine(true) +table.AppendBulk(data) +table.Render() +``` + +##### Output 6 +``` ++----------+--------------------------+-------+---------+ +| DATE | DESCRIPTION | CV2 | AMOUNT | ++----------+--------------------------+-------+---------+ +| 1/1/2014 | Domain name | 1234 | $10.98 | ++ +--------------------------+-------+---------+ +| | January Hosting | 2345 | $54.95 | ++----------+--------------------------+-------+---------+ +| 1/4/2014 | February Hosting | 3456 | $51.00 | ++ +--------------------------+-------+---------+ +| | February Extra Bandwidth | 4567 | $30.00 | ++----------+--------------------------+-------+---------+ +| TOTAL | $146 93 | ++----------+--------------------------+-------+---------+ +``` + #### TODO - ~~Import Directly from CSV~~ - `done` - ~~Support for `SetFooter`~~ - `done` diff -Nru lxd-2.7/dist/src/github.com/olekukonko/tablewriter/table.go lxd-2.8/dist/src/github.com/olekukonko/tablewriter/table.go --- lxd-2.7/dist/src/github.com/olekukonko/tablewriter/table.go 2016-12-21 00:52:52.000000000 +0000 +++ lxd-2.8/dist/src/github.com/olekukonko/tablewriter/table.go 2017-01-25 03:19:46.000000000 +0000 @@ -9,6 +9,7 @@ package tablewriter import ( + "bytes" "fmt" "io" "regexp" @@ -47,29 +48,30 @@ } type Table struct { - out io.Writer - rows [][]string - lines [][][]string - cs map[int]int - rs map[int]int - headers []string - footers []string - autoFmt bool - autoWrap bool - mW int - pCenter string - pRow string - pColumn string - tColumn int - tRow int - hAlign int - fAlign int - align int - newLine string - rowLine bool - hdrLine bool - borders Border - colSize int + out io.Writer + rows [][]string + lines [][][]string + cs map[int]int + rs map[int]int + headers []string + footers []string + autoFmt bool + autoWrap bool + mW int + pCenter string + pRow string + pColumn string + tColumn int + tRow int + hAlign int + fAlign int + align int + newLine string + rowLine bool + autoMergeCells bool + hdrLine bool + borders Border + colSize int } // Start New Table @@ -108,7 +110,11 @@ t.printLine(true) } t.printHeading() - t.printRows() + if t.autoMergeCells { + t.printRowsMergeCells() + } else { + t.printRows() + } if !t.rowLine && t.borders.Bottom { t.printLine(true) @@ -197,6 +203,12 @@ t.rowLine = line } +// Set Auto Merge Cells +// This would enable / disable the merge of cells with identical values +func (t *Table) SetAutoMergeCells(auto bool) { + t.autoMergeCells = auto +} + // Set Table Border // This would enable / disable line around the table func (t *Table) SetBorder(border bool) { @@ -253,6 +265,30 @@ } } +// Print line based on row width with our without cell separator +func (t Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) { + fmt.Fprint(t.out, t.pCenter) + for i := 0; i < len(t.cs); i++ { + v := t.cs[i] + if i > len(displayCellSeparator) || displayCellSeparator[i] { + // Display the cell separator + fmt.Fprintf(t.out, "%s%s%s%s", + t.pRow, + strings.Repeat(string(t.pRow), v), + t.pRow, + t.pCenter) + } else { + // Don't display the cell separator for this cell + fmt.Fprintf(t.out, "%s%s", + strings.Repeat(" ", v+2), + t.pCenter) + } + } + if nl { + fmt.Fprint(t.out, t.newLine) + } +} + // Return the PadRight function if align is left, PadLeft if align is right, // and Pad by default func pad(align int) func(string, string, int) string { @@ -475,7 +511,104 @@ if t.rowLine { t.printLine(true) } +} + +// Print the rows of the table and merge the cells that are identical +func (t Table) printRowsMergeCells() { + var previousLine []string + var displayCellBorder []bool + var tmpWriter bytes.Buffer + for i, lines := range t.lines { + // We store the display of the current line in a tmp writer, as we need to know which border needs to be print above + previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine) + if i > 0 { //We don't need to print borders above first line + if t.rowLine { + t.printLineOptionalCellSeparators(true, displayCellBorder) + } + } + tmpWriter.WriteTo(t.out) + } + //Print the end of the table + if t.rowLine { + t.printLine(true) + } +} + +// Print Row Information to a writer and merge identical cells. +// Adjust column alignment based on type + +func (t Table) printRowMergeCells(writer io.Writer, columns [][]string, colKey int, previousLine []string) ([]string, []bool) { + // Get Maximum Height + max := t.rs[colKey] + total := len(columns) + + // Pad Each Height + pads := []int{} + + for i, line := range columns { + length := len(line) + pad := max - length + pads = append(pads, pad) + for n := 0; n < pad; n++ { + columns[i] = append(columns[i], " ") + } + } + + var displayCellBorder []bool + for x := 0; x < max; x++ { + for y := 0; y < total; y++ { + + // Check if border is set + fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn)) + + fmt.Fprintf(writer, SPACE) + + str := columns[y][x] + + if t.autoMergeCells { + //Store the full line to merge mutli-lines cells + fullLine := strings.Join(columns[y], " ") + if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" { + // If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty. + displayCellBorder = append(displayCellBorder, false) + str = "" + } else { + // First line or different content, keep the content and print the cell border + displayCellBorder = append(displayCellBorder, true) + } + } + // This would print alignment + // Default alignment would use multiple configuration + switch t.align { + case ALIGN_CENTER: // + fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y])) + case ALIGN_RIGHT: + fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y])) + case ALIGN_LEFT: + fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y])) + default: + if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) { + fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y])) + } else { + fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y])) + } + } + fmt.Fprintf(writer, SPACE) + } + // Check if border is set + // Replace with space if not set + fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE)) + fmt.Fprint(writer, t.newLine) + } + + //The new previous line is the current one + previousLine = make([]string, total) + for y := 0; y < total; y++ { + previousLine[y] = strings.Join(columns[y], " ") //Store the full line for multi-lines cells + } + //Returns the newly added line and wether or not a border should be displayed above. + return previousLine, displayCellBorder } func (t *Table) parseDimension(str string, colKey, rowKey int) []string { diff -Nru lxd-2.7/dist/src/github.com/olekukonko/tablewriter/table_test.go lxd-2.8/dist/src/github.com/olekukonko/tablewriter/table_test.go --- lxd-2.7/dist/src/github.com/olekukonko/tablewriter/table_test.go 2016-12-21 00:52:52.000000000 +0000 +++ lxd-2.8/dist/src/github.com/olekukonko/tablewriter/table_test.go 2017-01-25 03:19:46.000000000 +0000 @@ -14,6 +14,8 @@ "os" "strings" "testing" + + "github.com/mattn/go-runewidth" ) func ExampleShort() { @@ -80,8 +82,13 @@ return } table.SetRowLine(true) - table.SetCenterSeparator("*") - table.SetColumnSeparator("‡") + if runewidth.IsEastAsian() { + table.SetCenterSeparator("*") + table.SetColumnSeparator("‡") + } else { + table.SetCenterSeparator("*") + table.SetColumnSeparator("‡") + } table.SetRowSeparator("-") table.SetAlignment(ALIGN_LEFT) table.Render() @@ -91,14 +98,17 @@ data := [][]string{ []string{"1/1/2014", "Domain name", "2233", "$10.98"}, []string{"1/1/2014", "January Hosting", "2233", "$54.95"}, + []string{"", " (empty)\n (empty)", "", ""}, []string{"1/4/2014", "February Hosting", "2233", "$51.00"}, []string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"}, + []string{"1/4/2014", " (Discount)", "2233", "-$1.00"}, } var buf bytes.Buffer table := NewWriter(&buf) + table.SetAutoWrapText(false) table.SetHeader([]string{"Date", "Description", "CV2", "Amount"}) - table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer + table.SetFooter([]string{"", "", "Total", "$145.93"}) // Add Footer table.SetBorder(false) // Set Border to false table.AppendBulk(data) // Add Bulk Data table.Render() @@ -107,10 +117,13 @@ +----------+--------------------------+-------+---------+ 1/1/2014 | Domain name | 2233 | $10.98 1/1/2014 | January Hosting | 2233 | $54.95 + | (empty) | | + | (empty) | | 1/4/2014 | February Hosting | 2233 | $51.00 1/4/2014 | February Extra Bandwidth | 2233 | $30.00 + 1/4/2014 | (Discount) | 2233 | -$1.00 +----------+--------------------------+-------+---------+ - TOTAL | $146 93 + TOTAL | $145 93 +-------+---------+ ` got := buf.String() @@ -123,14 +136,17 @@ data := [][]string{ []string{"1/1/2014", "Domain name", "2233", "$10.98"}, []string{"1/1/2014", "January Hosting", "2233", "$54.95"}, + []string{"", " (empty)\n (empty)", "", ""}, []string{"1/4/2014", "February Hosting", "2233", "$51.00"}, []string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"}, + []string{"1/4/2014", " (Discount)", "2233", "-$1.00"}, } var buf bytes.Buffer table := NewWriter(&buf) + table.SetAutoWrapText(false) table.SetHeader([]string{"Date", "Description", "CV2", "Amount"}) - table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer + table.SetFooter([]string{"", "", "Total", "$145.93"}) // Add Footer table.AppendBulk(data) // Add Bulk Data table.Render() @@ -139,10 +155,13 @@ +----------+--------------------------+-------+---------+ | 1/1/2014 | Domain name | 2233 | $10.98 | | 1/1/2014 | January Hosting | 2233 | $54.95 | +| | (empty) | | | +| | (empty) | | | | 1/4/2014 | February Hosting | 2233 | $51.00 | | 1/4/2014 | February Extra Bandwidth | 2233 | $30.00 | +| 1/4/2014 | (Discount) | 2233 | -$1.00 | +----------+--------------------------+-------+---------+ -| TOTAL | $146 93 | +| TOTAL | $145 93 | +----------+--------------------------+-------+---------+ ` got := buf.String() @@ -352,3 +371,94 @@ t.Error(fmt.Sprintf("Unexpected output '%v' != '%v'", output, want)) } } + +func TestAutoMergeRows(t *testing.T) { + data := [][]string{ + []string{"A", "The Good", "500"}, + []string{"A", "The Very very Bad Man", "288"}, + []string{"B", "The Very very Bad Man", "120"}, + []string{"B", "The Very very Bad Man", "200"}, + } + var buf bytes.Buffer + table := NewWriter(&buf) + table.SetHeader([]string{"Name", "Sign", "Rating"}) + + for _, v := range data { + table.Append(v) + } + table.SetAutoMergeCells(true) + table.Render() + want := `+------+-----------------------+--------+ +| NAME | SIGN | RATING | ++------+-----------------------+--------+ +| A | The Good | 500 | +| | The Very very Bad Man | 288 | +| B | | 120 | +| | | 200 | ++------+-----------------------+--------+ +` + got := buf.String() + if got != want { + t.Errorf("\ngot:\n%s\nwant:\n%s\n", got, want) + } + + buf.Reset() + table = NewWriter(&buf) + table.SetHeader([]string{"Name", "Sign", "Rating"}) + + for _, v := range data { + table.Append(v) + } + table.SetAutoMergeCells(true) + table.SetRowLine(true) + table.Render() + want = `+------+-----------------------+--------+ +| NAME | SIGN | RATING | ++------+-----------------------+--------+ +| A | The Good | 500 | ++ +-----------------------+--------+ +| | The Very very Bad Man | 288 | ++------+ +--------+ +| B | | 120 | ++ + +--------+ +| | | 200 | ++------+-----------------------+--------+ +` + got = buf.String() + if got != want { + t.Errorf("\ngot:\n%s\nwant:\n%s\n", got, want) + } + + buf.Reset() + table = NewWriter(&buf) + table.SetHeader([]string{"Name", "Sign", "Rating"}) + + dataWithlongText := [][]string{ + []string{"A", "The Good", "500"}, + []string{"A", "The Very very very very very Bad Man", "288"}, + []string{"B", "The Very very very very very Bad Man", "120"}, + []string{"C", "The Very very Bad Man", "200"}, + } + table.AppendBulk(dataWithlongText) + table.SetAutoMergeCells(true) + table.SetRowLine(true) + table.Render() + want = `+------+--------------------------------+--------+ +| NAME | SIGN | RATING | ++------+--------------------------------+--------+ +| A | The Good | 500 | ++------+--------------------------------+--------+ +| A | The Very very very very very | 288 | +| | Bad Man | | ++------+ +--------+ +| B | | 120 | +| | | | ++------+--------------------------------+--------+ +| C | The Very very Bad Man | 200 | ++------+--------------------------------+--------+ +` + got = buf.String() + if got != want { + t.Errorf("\ngot:\n%s\nwant:\n%s\n", got, want) + } +} diff -Nru lxd-2.7/dist/src/github.com/olekukonko/tablewriter/wrap.go lxd-2.8/dist/src/github.com/olekukonko/tablewriter/wrap.go --- lxd-2.7/dist/src/github.com/olekukonko/tablewriter/wrap.go 2016-12-21 00:52:52.000000000 +0000 +++ lxd-2.8/dist/src/github.com/olekukonko/tablewriter/wrap.go 2017-01-25 03:19:46.000000000 +0000 @@ -23,7 +23,7 @@ // Wrap wraps s into a paragraph of lines of length lim, with minimal // raggedness. func WrapString(s string, lim int) ([]string, int) { - words := strings.Split(strings.Replace(strings.TrimSpace(s), nl, sp, -1), sp) + words := strings.Split(strings.Replace(s, nl, sp, -1), sp) var lines []string max := 0 for _, v := range words { @@ -96,7 +96,7 @@ func getLines(s string) []string { var lines []string - for _, line := range strings.Split(strings.TrimSpace(s), nl) { + for _, line := range strings.Split(s, nl) { lines = append(lines, line) } return lines diff -Nru lxd-2.7/dist/src/github.com/olekukonko/tablewriter/wrap_test.go lxd-2.8/dist/src/github.com/olekukonko/tablewriter/wrap_test.go --- lxd-2.7/dist/src/github.com/olekukonko/tablewriter/wrap_test.go 2016-12-21 00:52:52.000000000 +0000 +++ lxd-2.8/dist/src/github.com/olekukonko/tablewriter/wrap_test.go 2017-01-25 03:19:46.000000000 +0000 @@ -10,6 +10,8 @@ import ( "strings" "testing" + + "github.com/mattn/go-runewidth" ) var text = "The quick brown fox jumps over the lazy dog." @@ -30,7 +32,7 @@ words, _ := WrapString(text, 500) got := strings.Join(words, string(sp)) if exp != got { - t.Fail() + t.Errorf("expected: %q, got: %q", exp, got) } } @@ -45,11 +47,15 @@ func TestDisplayWidth(t *testing.T) { input := "Česká řeřicha" - if n := DisplayWidth(input); n != 13 { - t.Errorf("Wants: %d Got: %d", 13, n) + want := 13 + if runewidth.IsEastAsian() { + want = 14 + } + if n := DisplayWidth(input); n != want { + t.Errorf("Wants: %d Got: %d", want, n) } input = "\033[43;30m" + input + "\033[00m" - if n := DisplayWidth(input); n != 13 { - t.Errorf("Wants: %d Got: %d", 13, n) + if n := DisplayWidth(input); n != want { + t.Errorf("Wants: %d Got: %d", want, n) } } diff -Nru lxd-2.7/dist/src/github.com/pborman/uuid/json.go lxd-2.8/dist/src/github.com/pborman/uuid/json.go --- lxd-2.7/dist/src/github.com/pborman/uuid/json.go 2016-12-21 00:53:02.000000000 +0000 +++ lxd-2.8/dist/src/github.com/pborman/uuid/json.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -// Copyright 2014 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uuid - -import "errors" - -func (u UUID) MarshalJSON() ([]byte, error) { - if len(u) != 16 { - return []byte(`""`), nil - } - var js [38]byte - js[0] = '"' - encodeHex(js[1:], u) - js[37] = '"' - return js[:], nil -} - -func (u *UUID) UnmarshalJSON(data []byte) error { - if string(data) == `""` { - return nil - } - if data[0] != '"' { - return errors.New("invalid UUID format") - } - data = data[1 : len(data)-1] - uu := Parse(string(data)) - if uu == nil { - return errors.New("invalid UUID format") - } - *u = uu - return nil -} diff -Nru lxd-2.7/dist/src/github.com/pborman/uuid/json_test.go lxd-2.8/dist/src/github.com/pborman/uuid/json_test.go --- lxd-2.7/dist/src/github.com/pborman/uuid/json_test.go 2016-12-21 00:53:02.000000000 +0000 +++ lxd-2.8/dist/src/github.com/pborman/uuid/json_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -// Copyright 2014 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uuid - -import ( - "encoding/json" - "reflect" - "testing" -) - -var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") - -func TestJSON(t *testing.T) { - type S struct { - ID1 UUID - ID2 UUID - } - s1 := S{ID1: testUUID} - data, err := json.Marshal(&s1) - if err != nil { - t.Fatal(err) - } - var s2 S - if err := json.Unmarshal(data, &s2); err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(&s1, &s2) { - t.Errorf("got %#v, want %#v", s2, s1) - } -} - -func BenchmarkUUID_MarshalJSON(b *testing.B) { - x := &struct { - UUID UUID `json:"uuid"` - }{} - x.UUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") - if x.UUID == nil { - b.Fatal("invalid uuid") - } - for i := 0; i < b.N; i++ { - js, err := json.Marshal(x) - if err != nil { - b.Fatalf("marshal json: %#v (%v)", js, err) - } - } -} - -func BenchmarkUUID_UnmarshalJSON(b *testing.B) { - js := []byte(`{"uuid":"f47ac10b-58cc-0372-8567-0e02b2c3d479"}`) - var x *struct { - UUID UUID `json:"uuid"` - } - for i := 0; i < b.N; i++ { - err := json.Unmarshal(js, &x) - if err != nil { - b.Fatalf("marshal json: %#v (%v)", js, err) - } - } -} diff -Nru lxd-2.7/dist/src/github.com/pborman/uuid/marshal.go lxd-2.8/dist/src/github.com/pborman/uuid/marshal.go --- lxd-2.7/dist/src/github.com/pborman/uuid/marshal.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/dist/src/github.com/pborman/uuid/marshal.go 2017-01-25 03:19:57.000000000 +0000 @@ -0,0 +1,83 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "errors" + "fmt" +) + +// MarshalText implements encoding.TextMarshaler. +func (u UUID) MarshalText() ([]byte, error) { + if len(u) != 16 { + return nil, nil + } + var js [36]byte + encodeHex(js[:], u) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (u *UUID) UnmarshalText(data []byte) error { + if len(data) == 0 { + return nil + } + id := Parse(string(data)) + if id == nil { + return errors.New("invalid UUID") + } + *u = id + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (u UUID) MarshalBinary() ([]byte, error) { + return u[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (u *UUID) UnmarshalBinary(data []byte) error { + if len(data) == 0 { + return nil + } + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + var id [16]byte + copy(id[:], data) + *u = id[:] + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (u Array) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], u[:]) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (u *Array) UnmarshalText(data []byte) error { + id := Parse(string(data)) + if id == nil { + return errors.New("invalid UUID") + } + *u = id.Array() + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (u Array) MarshalBinary() ([]byte, error) { + return u[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (u *Array) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(u[:], data) + return nil +} diff -Nru lxd-2.7/dist/src/github.com/pborman/uuid/marshal_test.go lxd-2.8/dist/src/github.com/pborman/uuid/marshal_test.go --- lxd-2.7/dist/src/github.com/pborman/uuid/marshal_test.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/dist/src/github.com/pborman/uuid/marshal_test.go 2017-01-25 03:19:57.000000000 +0000 @@ -0,0 +1,124 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "encoding/json" + "reflect" + "testing" +) + +var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") +var testArray = testUUID.Array() + +func TestJSON(t *testing.T) { + type S struct { + ID1 UUID + ID2 UUID + } + s1 := S{ID1: testUUID} + data, err := json.Marshal(&s1) + if err != nil { + t.Fatal(err) + } + var s2 S + if err := json.Unmarshal(data, &s2); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(&s1, &s2) { + t.Errorf("got %#v, want %#v", s2, s1) + } +} + +func TestJSONArray(t *testing.T) { + type S struct { + ID1 Array + ID2 Array + } + s1 := S{ID1: testArray} + data, err := json.Marshal(&s1) + if err != nil { + t.Fatal(err) + } + var s2 S + if err := json.Unmarshal(data, &s2); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(&s1, &s2) { + t.Errorf("got %#v, want %#v", s2, s1) + } +} + +func TestMarshal(t *testing.T) { + data, err := testUUID.MarshalBinary() + if err != nil { + t.Fatalf("MarhsalBinary returned unexpected error %v", err) + } + if !bytes.Equal(data, testUUID) { + t.Fatalf("MarhsalBinary returns %x, want %x", data, testUUID) + } + var u UUID + u.UnmarshalBinary(data) + if !Equal(data, u) { + t.Fatalf("UnmarhsalBinary returns %v, want %v", u, testUUID) + } +} + +func TestMarshalArray(t *testing.T) { + data, err := testArray.MarshalBinary() + if err != nil { + t.Fatalf("MarhsalBinary returned unexpected error %v", err) + } + if !bytes.Equal(data, testUUID) { + t.Fatalf("MarhsalBinary returns %x, want %x", data, testUUID) + } + var a Array + a.UnmarshalBinary(data) + if a != testArray { + t.Fatalf("UnmarhsalBinary returns %v, want %v", a, testArray) + } +} + +func TestMarshalTextArray(t *testing.T) { + data, err := testArray.MarshalText() + if err != nil { + t.Fatalf("MarhsalText returned unexpected error %v", err) + } + var a Array + a.UnmarshalText(data) + if a != testArray { + t.Fatalf("UnmarhsalText returns %v, want %v", a, testArray) + } +} + +func BenchmarkUUID_MarshalJSON(b *testing.B) { + x := &struct { + UUID UUID `json:"uuid"` + }{} + x.UUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if x.UUID == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + js, err := json.Marshal(x) + if err != nil { + b.Fatalf("marshal json: %#v (%v)", js, err) + } + } +} + +func BenchmarkUUID_UnmarshalJSON(b *testing.B) { + js := []byte(`{"uuid":"f47ac10b-58cc-0372-8567-0e02b2c3d479"}`) + var x *struct { + UUID UUID `json:"uuid"` + } + for i := 0; i < b.N; i++ { + err := json.Unmarshal(js, &x) + if err != nil { + b.Fatalf("marshal json: %#v (%v)", js, err) + } + } +} diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/acme/autocert/cache.go lxd-2.8/dist/src/golang.org/x/crypto/acme/autocert/cache.go --- lxd-2.7/dist/src/golang.org/x/crypto/acme/autocert/cache.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/acme/autocert/cache.go 2017-01-25 03:19:48.000000000 +0000 @@ -27,7 +27,7 @@ Get(ctx context.Context, key string) ([]byte, error) // Put stores the data in the cache under the specified key. - // Inderlying implementations may use any data storage format, + // Underlying implementations may use any data storage format, // as long as the reverse operation, Get, results in the original data. Put(ctx context.Context, key string, data []byte) error diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2b_amd64.go lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2b_amd64.go --- lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2b_amd64.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2b_amd64.go 2017-01-25 03:19:48.000000000 +0000 @@ -6,12 +6,12 @@ package blake2b -var useAVX2 = false -var useAVX = false -var useSSE4 = supportSSE4() +func init() { + useSSE4 = supportsSSE4() +} //go:noescape -func supportSSE4() bool +func supportsSSE4() bool //go:noescape func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2b_amd64.s lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2b_amd64.s --- lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2b_amd64.s 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2b_amd64.s 2017-01-25 03:19:48.000000000 +0000 @@ -280,8 +280,8 @@ MOVQ BP, SP RET -// func supportSSE4() bool -TEXT ·supportSSE4(SB), 4, $0-1 +// func supportsSSE4() bool +TEXT ·supportsSSE4(SB), 4, $0-1 MOVL $1, AX CPUID SHRL $19, CX // Bit 19 indicates SSE4 support diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go --- lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go 2017-01-25 03:19:48.000000000 +0000 @@ -6,18 +6,20 @@ package blake2b -var useAVX2 = supportAVX2() -var useAVX = supportAVX() -var useSSE4 = supportSSE4() +func init() { + useAVX2 = supportsAVX2() + useAVX = supportsAVX() + useSSE4 = supportsSSE4() +} //go:noescape -func supportSSE4() bool +func supportsSSE4() bool //go:noescape -func supportAVX() bool +func supportsAVX() bool //go:noescape -func supportAVX2() bool +func supportsAVX2() bool //go:noescape func hashBlocksAVX2(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s --- lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s 2017-01-25 03:19:48.000000000 +0000 @@ -489,14 +489,14 @@ MOVQ BP, SP RET -// func supportAVX2() bool -TEXT ·supportAVX2(SB), 4, $0-1 +// func supportsAVX2() bool +TEXT ·supportsAVX2(SB), 4, $0-1 MOVQ runtime·support_avx2(SB), AX MOVB AX, ret+0(FP) RET -// func supportAVX() bool -TEXT ·supportAVX(SB), 4, $0-1 +// func supportsAVX() bool +TEXT ·supportsAVX(SB), 4, $0-1 MOVQ runtime·support_avx(SB), AX MOVB AX, ret+0(FP) RET diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2b.go lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2b.go --- lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2b.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2b.go 2017-01-25 03:19:48.000000000 +0000 @@ -23,6 +23,12 @@ Size256 = 32 ) +var ( + useAVX2 bool + useAVX bool + useSSE4 bool +) + var errKeySize = errors.New("blake2b: invalid key size") var iv = [8]uint64{ diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2b_ref.go lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2b_ref.go --- lxd-2.7/dist/src/golang.org/x/crypto/blake2b/blake2b_ref.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/blake2b/blake2b_ref.go 2017-01-25 03:19:48.000000000 +0000 @@ -6,9 +6,6 @@ package blake2b -var useAVX2 = false -var useSSE4 = false - func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { hashBlocksGeneric(h, c, flag, blocks) } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.s lxd-2.8/dist/src/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.s --- lxd-2.7/dist/src/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.s 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.s 2017-01-25 03:19:48.000000000 +0000 @@ -278,8 +278,15 @@ MOVQ ad+72(FP), adp // Check for AVX2 support - CMPB runtime·support_avx2(SB), $1 - JE chacha20Poly1305Open_AVX2 + CMPB runtime·support_avx2(SB), $0 + JE noavx2bmi2Open + + // Check BMI2 bit for MULXQ. + // runtime·cpuid_ebx7 is always available here + // because it passed avx2 check + TESTL $(1<<8), runtime·cpuid_ebx7(SB) + JNE chacha20Poly1305Open_AVX2 +noavx2bmi2Open: // Special optimization, for very short buffers CMPQ inl, $128 @@ -1485,8 +1492,15 @@ MOVQ ad+72(FP), adp // Check for AVX2 support - CMPB runtime·support_avx2(SB), $1 - JE chacha20Poly1305Seal_AVX2 + CMPB runtime·support_avx2(SB), $0 + JE noavx2bmi2Seal + + // Check BMI2 bit for MULXQ. + // runtime·cpuid_ebx7 is always available here + // because it passed avx2 check + TESTL $(1<<8), runtime·cpuid_ebx7(SB) + JNE chacha20Poly1305Seal_AVX2 +noavx2bmi2Seal: // Special optimization, for very short buffers CMPQ inl, $128 diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ocsp/ocsp.go lxd-2.8/dist/src/golang.org/x/crypto/ocsp/ocsp.go --- lxd-2.7/dist/src/golang.org/x/crypto/ocsp/ocsp.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ocsp/ocsp.go 2017-01-25 03:19:48.000000000 +0000 @@ -116,12 +116,11 @@ } type responseData struct { - Raw asn1.RawContent - Version int `asn1:"optional,default:0,explicit,tag:0"` - RawResponderName asn1.RawValue `asn1:"optional,explicit,tag:1"` - KeyHash []byte `asn1:"optional,explicit,tag:2"` - ProducedAt time.Time `asn1:"generalized"` - Responses []singleResponse + Raw asn1.RawContent + Version int `asn1:"optional,default:0,explicit,tag:0"` + RawResponderID asn1.RawValue + ProducedAt time.Time `asn1:"generalized"` + Responses []singleResponse } type singleResponse struct { @@ -363,6 +362,15 @@ // If zero, the default is crypto.SHA1. IssuerHash crypto.Hash + // RawResponderName optionally contains the DER-encoded subject of the + // responder certificate. Exactly one of RawResponderName and + // ResponderKeyHash is set. + RawResponderName []byte + // ResponderKeyHash optionally contains the SHA-1 hash of the + // responder's public key. Exactly one of RawResponderName and + // ResponderKeyHash is set. + ResponderKeyHash []byte + // Extensions contains raw X.509 extensions from the singleExtensions field // of the OCSP response. When parsing certificates, this can be used to // extract non-critical extensions that are not parsed by this package. When @@ -494,6 +502,25 @@ SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm), } + // Handle the ResponderID CHOICE tag. ResponderID can be flattened into + // TBSResponseData once https://go-review.googlesource.com/34503 has been + // released. + rawResponderID := basicResp.TBSResponseData.RawResponderID + switch rawResponderID.Tag { + case 1: // Name + var rdn pkix.RDNSequence + if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &rdn); err != nil || len(rest) != 0 { + return nil, ParseError("invalid responder name") + } + ret.RawResponderName = rawResponderID.Bytes + case 2: // KeyHash + if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &ret.ResponderKeyHash); err != nil || len(rest) != 0 { + return nil, ParseError("invalid responder key hash") + } + default: + return nil, ParseError("invalid responder id tag") + } + if len(basicResp.Certificates) > 0 { ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes) if err != nil { @@ -501,17 +528,17 @@ } if err := ret.CheckSignatureFrom(ret.Certificate); err != nil { - return nil, ParseError("bad OCSP signature") + return nil, ParseError("bad signature on embedded certificate: " + err.Error()) } if issuer != nil { if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil { - return nil, ParseError("bad signature on embedded certificate") + return nil, ParseError("bad OCSP signature: " + err.Error()) } } } else if issuer != nil { if err := ret.CheckSignatureFrom(issuer); err != nil { - return nil, ParseError("bad OCSP signature") + return nil, ParseError("bad OCSP signature: " + err.Error()) } } @@ -620,8 +647,8 @@ // CreateResponse returns a DER-encoded OCSP response with the specified contents. // The fields in the response are populated as follows: // -// The responder cert is used to populate the ResponderName field, and the certificate -// itself is provided alongside the OCSP response signature. +// The responder cert is used to populate the responder's name field, and the +// certificate itself is provided alongside the OCSP response signature. // // The issuer cert is used to puplate the IssuerNameHash and IssuerKeyHash fields. // @@ -649,7 +676,7 @@ } if !template.IssuerHash.Available() { - return nil, fmt.Errorf("issuer hash algorithm %v not linked into binarya", template.IssuerHash) + return nil, fmt.Errorf("issuer hash algorithm %v not linked into binary", template.IssuerHash) } h := template.IssuerHash.New() h.Write(publicKeyInfo.PublicKey.RightAlign()) @@ -686,17 +713,17 @@ } } - responderName := asn1.RawValue{ + rawResponderID := asn1.RawValue{ Class: 2, // context-specific - Tag: 1, // explicit tag + Tag: 1, // Name (explicit tag) IsCompound: true, Bytes: responderCert.RawSubject, } tbsResponseData := responseData{ - Version: 0, - RawResponderName: responderName, - ProducedAt: time.Now().Truncate(time.Minute).UTC(), - Responses: []singleResponse{innerResponse}, + Version: 0, + RawResponderID: rawResponderID, + ProducedAt: time.Now().Truncate(time.Minute).UTC(), + Responses: []singleResponse{innerResponse}, } tbsResponseDataDER, err := asn1.Marshal(tbsResponseData) diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ocsp/ocsp_test.go lxd-2.8/dist/src/golang.org/x/crypto/ocsp/ocsp_test.go --- lxd-2.7/dist/src/golang.org/x/crypto/ocsp/ocsp_test.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ocsp/ocsp_test.go 2017-01-25 03:19:48.000000000 +0000 @@ -24,7 +24,13 @@ responseBytes, _ := hex.DecodeString(ocspResponseHex) resp, err := ParseResponse(responseBytes, nil) if err != nil { - t.Error(err) + t.Fatal(err) + } + + responderCert, _ := hex.DecodeString(startComResponderCertHex) + responder, err := x509.ParseCertificate(responderCert) + if err != nil { + t.Fatal(err) } expected := Response{ @@ -33,6 +39,7 @@ RevocationReason: Unspecified, ThisUpdate: time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC), NextUpdate: time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC), + RawResponderName: responder.RawSubject, } if !reflect.DeepEqual(resp.ThisUpdate, expected.ThisUpdate) { @@ -54,6 +61,14 @@ if resp.RevocationReason != expected.RevocationReason { t.Errorf("resp.RevocationReason: got %d, want %d", resp.RevocationReason, expected.RevocationReason) } + + if !bytes.Equal(resp.RawResponderName, expected.RawResponderName) { + t.Errorf("resp.RawResponderName: got %x, want %x", resp.RawResponderName, expected.RawResponderName) + } + + if !bytes.Equal(resp.ResponderKeyHash, expected.ResponderKeyHash) { + t.Errorf("resp.ResponderKeyHash: got %x, want %x", resp.ResponderKeyHash, expected.ResponderKeyHash) + } } func TestOCSPDecodeWithoutCert(t *testing.T) { @@ -386,6 +401,41 @@ "a1d24ce16e41a9941568fec5b42771e118f16c106a54ccc339a4b02166445a167902e75e" + "6d8620b0825dcd18a069b90fd851d10fa8effd409deec02860d26d8d833f304b10669b42" +const startComResponderCertHex = "308204b23082039aa003020102020101300d06092a864886f70d010105050030818c310b" + + "300906035504061302494c31163014060355040a130d5374617274436f6d204c74642e31" + + "2b3029060355040b1322536563757265204469676974616c204365727469666963617465" + + "205369676e696e67313830360603550403132f5374617274436f6d20436c617373203120" + + "5072696d61727920496e7465726d65646961746520536572766572204341301e170d3037" + + "313032353030323330365a170d3132313032333030323330365a304c310b300906035504" + + "061302494c31163014060355040a130d5374617274436f6d204c74642e31253023060355" + + "0403131c5374617274436f6d20436c6173732031204f435350205369676e657230820122" + + "300d06092a864886f70d01010105000382010f003082010a0282010100b9561b4c453187" + + "17178084e96e178df2255e18ed8d8ecc7c2b7b51a6c1c2e6bf0aa3603066f132fe10ae97" + + "b50e99fa24b83fc53dd2777496387d14e1c3a9b6a4933e2ac12413d085570a95b8147414" + + "a0bc007c7bcf222446ef7f1a156d7ea1c577fc5f0facdfd42eb0f5974990cb2f5cefebce" + + "ef4d1bdc7ae5c1075c5a99a93171f2b0845b4ff0864e973fcfe32f9d7511ff87a3e94341" + + "0c90a4493a306b6944359340a9ca96f02b66ce67f028df2980a6aaee8d5d5d452b8b0eb9" + + "3f923cc1e23fcccbdbe7ffcb114d08fa7a6a3c404f825d1a0e715935cf623a8c7b596700" + + "14ed0622f6089a9447a7a19010f7fe58f84129a2765ea367824d1c3bb2fda30853020301" + + "0001a382015c30820158300c0603551d130101ff04023000300b0603551d0f0404030203" + + "a8301e0603551d250417301506082b0601050507030906092b0601050507300105301d06" + + "03551d0e0416041445e0a36695414c5dd449bc00e33cdcdbd2343e173081a80603551d23" + + "0481a030819d8014eb4234d098b0ab9ff41b6b08f7cc642eef0e2c45a18181a47f307d31" + + "0b300906035504061302494c31163014060355040a130d5374617274436f6d204c74642e" + + "312b3029060355040b1322536563757265204469676974616c2043657274696669636174" + + "65205369676e696e6731293027060355040313205374617274436f6d2043657274696669" + + "636174696f6e20417574686f7269747982010a30230603551d12041c301a861868747470" + + "3a2f2f7777772e737461727473736c2e636f6d2f302c06096086480186f842010d041f16" + + "1d5374617274436f6d205265766f636174696f6e20417574686f72697479300d06092a86" + + "4886f70d01010505000382010100182d22158f0fc0291324fa8574c49bb8ff2835085adc" + + "bf7b7fc4191c397ab6951328253fffe1e5ec2a7da0d50fca1a404e6968481366939e666c" + + "0a6209073eca57973e2fefa9ed1718e8176f1d85527ff522c08db702e3b2b180f1cbff05" + + "d98128252cf0f450f7dd2772f4188047f19dc85317366f94bc52d60f453a550af58e308a" + + "aab00ced33040b62bf37f5b1ab2a4f7f0f80f763bf4d707bc8841d7ad9385ee2a4244469" + + "260b6f2bf085977af9074796048ecc2f9d48a1d24ce16e41a9941568fec5b42771e118f1" + + "6c106a54ccc339a4b02166445a167902e75e6d8620b0825dcd18a069b90fd851d10fa8ef" + + "fd409deec02860d26d8d833f304b10669b42" + const startComHex = "308206343082041ca003020102020118300d06092a864886f70d0101050500307d310b30" + "0906035504061302494c31163014060355040a130d5374617274436f6d204c74642e312b" + "3029060355040b1322536563757265204469676974616c20436572746966696361746520" + diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/openpgp/keys.go lxd-2.8/dist/src/golang.org/x/crypto/openpgp/keys.go --- lxd-2.7/dist/src/golang.org/x/crypto/openpgp/keys.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/openpgp/keys.go 2017-01-25 03:19:48.000000000 +0000 @@ -307,8 +307,6 @@ return } } - - panic("unreachable") } // ReadEntity reads an entity (public key, identities, subkeys etc) from the diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/openpgp/keys_test.go lxd-2.8/dist/src/golang.org/x/crypto/openpgp/keys_test.go --- lxd-2.7/dist/src/golang.org/x/crypto/openpgp/keys_test.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/openpgp/keys_test.go 2017-01-25 03:19:48.000000000 +0000 @@ -300,7 +300,7 @@ for _, identity := range entity.Identities { if len(identity.SelfSignature.PreferredHash) != 0 { - t.Fatal("Expected preferred hash to be empty but got length %d", len(identity.SelfSignature.PreferredHash)) + t.Fatalf("Expected preferred hash to be empty but got length %d", len(identity.SelfSignature.PreferredHash)) } } } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/openpgp/packet/packet.go lxd-2.8/dist/src/golang.org/x/crypto/openpgp/packet/packet.go --- lxd-2.7/dist/src/golang.org/x/crypto/openpgp/packet/packet.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/openpgp/packet/packet.go 2017-01-25 03:19:48.000000000 +0000 @@ -273,8 +273,6 @@ return } } - - panic("unreachable") } // packetType represents the numeric ids of the different OpenPGP packet types. See diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/openpgp/packet/public_key.go lxd-2.8/dist/src/golang.org/x/crypto/openpgp/packet/public_key.go --- lxd-2.7/dist/src/golang.org/x/crypto/openpgp/packet/public_key.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/openpgp/packet/public_key.go 2017-01-25 03:19:48.000000000 +0000 @@ -540,7 +540,6 @@ default: return errors.SignatureError("Unsupported public key algorithm used in signature") } - panic("unreachable") } // VerifySignatureV3 returns nil iff sig is a valid signature, made by this @@ -585,7 +584,6 @@ default: panic("shouldn't happen") } - panic("unreachable") } // keySignatureHash returns a Hash of the message that needs to be signed for diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go lxd-2.8/dist/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go --- lxd-2.7/dist/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go 2017-01-25 03:19:48.000000000 +0000 @@ -216,7 +216,6 @@ // V3 public keys only support RSA. panic("shouldn't happen") } - panic("unreachable") } // VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/openpgp/write_test.go lxd-2.8/dist/src/golang.org/x/crypto/openpgp/write_test.go --- lxd-2.7/dist/src/golang.org/x/crypto/openpgp/write_test.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/openpgp/write_test.go 2017-01-25 03:19:48.000000000 +0000 @@ -80,7 +80,7 @@ t.Errorf("failed to find bit length: %s", err) } if int(bl) != defaultRSAKeyBits { - t.Errorf("BitLength %v, expected %v", defaultRSAKeyBits) + t.Errorf("BitLength %v, expected %v", int(bl), defaultRSAKeyBits) } // Check bit-length with a config. @@ -238,7 +238,7 @@ signKey, _ := kring[0].signingKey(testTime) expectedKeyId := signKey.PublicKey.KeyId if md.SignedByKeyId != expectedKeyId { - t.Errorf("#%d: message signed by wrong key id, got: %d, want: %d", i, *md.SignedBy, expectedKeyId) + t.Errorf("#%d: message signed by wrong key id, got: %v, want: %v", i, *md.SignedBy, expectedKeyId) } if md.SignedBy == nil { t.Errorf("#%d: failed to find the signing Entity", i) diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/otr/otr.go lxd-2.8/dist/src/golang.org/x/crypto/otr/otr.go --- lxd-2.7/dist/src/golang.org/x/crypto/otr/otr.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/otr/otr.go 2017-01-25 03:19:48.000000000 +0000 @@ -943,6 +943,7 @@ t.data, tlvData, ok3 = getNBytes(tlvData, int(t.length)) if !ok1 || !ok2 || !ok3 { err = errors.New("otr: corrupt tlv data") + return } tlvs = append(tlvs, t) } @@ -1313,6 +1314,12 @@ mpis[i] = new(big.Int).SetBytes(mpiBytes) } + for _, mpi := range mpis { + if mpi.Sign() <= 0 { + return false + } + } + priv.PrivateKey.P = mpis[0] priv.PrivateKey.Q = mpis[1] priv.PrivateKey.G = mpis[2] diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/poly1305/sum_amd64.s lxd-2.8/dist/src/golang.org/x/crypto/poly1305/sum_amd64.s --- lxd-2.7/dist/src/golang.org/x/crypto/poly1305/sum_amd64.s 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/poly1305/sum_amd64.s 2017-01-25 03:19:48.000000000 +0000 @@ -54,9 +54,9 @@ ADCQ t3, h1; \ ADCQ $0, h2 -DATA poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF -DATA poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC -GLOBL poly1305Mask<>(SB), RODATA, $16 +DATA ·poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF +DATA ·poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC +GLOBL ·poly1305Mask<>(SB), RODATA, $16 // func poly1305(out *[16]byte, m *byte, mlen uint64, key *[32]key) TEXT ·poly1305(SB), $0-32 @@ -67,8 +67,8 @@ MOVQ 0(AX), R11 MOVQ 8(AX), R12 - ANDQ poly1305Mask<>(SB), R11 // r0 - ANDQ poly1305Mask<>+8(SB), R12 // r1 + ANDQ ·poly1305Mask<>(SB), R11 // r0 + ANDQ ·poly1305Mask<>+8(SB), R12 // r1 XORQ R8, R8 // h0 XORQ R9, R9 // h1 XORQ R10, R10 // h2 diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/poly1305/sum_arm.s lxd-2.8/dist/src/golang.org/x/crypto/poly1305/sum_arm.s --- lxd-2.7/dist/src/golang.org/x/crypto/poly1305/sum_arm.s 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/poly1305/sum_arm.s 2017-01-25 03:19:48.000000000 +0000 @@ -9,12 +9,12 @@ // This code was translated into a form compatible with 5a from the public // domain source by Andrew Moon: github.com/floodyberry/poly1305-opt/blob/master/app/extensions/poly1305. -DATA poly1305_init_constants_armv6<>+0x00(SB)/4, $0x3ffffff -DATA poly1305_init_constants_armv6<>+0x04(SB)/4, $0x3ffff03 -DATA poly1305_init_constants_armv6<>+0x08(SB)/4, $0x3ffc0ff -DATA poly1305_init_constants_armv6<>+0x0c(SB)/4, $0x3f03fff -DATA poly1305_init_constants_armv6<>+0x10(SB)/4, $0x00fffff -GLOBL poly1305_init_constants_armv6<>(SB), 8, $20 +DATA ·poly1305_init_constants_armv6<>+0x00(SB)/4, $0x3ffffff +DATA ·poly1305_init_constants_armv6<>+0x04(SB)/4, $0x3ffff03 +DATA ·poly1305_init_constants_armv6<>+0x08(SB)/4, $0x3ffc0ff +DATA ·poly1305_init_constants_armv6<>+0x0c(SB)/4, $0x3f03fff +DATA ·poly1305_init_constants_armv6<>+0x10(SB)/4, $0x00fffff +GLOBL ·poly1305_init_constants_armv6<>(SB), 8, $20 // Warning: the linker may use R11 to synthesize certain instructions. Please // take care and verify that no synthetic instructions use it. @@ -27,7 +27,7 @@ ADD $4, R13, R8 MOVM.IB [R4-R7], (R8) MOVM.IA.W (R1), [R2-R5] - MOVW $poly1305_init_constants_armv6<>(SB), R7 + MOVW $·poly1305_init_constants_armv6<>(SB), R7 MOVW R2, R8 MOVW R2>>26, R9 MOVW R3>>20, g diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/channel.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/channel.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/channel.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/channel.go 2017-01-25 03:19:48.000000000 +0000 @@ -461,8 +461,8 @@ pending: newBuffer(), extPending: newBuffer(), direction: direction, - incomingRequests: make(chan *Request, 16), - msg: make(chan interface{}, 16), + incomingRequests: make(chan *Request, chanSize), + msg: make(chan interface{}, chanSize), chanType: chanType, extraData: extraData, mux: m, diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/client_auth.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/client_auth.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/client_auth.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/client_auth.go 2017-01-25 03:19:48.000000000 +0000 @@ -30,8 +30,10 @@ // then any untried methods suggested by the server. tried := make(map[string]bool) var lastMethods []string + + sessionID := c.transport.getSessionID() for auth := AuthMethod(new(noneAuth)); auth != nil; { - ok, methods, err := auth.auth(c.transport.getSessionID(), config.User, c.transport, config.Rand) + ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand) if err != nil { return err } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/client_auth_test.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/client_auth_test.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/client_auth_test.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/client_auth_test.go 2017-01-25 03:19:48.000000000 +0000 @@ -77,7 +77,6 @@ return nil, errors.New("keyboard-interactive failed") }, AuthLogCallback: func(conn ConnMetadata, method string, err error) { - t.Logf("user %q, method %q: %v", conn.User(), method, err) }, } serverConfig.AddHostKey(testSigners["rsa"]) @@ -278,18 +277,18 @@ } clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner)) - t.Log("should succeed") + // should succeed if err := tryAuth(t, clientConfig); err != nil { t.Errorf("cert login failed: %v", err) } - t.Log("corrupted signature") + // corrupted signature cert.Signature.Blob[0]++ if err := tryAuth(t, clientConfig); err == nil { t.Errorf("cert login passed with corrupted sig") } - t.Log("revoked") + // revoked cert.Serial = 666 cert.SignCert(rand.Reader, testSigners["ecdsa"]) if err := tryAuth(t, clientConfig); err == nil { @@ -297,13 +296,13 @@ } cert.Serial = 1 - t.Log("sign with wrong key") + // sign with wrong key cert.SignCert(rand.Reader, testSigners["dsa"]) if err := tryAuth(t, clientConfig); err == nil { t.Errorf("cert login passed with non-authoritative key") } - t.Log("host cert") + // host cert cert.CertType = HostCert cert.SignCert(rand.Reader, testSigners["ecdsa"]) if err := tryAuth(t, clientConfig); err == nil { @@ -311,14 +310,14 @@ } cert.CertType = UserCert - t.Log("principal specified") + // principal specified cert.ValidPrincipals = []string{"user"} cert.SignCert(rand.Reader, testSigners["ecdsa"]) if err := tryAuth(t, clientConfig); err != nil { t.Errorf("cert login failed: %v", err) } - t.Log("wrong principal specified") + // wrong principal specified cert.ValidPrincipals = []string{"fred"} cert.SignCert(rand.Reader, testSigners["ecdsa"]) if err := tryAuth(t, clientConfig); err == nil { @@ -326,21 +325,21 @@ } cert.ValidPrincipals = nil - t.Log("added critical option") + // added critical option cert.CriticalOptions = map[string]string{"root-access": "yes"} cert.SignCert(rand.Reader, testSigners["ecdsa"]) if err := tryAuth(t, clientConfig); err == nil { t.Errorf("cert login passed with unrecognized critical option") } - t.Log("allowed source address") + // allowed source address cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24"} cert.SignCert(rand.Reader, testSigners["ecdsa"]) if err := tryAuth(t, clientConfig); err != nil { t.Errorf("cert login with source-address failed: %v", err) } - t.Log("disallowed source address") + // disallowed source address cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42"} cert.SignCert(rand.Reader, testSigners["ecdsa"]) if err := tryAuth(t, clientConfig); err == nil { diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/client.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/client.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/client.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/client.go 2017-01-25 03:19:48.000000000 +0000 @@ -40,7 +40,7 @@ return nil } - ch = make(chan NewChannel, 16) + ch = make(chan NewChannel, chanSize) c.channelHandlers[channelType] = ch return ch } @@ -97,13 +97,11 @@ c.transport = newClientTransport( newTransport(c.sshConn.conn, config.Rand, true /* is client */), c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr()) - if err := c.transport.requestInitialKeyChange(); err != nil { + if err := c.transport.waitSession(); err != nil { return err } - // We just did the key change, so the session ID is established. c.sessionID = c.transport.getSessionID() - return c.clientAuthenticate(config) } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/handshake.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/handshake.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/handshake.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/handshake.go 2017-01-25 03:19:48.000000000 +0000 @@ -19,6 +19,11 @@ // messages are wrong when using ECDH. const debugHandshake = false +// chanSize sets the amount of buffering SSH connections. This is +// primarily for testing: setting chanSize=0 uncovers deadlocks more +// quickly. +const chanSize = 16 + // keyingTransport is a packet based transport that supports key // changes. It need not be thread-safe. It should pass through // msgNewKeys in both directions. @@ -53,6 +58,20 @@ incoming chan []byte readError error + mu sync.Mutex + writeError error + sentInitPacket []byte + sentInitMsg *kexInitMsg + pendingPackets [][]byte // Used when a key exchange is in progress. + + // If the read loop wants to schedule a kex, it pings this + // channel, and the write loop will send out a kex message. + requestKex chan struct{} + + // If the other side requests or confirms a kex, its kexInit + // packet is sent here for the write loop to find it. + startKex chan *pendingKex + // data for host key checking hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error dialAddress string @@ -60,27 +79,28 @@ readSinceKex uint64 - // Protects the writing side of the connection - mu sync.Mutex - cond *sync.Cond - sentInitPacket []byte - sentInitMsg *kexInitMsg writtenSinceKex uint64 - writeError error // The session ID or nil if first kex did not complete yet. sessionID []byte } +type pendingKex struct { + otherInit []byte + done chan error +} + func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport { t := &handshakeTransport{ conn: conn, serverVersion: serverVersion, clientVersion: clientVersion, - incoming: make(chan []byte, 16), - config: config, + incoming: make(chan []byte, chanSize), + requestKex: make(chan struct{}, 1), + startKex: make(chan *pendingKex, 1), + + config: config, } - t.cond = sync.NewCond(&t.mu) return t } @@ -95,6 +115,7 @@ t.hostKeyAlgorithms = supportedHostKeyAlgos } go t.readLoop() + go t.kexLoop() return t } @@ -102,6 +123,7 @@ t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) t.hostKeys = config.hostKeys go t.readLoop() + go t.kexLoop() return t } @@ -109,6 +131,20 @@ return t.sessionID } +// waitSession waits for the session to be established. This should be +// the first thing to call after instantiating handshakeTransport. +func (t *handshakeTransport) waitSession() error { + p, err := t.readPacket() + if err != nil { + return err + } + if p[0] != msgNewKeys { + return fmt.Errorf("ssh: first packet should be msgNewKeys") + } + + return nil +} + func (t *handshakeTransport) id() string { if len(t.hostKeys) > 0 { return "server" @@ -116,6 +152,19 @@ return "client" } +func (t *handshakeTransport) printPacket(p []byte, write bool) { + action := "got" + if write { + action = "sent" + } + if p[0] == msgChannelData || p[0] == msgChannelExtendedData { + log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p)) + } else { + msg, err := decode(p) + log.Printf("%s %s %T %v (%v)", t.id(), action, msg, msg, err) + } +} + func (t *handshakeTransport) readPacket() ([]byte, error) { p, ok := <-t.incoming if !ok { @@ -125,8 +174,16 @@ } func (t *handshakeTransport) readLoop() { + // We always start with the mandatory key exchange. We use + // the channel for simplicity, and this works if we can rely + // on the SSH package itself not doing anything else before + // waitSession has completed. + t.requestKeyExchange() + + first := true for { - p, err := t.readOnePacket() + p, err := t.readOnePacket(first) + first = false if err != nil { t.readError = err close(t.incoming) @@ -138,20 +195,123 @@ t.incoming <- p } - // If we can't read, declare the writing part dead too. + // Stop writers too. + t.recordWriteError(t.readError) + + // Unblock the writer should it wait for this. + close(t.startKex) + + // Don't close t.requestKex; it's also written to from writePacket. +} + +func (t *handshakeTransport) pushPacket(p []byte) error { + if debugHandshake { + t.printPacket(p, true) + } + return t.conn.writePacket(p) +} + +func (t *handshakeTransport) getWriteError() error { t.mu.Lock() defer t.mu.Unlock() - if t.writeError == nil { - t.writeError = t.readError + return t.writeError +} + +func (t *handshakeTransport) recordWriteError(err error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.writeError == nil && err != nil { + t.writeError = err } - t.cond.Broadcast() } -func (t *handshakeTransport) readOnePacket() ([]byte, error) { - if t.readSinceKex > t.config.RekeyThreshold { - if err := t.requestKeyChange(); err != nil { - return nil, err +func (t *handshakeTransport) requestKeyExchange() { + select { + case t.requestKex <- struct{}{}: + default: + // something already requested a kex, so do nothing. + } + +} + +func (t *handshakeTransport) kexLoop() { +write: + for t.getWriteError() == nil { + var request *pendingKex + var sent bool + + for request == nil || !sent { + var ok bool + select { + case request, ok = <-t.startKex: + if !ok { + break write + } + case <-t.requestKex: + } + + if !sent { + if err := t.sendKexInit(); err != nil { + t.recordWriteError(err) + break + } + sent = true + } } + + if err := t.getWriteError(); err != nil { + if request != nil { + request.done <- err + } + break + } + + // We're not servicing t.requestKex, but that is OK: + // we never block on sending to t.requestKex. + + // We're not servicing t.startKex, but the remote end + // has just sent us a kexInitMsg, so it can't send + // another key change request. + + err := t.enterKeyExchange(request.otherInit) + + t.mu.Lock() + t.writeError = err + t.sentInitPacket = nil + t.sentInitMsg = nil + t.writtenSinceKex = 0 + request.done <- t.writeError + + // kex finished. Push packets that we received while + // the kex was in progress. Don't look at t.startKex + // and don't increment writtenSinceKex: if we trigger + // another kex while we are still busy with the last + // one, things will become very confusing. + for _, p := range t.pendingPackets { + t.writeError = t.pushPacket(p) + if t.writeError != nil { + break + } + } + t.pendingPackets = t.pendingPackets[0:] + t.mu.Unlock() + } + + // drain startKex channel. We don't service t.requestKex + // because nobody does blocking sends there. + go func() { + for init := range t.startKex { + init.done <- t.writeError + } + }() + + // Unblock reader. + t.conn.Close() +} + +func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) { + if t.readSinceKex > t.config.RekeyThreshold { + t.requestKeyExchange() } p, err := t.conn.readPacket() @@ -161,39 +321,30 @@ t.readSinceKex += uint64(len(p)) if debugHandshake { - if p[0] == msgChannelData || p[0] == msgChannelExtendedData { - log.Printf("%s got data (packet %d bytes)", t.id(), len(p)) - } else { - msg, err := decode(p) - log.Printf("%s got %T %v (%v)", t.id(), msg, msg, err) - } + t.printPacket(p, false) } + + if first && p[0] != msgKexInit { + return nil, fmt.Errorf("ssh: first packet should be msgKexInit") + } + if p[0] != msgKexInit { return p, nil } - t.mu.Lock() - firstKex := t.sessionID == nil - err = t.enterKeyExchangeLocked(p) - if err != nil { - // drop connection - t.conn.Close() - t.writeError = err + kex := pendingKex{ + done: make(chan error, 1), + otherInit: p, } + t.startKex <- &kex + err = <-kex.done if debugHandshake { log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err) } - // Unblock writers. - t.sentInitMsg = nil - t.sentInitPacket = nil - t.cond.Broadcast() - t.writtenSinceKex = 0 - t.mu.Unlock() - if err != nil { return nil, err } @@ -213,61 +364,16 @@ return successPacket, nil } -// keyChangeCategory describes whether a key exchange is the first on a -// connection, or a subsequent one. -type keyChangeCategory bool - -const ( - firstKeyExchange keyChangeCategory = true - subsequentKeyExchange keyChangeCategory = false -) - -// sendKexInit sends a key change message, and returns the message -// that was sent. After initiating the key change, all writes will be -// blocked until the change is done, and a failed key change will -// close the underlying transport. This function is safe for -// concurrent use by multiple goroutines. -func (t *handshakeTransport) sendKexInit(isFirst keyChangeCategory) error { - var err error - +// sendKexInit sends a key change message. +func (t *handshakeTransport) sendKexInit() error { t.mu.Lock() - // If this is the initial key change, but we already have a sessionID, - // then do nothing because the key exchange has already completed - // asynchronously. - if !isFirst || t.sessionID == nil { - _, _, err = t.sendKexInitLocked(isFirst) - } - t.mu.Unlock() - if err != nil { - return err - } - if isFirst { - if packet, err := t.readPacket(); err != nil { - return err - } else if packet[0] != msgNewKeys { - return unexpectedMessageError(msgNewKeys, packet[0]) - } - } - return nil -} - -func (t *handshakeTransport) requestInitialKeyChange() error { - return t.sendKexInit(firstKeyExchange) -} - -func (t *handshakeTransport) requestKeyChange() error { - return t.sendKexInit(subsequentKeyExchange) -} - -// sendKexInitLocked sends a key change message. t.mu must be locked -// while this happens. -func (t *handshakeTransport) sendKexInitLocked(isFirst keyChangeCategory) (*kexInitMsg, []byte, error) { - // kexInits may be sent either in response to the other side, - // or because our side wants to initiate a key change, so we - // may have already sent a kexInit. In that case, don't send a - // second kexInit. + defer t.mu.Unlock() if t.sentInitMsg != nil { - return t.sentInitMsg, t.sentInitPacket, nil + // kexInits may be sent either in response to the other side, + // or because our side wants to initiate a key change, so we + // may have already sent a kexInit. In that case, don't send a + // second kexInit. + return nil } msg := &kexInitMsg{ @@ -295,53 +401,57 @@ packetCopy := make([]byte, len(packet)) copy(packetCopy, packet) - if err := t.conn.writePacket(packetCopy); err != nil { - return nil, nil, err + if err := t.pushPacket(packetCopy); err != nil { + return err } t.sentInitMsg = msg t.sentInitPacket = packet - return msg, packet, nil + + return nil } func (t *handshakeTransport) writePacket(p []byte) error { + switch p[0] { + case msgKexInit: + return errors.New("ssh: only handshakeTransport can send kexInit") + case msgNewKeys: + return errors.New("ssh: only handshakeTransport can send newKeys") + } + t.mu.Lock() defer t.mu.Unlock() - - if t.writtenSinceKex > t.config.RekeyThreshold { - t.sendKexInitLocked(subsequentKeyExchange) - } - for t.sentInitMsg != nil && t.writeError == nil { - t.cond.Wait() - } if t.writeError != nil { return t.writeError } + + if t.sentInitMsg != nil { + // Copy the packet so the writer can reuse the buffer. + cp := make([]byte, len(p)) + copy(cp, p) + t.pendingPackets = append(t.pendingPackets, cp) + return nil + } t.writtenSinceKex += uint64(len(p)) + if t.writtenSinceKex > t.config.RekeyThreshold { + t.requestKeyExchange() + } - switch p[0] { - case msgKexInit: - return errors.New("ssh: only handshakeTransport can send kexInit") - case msgNewKeys: - return errors.New("ssh: only handshakeTransport can send newKeys") - default: - return t.conn.writePacket(p) + if err := t.pushPacket(p); err != nil { + t.writeError = err } + + return nil } func (t *handshakeTransport) Close() error { return t.conn.Close() } -// enterKeyExchange runs the key exchange. t.mu must be held while running this. -func (t *handshakeTransport) enterKeyExchangeLocked(otherInitPacket []byte) error { +func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error { if debugHandshake { log.Printf("%s entered key exchange", t.id()) } - myInit, myInitPacket, err := t.sendKexInitLocked(subsequentKeyExchange) - if err != nil { - return err - } otherInit := &kexInitMsg{} if err := Unmarshal(otherInitPacket, otherInit); err != nil { @@ -352,16 +462,15 @@ clientVersion: t.clientVersion, serverVersion: t.serverVersion, clientKexInit: otherInitPacket, - serverKexInit: myInitPacket, + serverKexInit: t.sentInitPacket, } clientInit := otherInit - serverInit := myInit + serverInit := t.sentInitMsg if len(t.hostKeys) == 0 { - clientInit = myInit - serverInit = otherInit + clientInit, serverInit = serverInit, clientInit - magics.clientKexInit = myInitPacket + magics.clientKexInit = t.sentInitPacket magics.serverKexInit = otherInitPacket } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/handshake_test.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/handshake_test.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/handshake_test.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/handshake_test.go 2017-01-25 03:19:48.000000000 +0000 @@ -9,6 +9,7 @@ "crypto/rand" "errors" "fmt" + "io" "net" "reflect" "runtime" @@ -58,14 +59,46 @@ return c1, c2, nil } -func handshakePair(clientConf *ClientConfig, addr string) (client *handshakeTransport, server *handshakeTransport, err error) { +// noiseTransport inserts ignore messages to check that the read loop +// and the key exchange filters out these messages. +type noiseTransport struct { + keyingTransport +} + +func (t *noiseTransport) writePacket(p []byte) error { + ignore := []byte{msgIgnore} + if err := t.keyingTransport.writePacket(ignore); err != nil { + return err + } + debug := []byte{msgDebug, 1, 2, 3} + if err := t.keyingTransport.writePacket(debug); err != nil { + return err + } + + return t.keyingTransport.writePacket(p) +} + +func addNoiseTransport(t keyingTransport) keyingTransport { + return &noiseTransport{t} +} + +// handshakePair creates two handshakeTransports connected with each +// other. If the noise argument is true, both transports will try to +// confuse the other side by sending ignore and debug messages. +func handshakePair(clientConf *ClientConfig, addr string, noise bool) (client *handshakeTransport, server *handshakeTransport, err error) { a, b, err := netPipe() if err != nil { return nil, nil, err } - trC := newTransport(a, rand.Reader, true) - trS := newTransport(b, rand.Reader, false) + var trC, trS keyingTransport + + trC = newTransport(a, rand.Reader, true) + trS = newTransport(b, rand.Reader, false) + if noise { + trC = addNoiseTransport(trC) + trS = addNoiseTransport(trS) + } clientConf.SetDefaults() v := []byte("version") @@ -77,6 +110,13 @@ serverConf.SetDefaults() server = newServerTransport(trS, v, v, serverConf) + if err := server.waitSession(); err != nil { + return nil, nil, fmt.Errorf("server.waitSession: %v", err) + } + if err := client.waitSession(); err != nil { + return nil, nil, fmt.Errorf("client.waitSession: %v", err) + } + return client, server, nil } @@ -84,8 +124,9 @@ if runtime.GOOS == "plan9" { t.Skip("see golang.org/issue/7237") } - checker := &testChecker{} - trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr") + + checker := &syncChecker{make(chan int, 10)} + trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false) if err != nil { t.Fatalf("handshakePair: %v", err) } @@ -93,7 +134,13 @@ defer trC.Close() defer trS.Close() + <-checker.called + + clientDone := make(chan int, 0) + gotHalf := make(chan int, 0) + go func() { + defer close(clientDone) // Client writes a bunch of stuff, and does a key // change in the middle. This should not confuse the // handshake in progress @@ -103,219 +150,144 @@ t.Fatalf("sendPacket: %v", err) } if i == 5 { + <-gotHalf // halfway through, we request a key change. - err := trC.sendKexInit(subsequentKeyExchange) - if err != nil { - t.Fatalf("sendKexInit: %v", err) - } + trC.requestKeyExchange() + + // Wait until we can be sure the key + // change has really started before we + // write more. + <-checker.called } } - trC.Close() }() // Server checks that client messages come in cleanly i := 0 - for { - p, err := trS.readPacket() + err = nil + for ; i < 10; i++ { + var p []byte + p, err = trS.readPacket() if err != nil { break } - if p[0] == msgNewKeys { - continue + if i == 5 { + gotHalf <- 1 } + want := []byte{msgRequestSuccess, byte(i)} if bytes.Compare(p, want) != 0 { t.Errorf("message %d: got %q, want %q", i, p, want) } - i++ + } + <-clientDone + if err != nil && err != io.EOF { + t.Fatalf("server error: %v", err) } if i != 10 { t.Errorf("received %d messages, want 10.", i) } - // If all went well, we registered exactly 1 key change. - if len(checker.calls) != 1 { - t.Fatalf("got %d host key checks, want 1", len(checker.calls)) - } - - pub := testSigners["ecdsa"].PublicKey() - want := fmt.Sprintf("%s %v %s %x", "addr", trC.remoteAddr, pub.Type(), pub.Marshal()) - if want != checker.calls[0] { - t.Errorf("got %q want %q for host key check", checker.calls[0], want) + close(checker.called) + if _, ok := <-checker.called; ok { + // If all went well, we registered exactly 2 key changes: one + // that establishes the session, and one that we requested + // additionally. + t.Fatalf("got another host key checks after 2 handshakes") } } -func TestHandshakeError(t *testing.T) { +func TestForceFirstKex(t *testing.T) { + // like handshakePair, but must access the keyingTransport. checker := &testChecker{} - trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "bad") + clientConf := &ClientConfig{HostKeyCallback: checker.Check} + a, b, err := netPipe() if err != nil { - t.Fatalf("handshakePair: %v", err) + t.Fatalf("netPipe: %v", err) } - defer trC.Close() - defer trS.Close() - // send a packet - packet := []byte{msgRequestSuccess, 42} - if err := trC.writePacket(packet); err != nil { - t.Errorf("writePacket: %v", err) - } + var trC, trS keyingTransport - // Now request a key change. - err = trC.sendKexInit(subsequentKeyExchange) - if err != nil { - t.Errorf("sendKexInit: %v", err) - } + trC = newTransport(a, rand.Reader, true) - // the key change will fail, and afterwards we can't write. - if err := trC.writePacket([]byte{msgRequestSuccess, 43}); err == nil { - t.Errorf("writePacket after botched rekey succeeded.") - } + // This is the disallowed packet: + trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})) - readback, err := trS.readPacket() - if err != nil { - t.Fatalf("server closed too soon: %v", err) - } - if bytes.Compare(readback, packet) != 0 { - t.Errorf("got %q want %q", readback, packet) - } - readback, err = trS.readPacket() - if err == nil { - t.Errorf("got a message %q after failed key change", readback) - } -} + // Rest of the setup. + trS = newTransport(b, rand.Reader, false) + clientConf.SetDefaults() -func TestForceFirstKex(t *testing.T) { - checker := &testChecker{} - trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr") - if err != nil { - t.Fatalf("handshakePair: %v", err) - } + v := []byte("version") + client := newClientTransport(trC, v, v, clientConf, "addr", a.RemoteAddr()) - defer trC.Close() - defer trS.Close() + serverConf := &ServerConfig{} + serverConf.AddHostKey(testSigners["ecdsa"]) + serverConf.AddHostKey(testSigners["rsa"]) + serverConf.SetDefaults() + server := newServerTransport(trS, v, v, serverConf) - trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})) + defer client.Close() + defer server.Close() // We setup the initial key exchange, but the remote side // tries to send serviceRequestMsg in cleartext, which is // disallowed. - err = trS.sendKexInit(firstKeyExchange) - if err == nil { + if err := server.waitSession(); err == nil { t.Errorf("server first kex init should reject unexpected packet") } } -func TestHandshakeTwice(t *testing.T) { - checker := &testChecker{} - trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr") +func TestHandshakeAutoRekeyWrite(t *testing.T) { + checker := &syncChecker{make(chan int, 10)} + clientConf := &ClientConfig{HostKeyCallback: checker.Check} + clientConf.RekeyThreshold = 500 + trC, trS, err := handshakePair(clientConf, "addr", false) if err != nil { t.Fatalf("handshakePair: %v", err) } - defer trC.Close() defer trS.Close() - // Both sides should ask for the first key exchange first. - err = trS.sendKexInit(firstKeyExchange) - if err != nil { - t.Errorf("server sendKexInit: %v", err) - } - - err = trC.sendKexInit(firstKeyExchange) - if err != nil { - t.Errorf("client sendKexInit: %v", err) - } - - sent := 0 - // send a packet - packet := make([]byte, 5) - packet[0] = msgRequestSuccess - if err := trC.writePacket(packet); err != nil { - t.Errorf("writePacket: %v", err) - } - sent++ - - // Send another packet. Use a fresh one, since writePacket destroys. - packet = make([]byte, 5) - packet[0] = msgRequestSuccess - if err := trC.writePacket(packet); err != nil { - t.Errorf("writePacket: %v", err) - } - sent++ - - // 2nd key change. - err = trC.sendKexInit(subsequentKeyExchange) - if err != nil { - t.Errorf("sendKexInit: %v", err) - } - - packet = make([]byte, 5) - packet[0] = msgRequestSuccess - if err := trC.writePacket(packet); err != nil { - t.Errorf("writePacket: %v", err) - } - sent++ - - packet = make([]byte, 5) - packet[0] = msgRequestSuccess - for i := 0; i < sent; i++ { - msg, err := trS.readPacket() - if err != nil { - t.Fatalf("server closed too soon: %v", err) + done := make(chan int, 1) + const numPacket = 5 + go func() { + defer close(done) + j := 0 + for ; j < numPacket; j++ { + if _, err := trS.readPacket(); err != nil { + break + } } - if bytes.Compare(msg, packet) != 0 { - t.Errorf("packet %d: got %q want %q", i, msg, packet) + if j != numPacket { + t.Errorf("got %d, want 5 messages", j) } - } - if len(checker.calls) != 2 { - t.Errorf("got %d key changes, want 2", len(checker.calls)) - } -} + }() -func TestHandshakeAutoRekeyWrite(t *testing.T) { - checker := &testChecker{} - clientConf := &ClientConfig{HostKeyCallback: checker.Check} - clientConf.RekeyThreshold = 500 - trC, trS, err := handshakePair(clientConf, "addr") - if err != nil { - t.Fatalf("handshakePair: %v", err) - } - defer trC.Close() - defer trS.Close() + <-checker.called - for i := 0; i < 5; i++ { + for i := 0; i < numPacket; i++ { packet := make([]byte, 251) packet[0] = msgRequestSuccess if err := trC.writePacket(packet); err != nil { t.Errorf("writePacket: %v", err) } - } - - j := 0 - for ; j < 5; j++ { - _, err := trS.readPacket() - if err != nil { - break + if i == 2 { + // Make sure the kex is in progress. + <-checker.called } - } - if j != 5 { - t.Errorf("got %d, want 5 messages", j) - } - - if len(checker.calls) != 2 { - t.Errorf("got %d key changes, wanted 2", len(checker.calls)) } + <-done } type syncChecker struct { called chan int } -func (t *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error { - t.called <- 1 +func (c *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error { + c.called <- 1 return nil } @@ -326,7 +298,7 @@ } clientConf.RekeyThreshold = 500 - trC, trS, err := handshakePair(clientConf, "addr") + trC, trS, err := handshakePair(clientConf, "addr", false) if err != nil { t.Fatalf("handshakePair: %v", err) } @@ -338,12 +310,19 @@ if err := trS.writePacket(packet); err != nil { t.Fatalf("writePacket: %v", err) } + // While we read out the packet, a key change will be // initiated. - if _, err := trC.readPacket(); err != nil { - t.Fatalf("readPacket(client): %v", err) - } + done := make(chan int, 1) + go func() { + defer close(done) + if _, err := trC.readPacket(); err != nil { + t.Fatalf("readPacket(client): %v", err) + } + + }() + <-done <-sync.called } @@ -357,6 +336,7 @@ func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error { return nil } + func (n *errorKeyingTransport) getSessionID() []byte { return nil } @@ -383,20 +363,32 @@ func TestHandshakeErrorHandlingRead(t *testing.T) { for i := 0; i < 20; i++ { - testHandshakeErrorHandlingN(t, i, -1) + testHandshakeErrorHandlingN(t, i, -1, false) } } func TestHandshakeErrorHandlingWrite(t *testing.T) { for i := 0; i < 20; i++ { - testHandshakeErrorHandlingN(t, -1, i) + testHandshakeErrorHandlingN(t, -1, i, false) + } +} + +func TestHandshakeErrorHandlingReadCoupled(t *testing.T) { + for i := 0; i < 20; i++ { + testHandshakeErrorHandlingN(t, i, -1, true) + } +} + +func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) { + for i := 0; i < 20; i++ { + testHandshakeErrorHandlingN(t, -1, i, true) } } // testHandshakeErrorHandlingN runs handshakes, injecting errors. If // handshakeTransport deadlocks, the go runtime will detect it and // panic. -func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int) { +func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) { msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)}) a, b := memPipe() @@ -409,37 +401,57 @@ serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'}) serverConn.hostKeys = []Signer{key} go serverConn.readLoop() + go serverConn.kexLoop() clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold} clientConf.SetDefaults() clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'}) clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()} go clientConn.readLoop() + go clientConn.kexLoop() var wg sync.WaitGroup - wg.Add(4) for _, hs := range []packetConn{serverConn, clientConn} { - go func(c packetConn) { - for { - err := c.writePacket(msg) - if err != nil { - break + if !coupled { + wg.Add(2) + go func(c packetConn) { + for i := 0; ; i++ { + str := fmt.Sprintf("%08x", i) + strings.Repeat("x", int(minRekeyThreshold)/4-8) + err := c.writePacket(Marshal(&serviceRequestMsg{str})) + if err != nil { + break + } } - } - wg.Done() - }(hs) - go func(c packetConn) { - for { - _, err := c.readPacket() - if err != nil { - break + wg.Done() + c.Close() + }(hs) + go func(c packetConn) { + for { + _, err := c.readPacket() + if err != nil { + break + } } - } - wg.Done() - }(hs) - } + wg.Done() + }(hs) + } else { + wg.Add(1) + go func(c packetConn) { + for { + _, err := c.readPacket() + if err != nil { + break + } + if err := c.writePacket(msg); err != nil { + break + } + } + wg.Done() + }(hs) + } + } wg.Wait() } @@ -448,7 +460,7 @@ t.Skip("see golang.org/issue/7237") } checker := &testChecker{} - trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr") + trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false) if err != nil { t.Fatalf("handshakePair: %v", err) } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/keys.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/keys.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/keys.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/keys.go 2017-01-25 03:19:48.000000000 +0000 @@ -798,8 +798,8 @@ P *big.Int Q *big.Int G *big.Int - Priv *big.Int Pub *big.Int + Priv *big.Int } rest, err := asn1.Unmarshal(der, &k) if err != nil { @@ -816,9 +816,9 @@ Q: k.Q, G: k.G, }, - Y: k.Priv, + Y: k.Pub, }, - X: k.Pub, + X: k.Priv, }, nil } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/mux.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/mux.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/mux.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/mux.go 2017-01-25 03:19:48.000000000 +0000 @@ -116,9 +116,9 @@ func newMux(p packetConn) *mux { m := &mux{ conn: p, - incomingChannels: make(chan NewChannel, 16), + incomingChannels: make(chan NewChannel, chanSize), globalResponses: make(chan interface{}, 1), - incomingRequests: make(chan *Request, 16), + incomingRequests: make(chan *Request, chanSize), errCond: newCond(), } if debugMux { diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/server.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/server.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/server.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/server.go 2017-01-25 03:19:48.000000000 +0000 @@ -188,7 +188,7 @@ tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */) s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config) - if err := s.transport.requestInitialKeyChange(); err != nil { + if err := s.transport.waitSession(); err != nil { return nil, err } @@ -260,7 +260,7 @@ } func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { - var err error + sessionID := s.transport.getSessionID() var cache pubKeyCache var perms *Permissions @@ -385,7 +385,7 @@ if !isAcceptableAlgo(sig.Format) { break } - signedData := buildDataSignedForAuth(s.transport.getSessionID(), userAuthReq, algoBytes, pubKeyData) + signedData := buildDataSignedForAuth(sessionID, userAuthReq, algoBytes, pubKeyData) if err := pubKey.Verify(signedData, sig); err != nil { return nil, err @@ -421,12 +421,12 @@ return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") } - if err = s.transport.writePacket(Marshal(&failureMsg)); err != nil { + if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil { return nil, err } } - if err = s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { + if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { return nil, err } return perms, nil diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/terminal/terminal.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/terminal/terminal.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/terminal/terminal.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/terminal/terminal.go 2017-01-25 03:19:48.000000000 +0000 @@ -772,8 +772,6 @@ t.remainder = t.inBuf[:n+len(t.remainder)] } - - panic("unreachable") // for Go 1.0. } // SetPrompt sets the prompt to be used when reading subsequent lines. @@ -922,3 +920,32 @@ } return s.entries[index], true } + +// readPasswordLine reads from reader until it finds \n or io.EOF. +// The slice returned does not include the \n. +// readPasswordLine also ignores any \r it finds. +func readPasswordLine(reader io.Reader) ([]byte, error) { + var buf [1]byte + var ret []byte + + for { + n, err := reader.Read(buf[:]) + if n > 0 { + switch buf[0] { + case '\n': + return ret, nil + case '\r': + // remove \r from passwords on Windows + default: + ret = append(ret, buf[0]) + } + continue + } + if err != nil { + if err == io.EOF && len(ret) > 0 { + return ret, nil + } + return ret, err + } + } +} diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/terminal/terminal_test.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/terminal/terminal_test.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/terminal/terminal_test.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/terminal/terminal_test.go 2017-01-25 03:19:48.000000000 +0000 @@ -270,6 +270,50 @@ } } +func TestReadPasswordLineEnd(t *testing.T) { + var tests = []struct { + input string + want string + }{ + {"\n", ""}, + {"\r\n", ""}, + {"test\r\n", "test"}, + {"testtesttesttes\n", "testtesttesttes"}, + {"testtesttesttes\r\n", "testtesttesttes"}, + {"testtesttesttesttest\n", "testtesttesttesttest"}, + {"testtesttesttesttest\r\n", "testtesttesttesttest"}, + } + for _, test := range tests { + buf := new(bytes.Buffer) + if _, err := buf.WriteString(test.input); err != nil { + t.Fatal(err) + } + + have, err := readPasswordLine(buf) + if err != nil { + t.Errorf("readPasswordLine(%q) failed: %v", test.input, err) + continue + } + if string(have) != test.want { + t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want) + continue + } + + if _, err = buf.WriteString(test.input); err != nil { + t.Fatal(err) + } + have, err = readPasswordLine(buf) + if err != nil { + t.Errorf("readPasswordLine(%q) failed: %v", test.input, err) + continue + } + if string(have) != test.want { + t.Errorf("readPasswordLine(%q) returns %q, but %q is expected", test.input, string(have), test.want) + continue + } + } +} + func TestMakeRawState(t *testing.T) { fd := int(os.Stdout.Fd()) if !IsTerminal(fd) { diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/terminal/util.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/terminal/util.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/terminal/util.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/terminal/util.go 2017-01-25 03:19:48.000000000 +0000 @@ -17,7 +17,6 @@ package terminal // import "golang.org/x/crypto/ssh/terminal" import ( - "io" "syscall" "unsafe" ) @@ -72,8 +71,10 @@ // Restore restores the terminal connected to the given file descriptor to a // previous state. func Restore(fd int, state *State) error { - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) - return err + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0); err != 0 { + return err + } + return nil } // GetSize returns the dimensions of the given terminal. @@ -86,6 +87,13 @@ return int(dimensions[1]), int(dimensions[0]), nil } +// passwordReader is an io.Reader that reads from a specific file descriptor. +type passwordReader int + +func (r passwordReader) Read(buf []byte) (int, error) { + return syscall.Read(int(r), buf) +} + // ReadPassword reads a line of input from a terminal without local echo. This // is commonly used for inputting passwords and other sensitive data. The slice // returned does not include the \n. @@ -107,27 +115,5 @@ syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) }() - var buf [16]byte - var ret []byte - for { - n, err := syscall.Read(fd, buf[:]) - if err != nil { - return nil, err - } - if n == 0 { - if len(ret) == 0 { - return nil, io.EOF - } - break - } - if buf[n-1] == '\n' { - n-- - } - ret = append(ret, buf[:n]...) - if n < len(buf) { - break - } - } - - return ret, nil + return readPasswordLine(passwordReader(fd)) } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/terminal/util_windows.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/terminal/util_windows.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/terminal/util_windows.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/terminal/util_windows.go 2017-01-25 03:19:48.000000000 +0000 @@ -17,7 +17,6 @@ package terminal import ( - "io" "syscall" "unsafe" ) @@ -123,6 +122,13 @@ return int(info.size.x), int(info.size.y), nil } +// passwordReader is an io.Reader that reads from a specific Windows HANDLE. +type passwordReader int + +func (r passwordReader) Read(buf []byte) (int, error) { + return syscall.Read(syscall.Handle(r), buf) +} + // ReadPassword reads a line of input from a terminal without local echo. This // is commonly used for inputting passwords and other sensitive data. The slice // returned does not include the \n. @@ -145,30 +151,5 @@ syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) }() - var buf [16]byte - var ret []byte - for { - n, err := syscall.Read(syscall.Handle(fd), buf[:]) - if err != nil { - return nil, err - } - if n == 0 { - if len(ret) == 0 { - return nil, io.EOF - } - break - } - if buf[n-1] == '\n' { - n-- - } - if n > 0 && buf[n-1] == '\r' { - n-- - } - ret = append(ret, buf[:n]...) - if n < len(buf) { - break - } - } - - return ret, nil + return readPasswordLine(passwordReader(fd)) } diff -Nru lxd-2.7/dist/src/golang.org/x/crypto/ssh/transport.go lxd-2.8/dist/src/golang.org/x/crypto/ssh/transport.go --- lxd-2.7/dist/src/golang.org/x/crypto/ssh/transport.go 2016-12-21 00:52:54.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/crypto/ssh/transport.go 2017-01-25 03:19:48.000000000 +0000 @@ -22,7 +22,9 @@ // Encrypt and send a packet of data to the remote peer. writePacket(packet []byte) error - // Read a packet from the connection + // Read a packet from the connection. The read is blocking, + // i.e. if error is nil, then the returned byte slice is + // always non-empty. readPacket() ([]byte, error) // Close closes the write-side of the connection. @@ -85,8 +87,18 @@ } // Read and decrypt next packet. -func (t *transport) readPacket() ([]byte, error) { - return t.reader.readPacket(t.bufReader) +func (t *transport) readPacket() (p []byte, err error) { + for { + p, err = t.reader.readPacket(t.bufReader) + if err != nil { + break + } + if len(p) == 0 || (p[0] != msgIgnore && p[0] != msgDebug) { + break + } + } + + return p, err } func (s *connectionState) readPacket(r *bufio.Reader) ([]byte, error) { diff -Nru lxd-2.7/dist/src/golang.org/x/net/bpf/constants.go lxd-2.8/dist/src/golang.org/x/net/bpf/constants.go --- lxd-2.7/dist/src/golang.org/x/net/bpf/constants.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/bpf/constants.go 2017-01-25 03:19:56.000000000 +0000 @@ -70,6 +70,9 @@ // Extension functions available in the Linux kernel. const ( + // extOffset is the negative maximum number of instructions used + // to load instructions by overloading the K argument. + extOffset = -0x1000 // ExtLen returns the length of the packet. ExtLen Extension = 1 // ExtProto returns the packet's L3 protocol type. diff -Nru lxd-2.7/dist/src/golang.org/x/net/bpf/instructions.go lxd-2.8/dist/src/golang.org/x/net/bpf/instructions.go --- lxd-2.7/dist/src/golang.org/x/net/bpf/instructions.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/bpf/instructions.go 2017-01-25 03:19:56.000000000 +0000 @@ -57,6 +57,9 @@ } return LoadScratch{Dst: reg, N: int(ri.K)} case opAddrModeAbsolute: + if ri.K > extOffset+0xffffffff { + return LoadExtension{Num: Extension(-extOffset + ri.K)} + } return LoadAbsolute{Size: sz, Off: ri.K} case opAddrModeIndirect: return LoadIndirect{Size: sz, Off: ri.K} @@ -104,6 +107,14 @@ case opJumpAlways: return Jump{Skip: ri.K} case opJumpEqual: + if ri.Jt == 0 { + return JumpIf{ + Cond: JumpNotEqual, + Val: ri.K, + SkipTrue: ri.Jf, + SkipFalse: 0, + } + } return JumpIf{ Cond: JumpEqual, Val: ri.K, @@ -111,6 +122,14 @@ SkipFalse: ri.Jf, } case opJumpGT: + if ri.Jt == 0 { + return JumpIf{ + Cond: JumpLessOrEqual, + Val: ri.K, + SkipTrue: ri.Jf, + SkipFalse: 0, + } + } return JumpIf{ Cond: JumpGreaterThan, Val: ri.K, @@ -118,6 +137,14 @@ SkipFalse: ri.Jf, } case opJumpGE: + if ri.Jt == 0 { + return JumpIf{ + Cond: JumpLessThan, + Val: ri.K, + SkipTrue: ri.Jf, + SkipFalse: 0, + } + } return JumpIf{ Cond: JumpGreaterOrEqual, Val: ri.K, @@ -171,6 +198,18 @@ return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val) } +// String returns the the instruction in assembler notation. +func (a LoadConstant) String() string { + switch a.Dst { + case RegA: + return fmt.Sprintf("ld #%d", a.Val) + case RegX: + return fmt.Sprintf("ldx #%d", a.Val) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + // LoadScratch loads scratch[N] into register Dst. type LoadScratch struct { Dst Register @@ -185,6 +224,18 @@ return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N)) } +// String returns the the instruction in assembler notation. +func (a LoadScratch) String() string { + switch a.Dst { + case RegA: + return fmt.Sprintf("ld M[%d]", a.N) + case RegX: + return fmt.Sprintf("ldx M[%d]", a.N) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + // LoadAbsolute loads packet[Off:Off+Size] as an integer value into // register A. type LoadAbsolute struct { @@ -197,6 +248,23 @@ return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off) } +// String returns the the instruction in assembler notation. +func (a LoadAbsolute) String() string { + switch a.Size { + case 1: // byte + return fmt.Sprintf("ldb [%d]", a.Off) + case 2: // half word + return fmt.Sprintf("ldh [%d]", a.Off) + case 4: // word + if a.Off > extOffset+0xffffffff { + return LoadExtension{Num: Extension(a.Off + 0x1000)}.String() + } + return fmt.Sprintf("ld [%d]", a.Off) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + // LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value // into register A. type LoadIndirect struct { @@ -209,6 +277,20 @@ return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off) } +// String returns the the instruction in assembler notation. +func (a LoadIndirect) String() string { + switch a.Size { + case 1: // byte + return fmt.Sprintf("ldb [x + %d]", a.Off) + case 2: // half word + return fmt.Sprintf("ldh [x + %d]", a.Off) + case 4: // word + return fmt.Sprintf("ld [x + %d]", a.Off) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + // LoadMemShift multiplies the first 4 bits of the byte at packet[Off] // by 4 and stores the result in register X. // @@ -224,6 +306,11 @@ return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off) } +// String returns the the instruction in assembler notation. +func (a LoadMemShift) String() string { + return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off) +} + // LoadExtension invokes a linux-specific extension and stores the // result in register A. type LoadExtension struct { @@ -235,7 +322,47 @@ if a.Num == ExtLen { return assembleLoad(RegA, 4, opAddrModePacketLen, 0) } - return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(-0x1000+a.Num)) + return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num)) +} + +// String returns the the instruction in assembler notation. +func (a LoadExtension) String() string { + switch a.Num { + case ExtLen: + return "ld #len" + case ExtProto: + return "ld #proto" + case ExtType: + return "ld #type" + case ExtPayloadOffset: + return "ld #poff" + case ExtInterfaceIndex: + return "ld #ifidx" + case ExtNetlinkAttr: + return "ld #nla" + case ExtNetlinkAttrNested: + return "ld #nlan" + case ExtMark: + return "ld #mark" + case ExtQueue: + return "ld #queue" + case ExtLinkLayerType: + return "ld #hatype" + case ExtRXHash: + return "ld #rxhash" + case ExtCPUID: + return "ld #cpu" + case ExtVLANTag: + return "ld #vlan_tci" + case ExtVLANTagPresent: + return "ld #vlan_avail" + case ExtVLANProto: + return "ld #vlan_tpid" + case ExtRand: + return "ld #rand" + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } } // StoreScratch stores register Src into scratch[N]. @@ -265,6 +392,18 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a StoreScratch) String() string { + switch a.Src { + case RegA: + return fmt.Sprintf("st M[%d]", a.N) + case RegX: + return fmt.Sprintf("stx M[%d]", a.N) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + // ALUOpConstant executes A = A Val. type ALUOpConstant struct { Op ALUOp @@ -279,6 +418,34 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a ALUOpConstant) String() string { + switch a.Op { + case ALUOpAdd: + return fmt.Sprintf("add #%d", a.Val) + case ALUOpSub: + return fmt.Sprintf("sub #%d", a.Val) + case ALUOpMul: + return fmt.Sprintf("mul #%d", a.Val) + case ALUOpDiv: + return fmt.Sprintf("div #%d", a.Val) + case ALUOpMod: + return fmt.Sprintf("mod #%d", a.Val) + case ALUOpAnd: + return fmt.Sprintf("and #%d", a.Val) + case ALUOpOr: + return fmt.Sprintf("or #%d", a.Val) + case ALUOpXor: + return fmt.Sprintf("xor #%d", a.Val) + case ALUOpShiftLeft: + return fmt.Sprintf("lsh #%d", a.Val) + case ALUOpShiftRight: + return fmt.Sprintf("rsh #%d", a.Val) + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + // ALUOpX executes A = A X type ALUOpX struct { Op ALUOp @@ -291,6 +458,34 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a ALUOpX) String() string { + switch a.Op { + case ALUOpAdd: + return "add x" + case ALUOpSub: + return "sub x" + case ALUOpMul: + return "mul x" + case ALUOpDiv: + return "div x" + case ALUOpMod: + return "mod x" + case ALUOpAnd: + return "and x" + case ALUOpOr: + return "or x" + case ALUOpXor: + return "xor x" + case ALUOpShiftLeft: + return "lsh x" + case ALUOpShiftRight: + return "rsh x" + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + // NegateA executes A = -A. type NegateA struct{} @@ -301,6 +496,11 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a NegateA) String() string { + return fmt.Sprintf("neg") +} + // Jump skips the following Skip instructions in the program. type Jump struct { Skip uint32 @@ -314,6 +514,11 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a Jump) String() string { + return fmt.Sprintf("ja %d", a.Skip) +} + // JumpIf skips the following Skip instructions in the program if A // Val is true. type JumpIf struct { @@ -361,6 +566,51 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a JumpIf) String() string { + switch a.Cond { + // K == A + case JumpEqual: + return conditionalJump(a, "jeq", "jneq") + // K != A + case JumpNotEqual: + return fmt.Sprintf("jneq #%d,%d", a.Val, a.SkipTrue) + // K > A + case JumpGreaterThan: + return conditionalJump(a, "jgt", "jle") + // K < A + case JumpLessThan: + return fmt.Sprintf("jlt #%d,%d", a.Val, a.SkipTrue) + // K >= A + case JumpGreaterOrEqual: + return conditionalJump(a, "jge", "jlt") + // K <= A + case JumpLessOrEqual: + return fmt.Sprintf("jle #%d,%d", a.Val, a.SkipTrue) + // K & A != 0 + case JumpBitsSet: + if a.SkipFalse > 0 { + return fmt.Sprintf("jset #%d,%d,%d", a.Val, a.SkipTrue, a.SkipFalse) + } + return fmt.Sprintf("jset #%d,%d", a.Val, a.SkipTrue) + // K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips + case JumpBitsNotSet: + return JumpIf{Cond: JumpBitsSet, SkipTrue: a.SkipFalse, SkipFalse: a.SkipTrue, Val: a.Val}.String() + default: + return fmt.Sprintf("unknown instruction: %#v", a) + } +} + +func conditionalJump(inst JumpIf, positiveJump, negativeJump string) string { + if inst.SkipTrue > 0 { + if inst.SkipFalse > 0 { + return fmt.Sprintf("%s #%d,%d,%d", positiveJump, inst.Val, inst.SkipTrue, inst.SkipFalse) + } + return fmt.Sprintf("%s #%d,%d", positiveJump, inst.Val, inst.SkipTrue) + } + return fmt.Sprintf("%s #%d,%d", negativeJump, inst.Val, inst.SkipFalse) +} + // RetA exits the BPF program, returning the value of register A. type RetA struct{} @@ -371,6 +621,11 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a RetA) String() string { + return fmt.Sprintf("ret a") +} + // RetConstant exits the BPF program, returning a constant value. type RetConstant struct { Val uint32 @@ -384,6 +639,11 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a RetConstant) String() string { + return fmt.Sprintf("ret #%d", a.Val) +} + // TXA copies the value of register X to register A. type TXA struct{} @@ -394,6 +654,11 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a TXA) String() string { + return fmt.Sprintf("txa") +} + // TAX copies the value of register A to register X. type TAX struct{} @@ -404,6 +669,11 @@ }, nil } +// String returns the the instruction in assembler notation. +func (a TAX) String() string { + return fmt.Sprintf("tax") +} + func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) { var ( cls uint16 diff -Nru lxd-2.7/dist/src/golang.org/x/net/bpf/instructions_test.go lxd-2.8/dist/src/golang.org/x/net/bpf/instructions_test.go --- lxd-2.7/dist/src/golang.org/x/net/bpf/instructions_test.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/bpf/instructions_test.go 2017-01-25 03:19:56.000000000 +0000 @@ -5,6 +5,7 @@ package bpf import ( + "fmt" "io/ioutil" "reflect" "strconv" @@ -143,11 +144,6 @@ } // Check that assembly and disassembly match each other. -// -// Because we offer "fake" jump conditions that don't appear in the -// machine code, disassembly won't be a 1:1 match with the original -// source, although the behavior will be identical. However, -// reassembling the disassembly should produce an identical program. func TestAsmDisasm(t *testing.T) { prog1, err := Assemble(allInstructions) if err != nil { @@ -155,30 +151,375 @@ } t.Logf("Assembled program is %d instructions long", len(prog1)) - src, allDecoded := Disassemble(prog1) + got, allDecoded := Disassemble(prog1) if !allDecoded { t.Errorf("Disassemble(Assemble(allInstructions)) produced unrecognized instructions:") - for i, inst := range src { + for i, inst := range got { if r, ok := inst.(RawInstruction); ok { t.Logf(" insn %d, %#v --> %#v", i+1, allInstructions[i], r) } } } - prog2, err := Assemble(src) - if err != nil { - t.Fatalf("assembly of Disassemble(Assemble(allInstructions)) failed: %s", err) + if len(allInstructions) != len(got) { + t.Fatalf("disassembly changed program size: %d insns before, %d insns after", len(allInstructions), len(got)) } + if !reflect.DeepEqual(allInstructions, got) { + t.Errorf("program mutated by disassembly:") + for i := range got { + if !reflect.DeepEqual(allInstructions[i], got[i]) { + t.Logf(" insn %d, s: %#v, p1: %#v, got: %#v", i+1, allInstructions[i], prog1[i], got[i]) + } + } + } +} + +type InvalidInstruction struct{} - if len(prog2) != len(prog1) { - t.Fatalf("disassembly changed program size: %d insns before, %d insns after", len(prog1), len(prog2)) +func (a InvalidInstruction) Assemble() (RawInstruction, error) { + return RawInstruction{}, fmt.Errorf("Invalid Instruction") +} + +func (a InvalidInstruction) String() string { + return fmt.Sprintf("unknown instruction: %#v", a) +} + +func TestString(t *testing.T) { + testCases := []struct { + instruction Instruction + assembler string + }{ + { + instruction: LoadConstant{Dst: RegA, Val: 42}, + assembler: "ld #42", + }, + { + instruction: LoadConstant{Dst: RegX, Val: 42}, + assembler: "ldx #42", + }, + { + instruction: LoadConstant{Dst: 0xffff, Val: 42}, + assembler: "unknown instruction: bpf.LoadConstant{Dst:0xffff, Val:0x2a}", + }, + { + instruction: LoadScratch{Dst: RegA, N: 3}, + assembler: "ld M[3]", + }, + { + instruction: LoadScratch{Dst: RegX, N: 3}, + assembler: "ldx M[3]", + }, + { + instruction: LoadScratch{Dst: 0xffff, N: 3}, + assembler: "unknown instruction: bpf.LoadScratch{Dst:0xffff, N:3}", + }, + { + instruction: LoadAbsolute{Off: 42, Size: 1}, + assembler: "ldb [42]", + }, + { + instruction: LoadAbsolute{Off: 42, Size: 2}, + assembler: "ldh [42]", + }, + { + instruction: LoadAbsolute{Off: 42, Size: 4}, + assembler: "ld [42]", + }, + { + instruction: LoadAbsolute{Off: 42, Size: -1}, + assembler: "unknown instruction: bpf.LoadAbsolute{Off:0x2a, Size:-1}", + }, + { + instruction: LoadIndirect{Off: 42, Size: 1}, + assembler: "ldb [x + 42]", + }, + { + instruction: LoadIndirect{Off: 42, Size: 2}, + assembler: "ldh [x + 42]", + }, + { + instruction: LoadIndirect{Off: 42, Size: 4}, + assembler: "ld [x + 42]", + }, + { + instruction: LoadIndirect{Off: 42, Size: -1}, + assembler: "unknown instruction: bpf.LoadIndirect{Off:0x2a, Size:-1}", + }, + { + instruction: LoadMemShift{Off: 42}, + assembler: "ldx 4*([42]&0xf)", + }, + { + instruction: LoadExtension{Num: ExtLen}, + assembler: "ld #len", + }, + { + instruction: LoadExtension{Num: ExtProto}, + assembler: "ld #proto", + }, + { + instruction: LoadExtension{Num: ExtType}, + assembler: "ld #type", + }, + { + instruction: LoadExtension{Num: ExtPayloadOffset}, + assembler: "ld #poff", + }, + { + instruction: LoadExtension{Num: ExtInterfaceIndex}, + assembler: "ld #ifidx", + }, + { + instruction: LoadExtension{Num: ExtNetlinkAttr}, + assembler: "ld #nla", + }, + { + instruction: LoadExtension{Num: ExtNetlinkAttrNested}, + assembler: "ld #nlan", + }, + { + instruction: LoadExtension{Num: ExtMark}, + assembler: "ld #mark", + }, + { + instruction: LoadExtension{Num: ExtQueue}, + assembler: "ld #queue", + }, + { + instruction: LoadExtension{Num: ExtLinkLayerType}, + assembler: "ld #hatype", + }, + { + instruction: LoadExtension{Num: ExtRXHash}, + assembler: "ld #rxhash", + }, + { + instruction: LoadExtension{Num: ExtCPUID}, + assembler: "ld #cpu", + }, + { + instruction: LoadExtension{Num: ExtVLANTag}, + assembler: "ld #vlan_tci", + }, + { + instruction: LoadExtension{Num: ExtVLANTagPresent}, + assembler: "ld #vlan_avail", + }, + { + instruction: LoadExtension{Num: ExtVLANProto}, + assembler: "ld #vlan_tpid", + }, + { + instruction: LoadExtension{Num: ExtRand}, + assembler: "ld #rand", + }, + { + instruction: LoadAbsolute{Off: 0xfffff038, Size: 4}, + assembler: "ld #rand", + }, + { + instruction: LoadExtension{Num: 0xfff}, + assembler: "unknown instruction: bpf.LoadExtension{Num:4095}", + }, + { + instruction: StoreScratch{Src: RegA, N: 3}, + assembler: "st M[3]", + }, + { + instruction: StoreScratch{Src: RegX, N: 3}, + assembler: "stx M[3]", + }, + { + instruction: StoreScratch{Src: 0xffff, N: 3}, + assembler: "unknown instruction: bpf.StoreScratch{Src:0xffff, N:3}", + }, + { + instruction: ALUOpConstant{Op: ALUOpAdd, Val: 42}, + assembler: "add #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpSub, Val: 42}, + assembler: "sub #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpMul, Val: 42}, + assembler: "mul #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpDiv, Val: 42}, + assembler: "div #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpOr, Val: 42}, + assembler: "or #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpAnd, Val: 42}, + assembler: "and #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpShiftLeft, Val: 42}, + assembler: "lsh #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpShiftRight, Val: 42}, + assembler: "rsh #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpMod, Val: 42}, + assembler: "mod #42", + }, + { + instruction: ALUOpConstant{Op: ALUOpXor, Val: 42}, + assembler: "xor #42", + }, + { + instruction: ALUOpConstant{Op: 0xffff, Val: 42}, + assembler: "unknown instruction: bpf.ALUOpConstant{Op:0xffff, Val:0x2a}", + }, + { + instruction: ALUOpX{Op: ALUOpAdd}, + assembler: "add x", + }, + { + instruction: ALUOpX{Op: ALUOpSub}, + assembler: "sub x", + }, + { + instruction: ALUOpX{Op: ALUOpMul}, + assembler: "mul x", + }, + { + instruction: ALUOpX{Op: ALUOpDiv}, + assembler: "div x", + }, + { + instruction: ALUOpX{Op: ALUOpOr}, + assembler: "or x", + }, + { + instruction: ALUOpX{Op: ALUOpAnd}, + assembler: "and x", + }, + { + instruction: ALUOpX{Op: ALUOpShiftLeft}, + assembler: "lsh x", + }, + { + instruction: ALUOpX{Op: ALUOpShiftRight}, + assembler: "rsh x", + }, + { + instruction: ALUOpX{Op: ALUOpMod}, + assembler: "mod x", + }, + { + instruction: ALUOpX{Op: ALUOpXor}, + assembler: "xor x", + }, + { + instruction: ALUOpX{Op: 0xffff}, + assembler: "unknown instruction: bpf.ALUOpX{Op:0xffff}", + }, + { + instruction: NegateA{}, + assembler: "neg", + }, + { + instruction: Jump{Skip: 10}, + assembler: "ja 10", + }, + { + instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8, SkipFalse: 9}, + assembler: "jeq #42,8,9", + }, + { + instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8}, + assembler: "jeq #42,8", + }, + { + instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipFalse: 8}, + assembler: "jneq #42,8", + }, + { + instruction: JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 8}, + assembler: "jneq #42,8", + }, + { + instruction: JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 7}, + assembler: "jlt #42,7", + }, + { + instruction: JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 6}, + assembler: "jle #42,6", + }, + { + instruction: JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4, SkipFalse: 5}, + assembler: "jgt #42,4,5", + }, + { + instruction: JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4}, + assembler: "jgt #42,4", + }, + { + instruction: JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3, SkipFalse: 4}, + assembler: "jge #42,3,4", + }, + { + instruction: JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3}, + assembler: "jge #42,3", + }, + { + instruction: JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2, SkipFalse: 3}, + assembler: "jset #42,2,3", + }, + { + instruction: JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2}, + assembler: "jset #42,2", + }, + { + instruction: JumpIf{Cond: JumpBitsNotSet, Val: 42, SkipTrue: 2, SkipFalse: 3}, + assembler: "jset #42,3,2", + }, + { + instruction: JumpIf{Cond: JumpBitsNotSet, Val: 42, SkipTrue: 2}, + assembler: "jset #42,0,2", + }, + { + instruction: JumpIf{Cond: 0xffff, Val: 42, SkipTrue: 1, SkipFalse: 2}, + assembler: "unknown instruction: bpf.JumpIf{Cond:0xffff, Val:0x2a, SkipTrue:0x1, SkipFalse:0x2}", + }, + { + instruction: TAX{}, + assembler: "tax", + }, + { + instruction: TXA{}, + assembler: "txa", + }, + { + instruction: RetA{}, + assembler: "ret a", + }, + { + instruction: RetConstant{Val: 42}, + assembler: "ret #42", + }, + // Invalid instruction + { + instruction: InvalidInstruction{}, + assembler: "unknown instruction: bpf.InvalidInstruction{}", + }, } - if !reflect.DeepEqual(prog1, prog2) { - t.Errorf("program mutated by disassembly:") - for i := range prog2 { - if !reflect.DeepEqual(prog1[i], prog2[i]) { - t.Logf(" insn %d, s: %#v, p1: %#v, p2: %#v", i+1, allInstructions[i], prog1[i], prog2[i]) + + for _, testCase := range testCases { + if input, ok := testCase.instruction.(fmt.Stringer); ok { + got := input.String() + if got != testCase.assembler { + t.Errorf("String did not return expected assembler notation, expected: %s, got: %s", testCase.assembler, got) } + } else { + t.Errorf("Instruction %#v is not a fmt.Stringer", testCase.instruction) } } } diff -Nru lxd-2.7/dist/src/golang.org/x/net/http2/h2i/h2i.go lxd-2.8/dist/src/golang.org/x/net/http2/h2i/h2i.go --- lxd-2.7/dist/src/golang.org/x/net/http2/h2i/h2i.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/http2/h2i/h2i.go 2017-01-25 03:19:56.000000000 +0000 @@ -88,6 +88,14 @@ return host } +// withoutPort strips the port from addr if present. +func withoutPort(addr string) string { + if h, _, err := net.SplitHostPort(addr); err == nil { + return h + } + return addr +} + // h2i is the app's state. type h2i struct { host string @@ -134,7 +142,7 @@ func (app *h2i) Main() error { cfg := &tls.Config{ - ServerName: app.host, + ServerName: withoutPort(app.host), NextProtos: strings.Split(*flagNextProto, ","), InsecureSkipVerify: *flagInsecure, } @@ -473,7 +481,7 @@ host = req.URL.Host } - path := req.URL.Path + path := req.RequestURI if path == "" { path = "/" } diff -Nru lxd-2.7/dist/src/golang.org/x/net/http2/server.go lxd-2.8/dist/src/golang.org/x/net/http2/server.go --- lxd-2.7/dist/src/golang.org/x/net/http2/server.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/http2/server.go 2017-01-25 03:19:56.000000000 +0000 @@ -278,6 +278,16 @@ pushEnabled: true, } + // The net/http package sets the write deadline from the + // http.Server.WriteTimeout during the TLS handshake, but then + // passes the connection off to us with the deadline already + // set. Disarm it here so that it is not applied to additional + // streams opened on this connection. + // TODO: implement WriteTimeout fully. See Issue 18437. + if sc.hs.WriteTimeout != 0 { + sc.conn.SetWriteDeadline(time.Time{}) + } + if s.NewWriteScheduler != nil { sc.writeSched = s.NewWriteScheduler() } else { diff -Nru lxd-2.7/dist/src/golang.org/x/net/http2/server_push_test.go lxd-2.8/dist/src/golang.org/x/net/http2/server_push_test.go --- lxd-2.7/dist/src/golang.org/x/net/http2/server_push_test.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/http2/server_push_test.go 2017-01-25 03:19:56.000000000 +0000 @@ -430,18 +430,20 @@ func TestServer_Push_StateTransitions(t *testing.T) { const body = "foo" - startedPromise := make(chan bool) + gotPromise := make(chan bool) finishedPush := make(chan bool) + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { switch r.URL.RequestURI() { case "/": if err := w.(http.Pusher).Push("/pushed", nil); err != nil { t.Errorf("Push error: %v", err) } - close(startedPromise) // Don't finish this request until the push finishes so we don't // nondeterministically interleave output frames with the push. <-finishedPush + case "/pushed": + <-gotPromise } w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Length", strconv.Itoa(len(body))) @@ -458,11 +460,16 @@ t.Fatalf("streamState(2)=%v, want %v", got, want) } getSlash(st) - <-startedPromise + // After the PUSH_PROMISE is sent, the stream should be stateHalfClosedRemote. + st.wantPushPromise() if got, want := st.streamState(2), stateHalfClosedRemote; got != want { t.Fatalf("streamState(2)=%v, want %v", got, want) } - st.wantPushPromise() + // We stall the HTTP handler for "/pushed" until the above check. If we don't + // stall the handler, then the handler might write HEADERS and DATA and finish + // the stream before we check st.streamState(2) -- should that happen, we'll + // see stateClosed and fail the above check. + close(gotPromise) st.wantHeaders() if df := st.wantData(); !df.StreamEnded() { t.Fatal("expected END_STREAM flag on DATA") diff -Nru lxd-2.7/dist/src/golang.org/x/net/http2/transport.go lxd-2.8/dist/src/golang.org/x/net/http2/transport.go --- lxd-2.7/dist/src/golang.org/x/net/http2/transport.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/http2/transport.go 2017-01-25 03:19:56.000000000 +0000 @@ -658,8 +658,6 @@ } if len(keys) > 0 { sort.Strings(keys) - // TODO: could do better allocation-wise here, but trailers are rare, - // so being lazy for now. return strings.Join(keys, ","), nil } return "", nil diff -Nru lxd-2.7/dist/src/golang.org/x/net/http2/writesched_priority_test.go lxd-2.8/dist/src/golang.org/x/net/http2/writesched_priority_test.go --- lxd-2.7/dist/src/golang.org/x/net/http2/writesched_priority_test.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/http2/writesched_priority_test.go 2017-01-25 03:19:56.000000000 +0000 @@ -434,7 +434,7 @@ t.Fatalf("Pop(%d)=false, want true", i) } if got, want := wr.DataSize(), 8; got != want { - t.Fatalf("Pop(%d)=%d bytes, want %d bytes", got, want) + t.Fatalf("Pop(%d)=%d bytes, want %d bytes", i, got, want) } } } diff -Nru lxd-2.7/dist/src/golang.org/x/net/http2/writesched_random_test.go lxd-2.8/dist/src/golang.org/x/net/http2/writesched_random_test.go --- lxd-2.7/dist/src/golang.org/x/net/http2/writesched_random_test.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/http2/writesched_random_test.go 2017-01-25 03:19:56.000000000 +0000 @@ -30,7 +30,7 @@ t.Fatalf("got %d frames, expected 6", len(order)) } if order[0].StreamID() != 0 || order[1].StreamID() != 0 { - t.Fatalf("expected non-stream frames first", order[0], order[1]) + t.Fatal("expected non-stream frames first", order[0], order[1]) } got := make(map[uint32]bool) for _, wr := range order[2:] { diff -Nru lxd-2.7/dist/src/golang.org/x/net/internal/netreflect/socket_test.go lxd-2.8/dist/src/golang.org/x/net/internal/netreflect/socket_test.go --- lxd-2.7/dist/src/golang.org/x/net/internal/netreflect/socket_test.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/internal/netreflect/socket_test.go 2017-01-25 03:19:56.000000000 +0000 @@ -5,76 +5,20 @@ package netreflect_test import ( - "fmt" - "io/ioutil" "net" "os" - "runtime" "testing" "golang.org/x/net/internal/netreflect" + "golang.org/x/net/internal/nettest" ) -func localPath() string { - f, err := ioutil.TempFile("", "netreflect") - if err != nil { - panic(err) - } - path := f.Name() - f.Close() - os.Remove(path) - return path -} - -func newLocalListener(network string) (net.Listener, error) { - switch network { - case "tcp": - if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil { - return ln, nil - } - return net.Listen("tcp6", "[::1]:0") - case "tcp4": - return net.Listen("tcp4", "127.0.0.1:0") - case "tcp6": - return net.Listen("tcp6", "[::1]:0") - case "unix", "unixpacket": - return net.Listen(network, localPath()) - } - return nil, fmt.Errorf("%s is not supported", network) -} - -func newLocalPacketListener(network string) (net.PacketConn, error) { - switch network { - case "udp": - if c, err := net.ListenPacket("udp4", "127.0.0.1:0"); err == nil { - return c, nil - } - return net.ListenPacket("udp6", "[::1]:0") - case "udp4": - return net.ListenPacket("udp4", "127.0.0.1:0") - case "udp6": - return net.ListenPacket("udp6", "[::1]:0") - case "unixgram": - return net.ListenPacket(network, localPath()) - } - return nil, fmt.Errorf("%s is not supported", network) -} - func TestSocketOf(t *testing.T) { for _, network := range []string{"tcp", "unix", "unixpacket"} { - switch runtime.GOOS { - case "darwin": - if network == "unixpacket" { - continue - } - case "nacl", "plan9": + if !nettest.TestableNetwork(network) { continue - case "windows": - if network == "unix" || network == "unixpacket" { - continue - } } - ln, err := newLocalListener(network) + ln, err := nettest.NewLocalListener(network) if err != nil { t.Error(err) continue @@ -101,15 +45,10 @@ func TestPacketSocketOf(t *testing.T) { for _, network := range []string{"udp", "unixgram"} { - switch runtime.GOOS { - case "nacl", "plan9": + if !nettest.TestableNetwork(network) { continue - case "windows": - if network == "unixgram" { - continue - } } - c, err := newLocalPacketListener(network) + c, err := nettest.NewLocalPacketListener(network) if err != nil { t.Error(err) continue diff -Nru lxd-2.7/dist/src/golang.org/x/net/internal/nettest/stack.go lxd-2.8/dist/src/golang.org/x/net/internal/nettest/stack.go --- lxd-2.7/dist/src/golang.org/x/net/internal/nettest/stack.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/internal/nettest/stack.go 2017-01-25 03:19:56.000000000 +0000 @@ -2,35 +2,40 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package nettest provides utilities for IP testing. +// Package nettest provides utilities for network testing. package nettest // import "golang.org/x/net/internal/nettest" -import "net" +import ( + "fmt" + "io/ioutil" + "net" + "os" + "runtime" +) + +var ( + supportsIPv4 bool + supportsIPv6 bool +) + +func init() { + if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil { + ln.Close() + supportsIPv4 = true + } + if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil { + ln.Close() + supportsIPv6 = true + } +} // SupportsIPv4 reports whether the platform supports IPv4 networking // functionality. -func SupportsIPv4() bool { - ln, err := net.Listen("tcp4", "127.0.0.1:0") - if err != nil { - return false - } - ln.Close() - return true -} +func SupportsIPv4() bool { return supportsIPv4 } // SupportsIPv6 reports whether the platform supports IPv6 networking // functionality. -func SupportsIPv6() bool { - if causesIPv6Crash() { - return false - } - ln, err := net.Listen("tcp6", "[::1]:0") - if err != nil { - return false - } - ln.Close() - return true -} +func SupportsIPv6() bool { return supportsIPv6 } // SupportsRawIPSocket reports whether the platform supports raw IP // sockets. @@ -50,3 +55,93 @@ func ProtocolNotSupported(err error) bool { return protocolNotSupported(err) } + +// TestableNetwork reports whether network is testable on the current +// platform configuration. +func TestableNetwork(network string) bool { + // This is based on logic from standard library's + // net/platform_test.go. + switch network { + case "unix", "unixgram": + switch runtime.GOOS { + case "android", "nacl", "plan9", "windows": + return false + } + if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") { + return false + } + case "unixpacket": + switch runtime.GOOS { + case "android", "darwin", "freebsd", "nacl", "plan9", "windows": + return false + } + } + return true +} + +// NewLocalListener returns a listener which listens to a loopback IP +// address or local file system path. +// Network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket". +func NewLocalListener(network string) (net.Listener, error) { + switch network { + case "tcp": + if supportsIPv4 { + if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil { + return ln, nil + } + } + if supportsIPv6 { + return net.Listen("tcp6", "[::1]:0") + } + case "tcp4": + if supportsIPv4 { + return net.Listen("tcp4", "127.0.0.1:0") + } + case "tcp6": + if supportsIPv6 { + return net.Listen("tcp6", "[::1]:0") + } + case "unix", "unixpacket": + return net.Listen(network, localPath()) + } + return nil, fmt.Errorf("%s is not supported", network) +} + +// NewLocalPacketListener returns a packet listener which listens to a +// loopback IP address or local file system path. +// Network must be "udp", "udp4", "udp6" or "unixgram". +func NewLocalPacketListener(network string) (net.PacketConn, error) { + switch network { + case "udp": + if supportsIPv4 { + if c, err := net.ListenPacket("udp4", "127.0.0.1:0"); err == nil { + return c, nil + } + } + if supportsIPv6 { + return net.ListenPacket("udp6", "[::1]:0") + } + case "udp4": + if supportsIPv4 { + return net.ListenPacket("udp4", "127.0.0.1:0") + } + case "udp6": + if supportsIPv6 { + return net.ListenPacket("udp6", "[::1]:0") + } + case "unixgram": + return net.ListenPacket(network, localPath()) + } + return nil, fmt.Errorf("%s is not supported", network) +} + +func localPath() string { + f, err := ioutil.TempFile("", "nettest") + if err != nil { + panic(err) + } + path := f.Name() + f.Close() + os.Remove(path) + return path +} diff -Nru lxd-2.7/dist/src/golang.org/x/net/ipv4/doc.go lxd-2.8/dist/src/golang.org/x/net/ipv4/doc.go --- lxd-2.7/dist/src/golang.org/x/net/ipv4/doc.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/ipv4/doc.go 2017-01-25 03:19:56.000000000 +0000 @@ -23,8 +23,8 @@ // net.UDPConn and net.IPConn which are created as network connections // that use the IPv4 transport. When a single TCP connection carrying // a data flow of multiple packets needs to indicate the flow is -// important, ipv4.Conn is used to set the type-of-service field on -// the IPv4 header for each packet. +// important, Conn is used to set the type-of-service field on the +// IPv4 header for each packet. // // ln, err := net.Listen("tcp4", "0.0.0.0:1024") // if err != nil { @@ -96,8 +96,8 @@ // The application might set per packet control message transmissions // between the protocol stack within the kernel. When the application // needs a destination address on an incoming packet, -// SetControlMessage of ipv4.PacketConn is used to enable control -// message transmissions. +// SetControlMessage of PacketConn is used to enable control message +// transmissions. // // if err := p.SetControlMessage(ipv4.FlagDst, true); err != nil { // // error handling diff -Nru lxd-2.7/dist/src/golang.org/x/net/ipv4/packet.go lxd-2.8/dist/src/golang.org/x/net/ipv4/packet.go --- lxd-2.7/dist/src/golang.org/x/net/ipv4/packet.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/ipv4/packet.go 2017-01-25 03:19:56.000000000 +0000 @@ -64,7 +64,7 @@ // // The IPv4 header h must contain appropriate fields that include: // -// Version = ipv4.Version +// Version = // Len = // TOS = // TotalLen = diff -Nru lxd-2.7/dist/src/golang.org/x/net/ipv4/sys_darwin.go lxd-2.8/dist/src/golang.org/x/net/ipv4/sys_darwin.go --- lxd-2.7/dist/src/golang.org/x/net/ipv4/sys_darwin.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/ipv4/sys_darwin.go 2017-01-25 03:19:56.000000000 +0000 @@ -6,6 +6,8 @@ import ( "net" + "strconv" + "strings" "syscall" "unsafe" ) @@ -36,41 +38,40 @@ func init() { // Seems like kern.osreldate is veiled on latest OS X. We use // kern.osrelease instead. - osver, err := syscall.Sysctl("kern.osrelease") + s, err := syscall.Sysctl("kern.osrelease") if err != nil { return } - var i int - for i = range osver { - if osver[i] == '.' { - break - } + ss := strings.Split(s, ".") + if len(ss) == 0 { + return } // The IP_PKTINFO and protocol-independent multicast API were - // introduced in OS X 10.7 (Darwin 11.0.0). But it looks like - // those features require OS X 10.8 (Darwin 12.0.0) and above. + // introduced in OS X 10.7 (Darwin 11). But it looks like + // those features require OS X 10.8 (Darwin 12) or above. // See http://support.apple.com/kb/HT1633. - if i > 2 || i == 2 && osver[0] >= '1' && osver[1] >= '2' { - ctlOpts[ctlPacketInfo].name = sysIP_PKTINFO - ctlOpts[ctlPacketInfo].length = sizeofInetPktinfo - ctlOpts[ctlPacketInfo].marshal = marshalPacketInfo - ctlOpts[ctlPacketInfo].parse = parsePacketInfo - sockOpts[ssoPacketInfo].name = sysIP_RECVPKTINFO - sockOpts[ssoPacketInfo].typ = ssoTypeInt - sockOpts[ssoMulticastInterface].typ = ssoTypeIPMreqn - sockOpts[ssoJoinGroup].name = sysMCAST_JOIN_GROUP - sockOpts[ssoJoinGroup].typ = ssoTypeGroupReq - sockOpts[ssoLeaveGroup].name = sysMCAST_LEAVE_GROUP - sockOpts[ssoLeaveGroup].typ = ssoTypeGroupReq - sockOpts[ssoJoinSourceGroup].name = sysMCAST_JOIN_SOURCE_GROUP - sockOpts[ssoJoinSourceGroup].typ = ssoTypeGroupSourceReq - sockOpts[ssoLeaveSourceGroup].name = sysMCAST_LEAVE_SOURCE_GROUP - sockOpts[ssoLeaveSourceGroup].typ = ssoTypeGroupSourceReq - sockOpts[ssoBlockSourceGroup].name = sysMCAST_BLOCK_SOURCE - sockOpts[ssoBlockSourceGroup].typ = ssoTypeGroupSourceReq - sockOpts[ssoUnblockSourceGroup].name = sysMCAST_UNBLOCK_SOURCE - sockOpts[ssoUnblockSourceGroup].typ = ssoTypeGroupSourceReq + if mjver, err := strconv.Atoi(ss[0]); err != nil || mjver < 12 { + return } + ctlOpts[ctlPacketInfo].name = sysIP_PKTINFO + ctlOpts[ctlPacketInfo].length = sizeofInetPktinfo + ctlOpts[ctlPacketInfo].marshal = marshalPacketInfo + ctlOpts[ctlPacketInfo].parse = parsePacketInfo + sockOpts[ssoPacketInfo].name = sysIP_RECVPKTINFO + sockOpts[ssoPacketInfo].typ = ssoTypeInt + sockOpts[ssoMulticastInterface].typ = ssoTypeIPMreqn + sockOpts[ssoJoinGroup].name = sysMCAST_JOIN_GROUP + sockOpts[ssoJoinGroup].typ = ssoTypeGroupReq + sockOpts[ssoLeaveGroup].name = sysMCAST_LEAVE_GROUP + sockOpts[ssoLeaveGroup].typ = ssoTypeGroupReq + sockOpts[ssoJoinSourceGroup].name = sysMCAST_JOIN_SOURCE_GROUP + sockOpts[ssoJoinSourceGroup].typ = ssoTypeGroupSourceReq + sockOpts[ssoLeaveSourceGroup].name = sysMCAST_LEAVE_SOURCE_GROUP + sockOpts[ssoLeaveSourceGroup].typ = ssoTypeGroupSourceReq + sockOpts[ssoBlockSourceGroup].name = sysMCAST_BLOCK_SOURCE + sockOpts[ssoBlockSourceGroup].typ = ssoTypeGroupSourceReq + sockOpts[ssoUnblockSourceGroup].name = sysMCAST_UNBLOCK_SOURCE + sockOpts[ssoUnblockSourceGroup].typ = ssoTypeGroupSourceReq } func (pi *inetPktinfo) setIfindex(i int) { diff -Nru lxd-2.7/dist/src/golang.org/x/net/ipv6/doc.go lxd-2.8/dist/src/golang.org/x/net/ipv6/doc.go --- lxd-2.7/dist/src/golang.org/x/net/ipv6/doc.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/ipv6/doc.go 2017-01-25 03:19:56.000000000 +0000 @@ -24,8 +24,8 @@ // net.UDPConn and net.IPConn which are created as network connections // that use the IPv6 transport. When a single TCP connection carrying // a data flow of multiple packets needs to indicate the flow is -// important, ipv6.Conn is used to set the traffic class field on the -// IPv6 header for each packet. +// important, Conn is used to set the traffic class field on the IPv6 +// header for each packet. // // ln, err := net.Listen("tcp6", "[::]:1024") // if err != nil { @@ -97,8 +97,8 @@ // The application might set per packet control message transmissions // between the protocol stack within the kernel. When the application // needs a destination address on an incoming packet, -// SetControlMessage of ipv6.PacketConn is used to enable control -// message transmissions. +// SetControlMessage of PacketConn is used to enable control message +// transmissions. // // if err := p.SetControlMessage(ipv6.FlagDst, true); err != nil { // // error handling diff -Nru lxd-2.7/dist/src/golang.org/x/net/ipv6/icmp.go lxd-2.8/dist/src/golang.org/x/net/ipv6/icmp.go --- lxd-2.7/dist/src/golang.org/x/net/ipv6/icmp.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/ipv6/icmp.go 2017-01-25 03:19:56.000000000 +0000 @@ -6,6 +6,9 @@ import "golang.org/x/net/internal/iana" +// BUG(mikio): On Windows, methods related to ICMPFilter are not +// implemented. + // An ICMPType represents a type of ICMP message. type ICMPType int diff -Nru lxd-2.7/dist/src/golang.org/x/net/nettest/conntest_test.go lxd-2.8/dist/src/golang.org/x/net/nettest/conntest_test.go --- lxd-2.7/dist/src/golang.org/x/net/nettest/conntest_test.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/nettest/conntest_test.go 2017-01-25 03:19:56.000000000 +0000 @@ -7,63 +7,13 @@ package nettest import ( - "fmt" - "io/ioutil" "net" "os" "runtime" "testing" -) - -// testUnixAddr uses ioutil.TempFile to get a name that is unique. -// It also uses /tmp directory in case it is prohibited to create UNIX -// sockets in TMPDIR. -func testUnixAddr() string { - f, err := ioutil.TempFile("", "go-nettest") - if err != nil { - panic(err) - } - addr := f.Name() - f.Close() - os.Remove(addr) - return addr -} -// testableNetwork reports whether network is testable on the current -// platform configuration. -// This is based on logic from standard library's net/platform_test.go. -func testableNetwork(network string) bool { - switch network { - case "unix": - switch runtime.GOOS { - case "android", "nacl", "plan9", "windows": - return false - } - if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") { - return false - } - case "unixpacket": - switch runtime.GOOS { - case "android", "darwin", "nacl", "plan9", "windows", "freebsd": - return false - } - } - return true -} - -func newLocalListener(network string) (net.Listener, error) { - switch network { - case "tcp": - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - ln, err = net.Listen("tcp6", "[::1]:0") - } - return ln, err - case "unix", "unixpacket": - return net.Listen(network, testUnixAddr()) - } - return nil, fmt.Errorf("%s is not supported", network) -} + "golang.org/x/net/internal/nettest" +) func TestTestConn(t *testing.T) { tests := []struct{ name, network string }{ @@ -74,12 +24,12 @@ for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if !testableNetwork(tt.network) { + if !nettest.TestableNetwork(tt.network) { t.Skipf("not supported on %s", runtime.GOOS) } mp := func() (c1, c2 net.Conn, stop func(), err error) { - ln, err := newLocalListener(tt.network) + ln, err := nettest.NewLocalListener(tt.network) if err != nil { return nil, nil, nil, err } diff -Nru lxd-2.7/dist/src/golang.org/x/net/trace/events.go lxd-2.8/dist/src/golang.org/x/net/trace/events.go --- lxd-2.7/dist/src/golang.org/x/net/trace/events.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/trace/events.go 2017-01-25 03:19:56.000000000 +0000 @@ -21,11 +21,6 @@ "time" ) -var eventsTmpl = template.Must(template.New("events").Funcs(template.FuncMap{ - "elapsed": elapsed, - "trimSpace": strings.TrimSpace, -}).Parse(eventsHTML)) - const maxEventsPerLog = 100 type bucket struct { @@ -101,7 +96,7 @@ famMu.RLock() defer famMu.RUnlock() - if err := eventsTmpl.Execute(w, data); err != nil { + if err := eventsTmpl().Execute(w, data); err != nil { log.Printf("net/trace: Failed executing template: %v", err) } } @@ -421,6 +416,19 @@ } } +var eventsTmplCache *template.Template +var eventsTmplOnce sync.Once + +func eventsTmpl() *template.Template { + eventsTmplOnce.Do(func() { + eventsTmplCache = template.Must(template.New("events").Funcs(template.FuncMap{ + "elapsed": elapsed, + "trimSpace": strings.TrimSpace, + }).Parse(eventsHTML)) + }) + return eventsTmplCache +} + const eventsHTML = ` diff -Nru lxd-2.7/dist/src/golang.org/x/net/trace/histogram.go lxd-2.8/dist/src/golang.org/x/net/trace/histogram.go --- lxd-2.7/dist/src/golang.org/x/net/trace/histogram.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/trace/histogram.go 2017-01-25 03:19:56.000000000 +0000 @@ -12,6 +12,7 @@ "html/template" "log" "math" + "sync" "golang.org/x/net/internal/timeseries" ) @@ -320,15 +321,20 @@ func (h *histogram) html() template.HTML { buf := new(bytes.Buffer) - if err := distTmpl.Execute(buf, h.newData()); err != nil { + if err := distTmpl().Execute(buf, h.newData()); err != nil { buf.Reset() log.Printf("net/trace: couldn't execute template: %v", err) } return template.HTML(buf.String()) } -// Input: data -var distTmpl = template.Must(template.New("distTmpl").Parse(` +var distTmplCache *template.Template +var distTmplOnce sync.Once + +func distTmpl() *template.Template { + distTmplOnce.Do(func() { + // Input: data + distTmplCache = template.Must(template.New("distTmpl").Parse(` @@ -354,3 +360,6 @@ {{end}}
Count: {{.Count}}
`)) + }) + return distTmplCache +} diff -Nru lxd-2.7/dist/src/golang.org/x/net/trace/trace.go lxd-2.8/dist/src/golang.org/x/net/trace/trace.go --- lxd-2.7/dist/src/golang.org/x/net/trace/trace.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/trace/trace.go 2017-01-25 03:19:56.000000000 +0000 @@ -238,7 +238,7 @@ completedMu.RLock() defer completedMu.RUnlock() - if err := pageTmpl.ExecuteTemplate(w, "Page", data); err != nil { + if err := pageTmpl().ExecuteTemplate(w, "Page", data); err != nil { log.Printf("net/trace: Failed executing template: %v", err) } } @@ -902,10 +902,18 @@ return string(b) } -var pageTmpl = template.Must(template.New("Page").Funcs(template.FuncMap{ - "elapsed": elapsed, - "add": func(a, b int) int { return a + b }, -}).Parse(pageHTML)) +var pageTmplCache *template.Template +var pageTmplOnce sync.Once + +func pageTmpl() *template.Template { + pageTmplOnce.Do(func() { + pageTmplCache = template.Must(template.New("Page").Funcs(template.FuncMap{ + "elapsed": elapsed, + "add": func(a, b int) int { return a + b }, + }).Parse(pageHTML)) + }) + return pageTmplCache +} const pageHTML = ` {{template "Prolog" .}} diff -Nru lxd-2.7/dist/src/golang.org/x/net/trace/trace_test.go lxd-2.8/dist/src/golang.org/x/net/trace/trace_test.go --- lxd-2.7/dist/src/golang.org/x/net/trace/trace_test.go 2016-12-21 00:53:01.000000000 +0000 +++ lxd-2.8/dist/src/golang.org/x/net/trace/trace_test.go 2017-01-25 03:19:56.000000000 +0000 @@ -70,6 +70,20 @@ } } +// TestParseTemplate checks that all templates used by this package are valid +// as they are parsed on first usage +func TestParseTemplate(t *testing.T) { + if tmpl := distTmpl(); tmpl == nil { + t.Error("invalid template returned from distTmpl()") + } + if tmpl := pageTmpl(); tmpl == nil { + t.Error("invalid template returned from pageTmpl()") + } + if tmpl := eventsTmpl(); tmpl == nil { + t.Error("invalid template returned from eventsTmpl()") + } +} + func benchmarkTrace(b *testing.B, maxEvents, numEvents int) { numSpans := (b.N + numEvents + 1) / numEvents diff -Nru lxd-2.7/dist/src/gopkg.in/yaml.v2/decode_test.go lxd-2.8/dist/src/gopkg.in/yaml.v2/decode_test.go --- lxd-2.7/dist/src/gopkg.in/yaml.v2/decode_test.go 2016-12-21 00:52:49.000000000 +0000 +++ lxd-2.8/dist/src/gopkg.in/yaml.v2/decode_test.go 2017-01-25 03:19:43.000000000 +0000 @@ -551,7 +551,7 @@ }, { "a: 2015-02-24T18:19:39Z\n", - map[string]time.Time{"a": time.Unix(1424801979, 0)}, + map[string]time.Time{"a": time.Unix(1424801979, 0).In(time.UTC)}, }, // Encode empty lists as zero-length slices. diff -Nru lxd-2.7/doc/cloud-init.md lxd-2.8/doc/cloud-init.md --- lxd-2.7/doc/cloud-init.md 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/doc/cloud-init.md 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,145 @@ +# Custom Network Configuration With cloud-init + +[cloud-init](https://launchpad.net/cloud-init) may be used for custom network configuration of containers. + +Before trying to use it, however, first determine which image source you are +about to use as not all container images have cloud-init package installed. +At the time of writing, images provided at images.linuxcontainers.org do not +have the cloud-init package installed, therefore, any of the configuration +options mentioned in this guide will not work. On the contrary, images +provided at cloud-images.ubuntu.com have the necessary package installed +and also have a templates directory in their archive populated with + * cloud-init-meta.tpl + * cloud-init-user.tpl + * cloud-init-vendor.tpl + * cloud-init-network.tpl + +and others not related to cloud-init. + +Templates provided with container images at cloud-images.ubuntu.com have +the following in their metadata.yaml: + +``` +/var/lib/cloud/seed/nocloud-net/network-config: + when: + - create + - copy + template: cloud-init-network.tpl +``` + +Therefore, either when you create or copy a container it gets a newly rendered +network configuration from a pre-defined template. cloud-init uses the +network-config file to render /etc/network/interfaces.d/50-cloud-init.cfg when +you first start a container. It will not react to any changes if you restart +a container afterwards unless you force it. + +The default behavior is to use a DHCP client on a container's eth0 interface. + +In order to change this you need to define your own network configuration +using user.network-config key in the config dictionary which will override +the default configuration (this is due to how the template is structured). + +The allowed values follow /etc/network/interfaces syntax in case of Ubuntu +images. + +For example, to configure a specific network interface with a static IPv4 +address and also use a custom nameserver use + +``` +config: + user.network-config: | + version: 1 + config: + - type: physical + name: eth1 + subnets: + - type: static + ipv4: true + address: 10.10.101.20 + netmask: 255.255.255.0 + gateway: 10.10.101.1 + control: auto + - type: nameserver + address: 10.10.10.254 +``` + +A container's rootfs will contain the following files as a result: + + * /var/lib/cloud/seed/nocloud-net/network-config + * /etc/network/interfaces.d/50-cloud-init.cfg + +The former will be the same as the value provided in user.network-config, +the latter will be a file in /etc/network/interfaces format converted from +the network-config file by cloud-init (if it is not check syslog for cloud-init +error messages). + + +/etc/network/interfaces.d/50-cloud-init.cfg should then contain + +``` +# This file is generated from information provided by +# the datasource. Changes to it will not persist across an instance. +# To disable cloud-init's network configuration capabilities, write a file +# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: +# network: {config: disabled} +auto lo +iface lo inet loopback + dns-nameservers 10.10.10.254 + +auto eth1 +iface eth1 inet static + address 10.10.101.20 + gateway 10.10.101.1 + netmask 255.255.255.0 +``` + +You will also notice that /run/resolvconf/resolv.conf or /etc/resolv.conf +which is pointing to it will contain the desired dns server after boot-up. + +``` +# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) +# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN +nameserver 10.10.10.254 +``` + +# Implementation Details + +cloud-init allows you to seed instance configuration using the following files +located at /var/lib/cloud/seed/nocloud-net: + * user-data (required) + * meta-data (required) + * vendor-data (optional) + * network-config (optional) + +The network-config file is written to by lxd using data provided in templates +that come with an image. This is governed by metadata.yaml but naming of the +configuration keys and template content is not hard-coded as far as lxd is +concerned - this is purely image data that can be modified if needed. + + * [NoCloud data source documentation](https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html) + * The source code for [NoCloud data source](https://git.launchpad.net/cloud-init/tree/cloudinit/sources/DataSourceNoCloud.py) + * A good reference on which values you can use are [unit tests for cloud-init](https://git.launchpad.net/cloud-init/tree/tests/unittests/test_datasource/test_nocloud.py#n163) + * [cloud-init directory layout](https://cloudinit.readthedocs.io/en/latest/topics/dir_layout.html) + +A default cloud-init-network.tpl provided with images from the "ubuntu:" image +source looks like this: + +``` +{% if config_get("user.network-config", "") == "" %}version: 1 +config: + - type: physical + name: eth0 + subnets: + - type: {% if config_get("user.network_mode", "") == "link-local" %}manual{% else %}dhcp{% endif %} + control: auto{% else %}{{ config_get("user.network-config", "") }}{% endif %} +``` + +The template syntax is the one used in the pongo2 template engine. A custom +config_get function is defined to retrieve values from a container +configuration. + +Options available with such a template structure: + * Use DHCP by default on your eth0 interface; + * Set user.network_mode to "link-local" and configure networking by hand; + * Seed cloud-init by defining user.network-config. + diff -Nru lxd-2.7/lxc/action.go lxd-2.8/lxc/action.go --- lxd-2.7/lxc/action.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/action.go 2017-01-25 02:50:49.000000000 +0000 @@ -5,6 +5,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" ) @@ -76,7 +77,7 @@ } // "start" for a frozen container means "unfreeze" - if current.StatusCode == shared.Frozen { + if current.StatusCode == api.Frozen { c.action = shared.Unfreeze } @@ -91,7 +92,7 @@ return err } - if resp.Type != lxd.Async { + if resp.Type != api.AsyncResponse { return fmt.Errorf(i18n.G("bad result type from action")) } diff -Nru lxd-2.7/lxc/config.go lxd-2.8/lxc/config.go --- lxd-2.7/lxc/config.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/config.go 2017-01-25 02:50:49.000000000 +0000 @@ -15,6 +15,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" "github.com/lxc/lxd/shared/termios" @@ -341,24 +342,27 @@ return err } - brief := config.Brief() + brief := config.Writable() data, err = yaml.Marshal(&brief) + if err != nil { + return err + } } else { - var brief shared.BriefContainerInfo + var brief api.ContainerPut if shared.IsSnapshot(container) { config, err := d.SnapshotInfo(container) if err != nil { return err } - brief = shared.BriefContainerInfo{ + brief = api.ContainerPut{ Profiles: config.Profiles, Config: config.Config, Devices: config.Devices, Ephemeral: config.Ephemeral, } if c.expanded { - brief = shared.BriefContainerInfo{ + brief = api.ContainerPut{ Profiles: config.Profiles, Config: config.ExpandedConfig, Devices: config.ExpandedDevices, @@ -371,9 +375,10 @@ return err } - brief = config.Brief() + brief = config.Writable() if c.expanded { - brief = config.BriefExpanded() + brief.Config = config.ExpandedConfig + brief.Devices = config.ExpandedDevices } } @@ -491,7 +496,7 @@ return err } - newdata := shared.BriefContainerInfo{} + newdata := api.ContainerPut{} err = yaml.Unmarshal(contents, &newdata) if err != nil { return err @@ -505,7 +510,7 @@ return err } - brief := config.Brief() + brief := config.Writable() data, err := yaml.Marshal(&brief) if err != nil { return err @@ -519,7 +524,7 @@ for { // Parse the text received from the editor - newdata := shared.BriefContainerInfo{} + newdata := api.ContainerPut{} err = yaml.Unmarshal(content, &newdata) if err == nil { err = client.UpdateContainerConfig(cont, newdata) @@ -554,7 +559,7 @@ return err } - newdata := shared.BriefServerState{} + newdata := api.ServerPut{} err = yaml.Unmarshal(contents, &newdata) if err != nil { return err @@ -570,7 +575,7 @@ return err } - brief := config.Brief() + brief := config.Writable() data, err := yaml.Marshal(&brief) if err != nil { return err @@ -584,7 +589,7 @@ for { // Parse the text received from the editor - newdata := shared.BriefServerState{} + newdata := api.ServerPut{} err = yaml.Unmarshal(content, &newdata) if err == nil { _, err = client.UpdateServerConfig(newdata) @@ -631,7 +636,7 @@ props = []string{} } - var resp *lxd.Response + var resp *api.Response if which == "profile" { resp, err = client.ProfileDeviceAdd(name, devname, devtype, props) } else { @@ -723,7 +728,7 @@ dev[key] = value st.Devices[devname] = dev - err = client.PutProfile(name, *st) + err = client.PutProfile(name, st.Writable()) if err != nil { return err } @@ -741,7 +746,7 @@ dev[key] = value st.Devices[devname] = dev - err = client.UpdateContainerConfig(name, st.Brief()) + err = client.UpdateContainerConfig(name, st.Writable()) if err != nil { return err } @@ -779,7 +784,7 @@ delete(dev, key) st.Devices[devname] = dev - err = client.PutProfile(name, *st) + err = client.PutProfile(name, st.Writable()) if err != nil { return err } @@ -797,7 +802,7 @@ delete(dev, key) st.Devices[devname] = dev - err = client.UpdateContainerConfig(name, st.Brief()) + err = client.UpdateContainerConfig(name, st.Writable()) if err != nil { return err } @@ -818,7 +823,7 @@ } devname := args[3] - var resp *lxd.Response + var resp *api.Response if which == "profile" { resp, err = client.ProfileDeviceDelete(name, devname) } else { @@ -872,7 +877,7 @@ return err } - var devices map[string]shared.Device + var devices map[string]map[string]string if which == "profile" { resp, err := client.ProfileConfig(name) if err != nil { diff -Nru lxd-2.7/lxc/copy.go lxd-2.8/lxc/copy.go --- lxd-2.7/lxc/copy.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/copy.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,6 +6,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" ) @@ -55,7 +56,7 @@ var status struct { Architecture string - Devices shared.Devices + Devices map[string]map[string]string Config map[string]string Profiles []string } @@ -189,7 +190,7 @@ return err } - for k, v := range *op.Metadata { + for k, v := range op.Metadata { secrets[k] = v.(string) } @@ -207,7 +208,7 @@ * report that. */ for _, addr := range addresses { - var migration *lxd.Response + var migration *api.Response sourceWSUrl := "https://" + addr + sourceWSResponse.Operation migration, err = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1, false, source, sourceWSResponse.Operation) diff -Nru lxd-2.7/lxc/delete.go lxd-2.8/lxc/delete.go --- lxd-2.7/lxc/delete.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/delete.go 2017-01-25 02:50:49.000000000 +0000 @@ -8,6 +8,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" ) @@ -87,7 +88,7 @@ return err } - if ct.StatusCode != 0 && ct.StatusCode != shared.Stopped { + if ct.StatusCode != 0 && ct.StatusCode != api.Stopped { if !c.force { return fmt.Errorf(i18n.G("The container is currently running, stop it first or pass --force.")) } @@ -102,7 +103,7 @@ return err } - if op.StatusCode == shared.Failure { + if op.StatusCode == api.Failure { return fmt.Errorf(i18n.G("Stopping container failed!")) } diff -Nru lxd-2.7/lxc/exec.go lxd-2.8/lxc/exec.go --- lxd-2.7/lxc/exec.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/exec.go 2017-01-25 02:50:49.000000000 +0000 @@ -12,6 +12,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" "github.com/lxc/lxd/shared/termios" @@ -68,7 +69,7 @@ return err } - msg := shared.ContainerExecControl{} + msg := api.ContainerExecControl{} msg.Command = "window-resize" msg.Args = make(map[string]string) msg.Args["width"] = strconv.Itoa(width) @@ -92,9 +93,9 @@ return err } - msg := shared.ContainerExecControl{} + msg := api.ContainerExecControl{} msg.Command = "signal" - msg.Signal = sig + msg.Signal = int(sig) buf, err := json.Marshal(msg) if err != nil { diff -Nru lxd-2.7/lxc/file.go lxd-2.8/lxc/file.go --- lxd-2.7/lxc/file.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/file.go 2017-01-25 02:50:49.000000000 +0000 @@ -120,7 +120,18 @@ } if c.mkdirs { - if err := d.MkdirP(container, targetPath, mode); err != nil { + f, err := os.Open(args[0]) + if err != nil { + return err + } + finfo, err := f.Stat() + f.Close() + if err != nil { + return err + } + + mode, uid, gid := shared.GetOwnerMode(finfo) + if err := d.MkdirP(container, targetPath, mode, uid, gid); err != nil { return err } } @@ -173,14 +184,39 @@ } if c.mkdirs { - if err := d.MkdirP(container, path.Dir(fpath), mode); err != nil { + finfo, err := f.Stat() + if err != nil { + return err + } + + if c.mode == "" || c.uid == -1 || c.gid == -1 { + dMode, dUid, dGid := shared.GetOwnerMode(finfo) + if c.mode == "" { + mode = dMode + } + + if c.uid == -1 { + uid = dUid + } + + if c.gid == -1 { + gid = dGid + } + } + + if err := d.MkdirP(container, path.Dir(fpath), mode, uid, gid); err != nil { return err } } if send_file_perms { if c.mode == "" || c.uid == -1 || c.gid == -1 { - fMode, fUid, fGid, err := c.getOwner(f) + finfo, err := f.Stat() + if err != nil { + return err + } + + fMode, fUid, fGid := shared.GetOwnerMode(finfo) if err != nil { return err } diff -Nru lxd-2.7/lxc/file_unix.go lxd-2.8/lxc/file_unix.go --- lxd-2.7/lxc/file_unix.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/file_unix.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -// +build !windows - -package main - -import ( - "os" - "syscall" -) - -func (c *fileCmd) getOwner(f *os.File) (os.FileMode, int, int, error) { - fInfo, err := f.Stat() - if err != nil { - return os.FileMode(0), -1, -1, err - } - - mode := fInfo.Mode() - uid := int(fInfo.Sys().(*syscall.Stat_t).Uid) - gid := int(fInfo.Sys().(*syscall.Stat_t).Gid) - - return mode, uid, gid, nil -} diff -Nru lxd-2.7/lxc/file_windows.go lxd-2.8/lxc/file_windows.go --- lxd-2.7/lxc/file_windows.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/file_windows.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -// +build windows - -package main - -import ( - "os" -) - -func (c *fileCmd) getOwner(f *os.File) (os.FileMode, int, int, error) { - return os.FileMode(0), -1, -1, nil -} diff -Nru lxd-2.7/lxc/image.go lxd-2.8/lxc/image.go --- lxd-2.7/lxc/image.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/image.go 2017-01-25 02:50:49.000000000 +0000 @@ -15,6 +15,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" "github.com/lxc/lxd/shared/termios" @@ -346,15 +347,20 @@ fmt.Printf(i18n.G("Public: %s")+"\n", public) fmt.Printf(i18n.G("Timestamps:") + "\n") const layout = "2006/01/02 15:04 UTC" - if info.CreationDate.UTC().Unix() != 0 { - fmt.Printf(" "+i18n.G("Created: %s")+"\n", info.CreationDate.UTC().Format(layout)) + if shared.TimeIsSet(info.CreatedAt) { + fmt.Printf(" "+i18n.G("Created: %s")+"\n", info.CreatedAt.UTC().Format(layout)) } - fmt.Printf(" "+i18n.G("Uploaded: %s")+"\n", info.UploadDate.UTC().Format(layout)) - if info.ExpiryDate.UTC().Unix() != 0 { - fmt.Printf(" "+i18n.G("Expires: %s")+"\n", info.ExpiryDate.UTC().Format(layout)) + fmt.Printf(" "+i18n.G("Uploaded: %s")+"\n", info.UploadedAt.UTC().Format(layout)) + if shared.TimeIsSet(info.ExpiresAt) { + fmt.Printf(" "+i18n.G("Expires: %s")+"\n", info.ExpiresAt.UTC().Format(layout)) } else { fmt.Printf(" " + i18n.G("Expires: never") + "\n") } + if shared.TimeIsSet(info.LastUsedAt) { + fmt.Printf(" "+i18n.G("Last used: %s")+"\n", info.LastUsedAt.UTC().Format(layout)) + } else { + fmt.Printf(" " + i18n.G("Last used: never") + "\n") + } fmt.Println(i18n.G("Properties:")) for key, value := range info.Properties { fmt.Printf(" %s: %s\n", key, value) @@ -364,11 +370,11 @@ fmt.Printf(" - %s\n", alias.Name) } fmt.Printf(i18n.G("Auto update: %s")+"\n", autoUpdate) - if info.Source != nil { + if info.UpdateSource != nil { fmt.Println(i18n.G("Source:")) - fmt.Printf(" Server: %s\n", info.Source.Server) - fmt.Printf(" Protocol: %s\n", info.Source.Protocol) - fmt.Printf(" Alias: %s\n", info.Source.Alias) + fmt.Printf(" Server: %s\n", info.UpdateSource.Server) + fmt.Printf(" Protocol: %s\n", info.UpdateSource.Protocol) + fmt.Printf(" Alias: %s\n", info.UpdateSource.Alias) } return nil @@ -425,7 +431,7 @@ } else { progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")} handler := func(percent int64, speed int64) { - progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed))) + progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed, 2))) } fingerprint, err = d.PostImage(imageFile, rootfsFile, properties, c.publicImage, c.addAliases, handler) @@ -466,7 +472,7 @@ return err } - var images []shared.ImageInfo + var images []api.Image allImages, err := d.ListImages() if err != nil { return err @@ -557,7 +563,7 @@ return err } - properties := info.Brief() + properties := info.Writable() data, err := yaml.Marshal(&properties) fmt.Printf("%s", data) @@ -576,7 +582,7 @@ return result } -func (c *imageCmd) shortestAlias(list []shared.ImageAlias) string { +func (c *imageCmd) shortestAlias(list []api.ImageAlias) string { shortest := "" for _, l := range list { if shortest == "" { @@ -600,7 +606,7 @@ return "" } -func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error { +func (c *imageCmd) showImages(images []api.Image, filters []string) error { switch c.format { case listFormatTable: data := [][]string{} @@ -622,7 +628,7 @@ } const layout = "Jan 2, 2006 at 3:04pm (MST)" - uploaded := image.UploadDate.UTC().Format(layout) + uploaded := image.UploadedAt.UTC().Format(layout) size := fmt.Sprintf("%.2fMB", float64(image.Size)/1024.0/1024.0) data = append(data, []string{shortest, fp, public, description, image.Architecture, size, uploaded}) } @@ -643,7 +649,7 @@ table.AppendBulk(data) table.Render() case listFormatJSON: - data := make([]*shared.ImageInfo, len(images)) + data := make([]*api.Image, len(images)) for i := range images { data[i] = &images[i] } @@ -659,7 +665,7 @@ return nil } -func (c *imageCmd) showAliases(aliases shared.ImageAliases, filters []string) error { +func (c *imageCmd) showAliases(aliases []api.ImageAliasesEntry, filters []string) error { data := [][]string{} for _, alias := range aliases { if !c.aliasShouldShow(filters, &alias) { @@ -692,7 +698,7 @@ return err } - newdata := shared.BriefImageInfo{} + newdata := api.ImagePut{} err = yaml.Unmarshal(contents, &newdata) if err != nil { return err @@ -706,7 +712,7 @@ return err } - brief := config.Brief() + brief := config.Writable() data, err := yaml.Marshal(&brief) if err != nil { return err @@ -720,7 +726,7 @@ for { // Parse the text received from the editor - newdata := shared.BriefImageInfo{} + newdata := api.ImagePut{} err = yaml.Unmarshal(content, &newdata) if err == nil { err = client.PutImageInfo(image, newdata) @@ -747,7 +753,7 @@ return nil } -func (c *imageCmd) imageShouldShow(filters []string, state *shared.ImageInfo) bool { +func (c *imageCmd) imageShouldShow(filters []string, state *api.Image) bool { if len(filters) == 0 { return true } @@ -806,7 +812,7 @@ return true } -func (c *imageCmd) aliasShouldShow(filters []string, state *shared.ImageAliasesEntry) bool { +func (c *imageCmd) aliasShouldShow(filters []string, state *api.ImageAliasesEntry) bool { if len(filters) == 0 { return true } diff -Nru lxd-2.7/lxc/info.go lxd-2.8/lxc/info.go --- lxd-2.7/lxc/info.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/info.go 2017-01-25 02:50:49.000000000 +0000 @@ -91,8 +91,8 @@ fmt.Printf(i18n.G("Remote: %s")+"\n", d.Remote.Addr) } fmt.Printf(i18n.G("Architecture: %s")+"\n", ct.Architecture) - if ct.CreationDate.UTC().Unix() != 0 { - fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreationDate.UTC().Format(layout)) + if shared.TimeIsSet(ct.CreatedAt) { + fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreatedAt.UTC().Format(layout)) } fmt.Printf(i18n.G("Status: %s")+"\n", ct.Status) @@ -134,7 +134,7 @@ if cs.Disk != nil { for entry, disk := range cs.Disk { if disk.Usage != 0 { - diskInfo += fmt.Sprintf(" %s: %s\n", entry, shared.GetByteSizeString(disk.Usage)) + diskInfo += fmt.Sprintf(" %s: %s\n", entry, shared.GetByteSizeString(disk.Usage, 2)) } } } @@ -158,19 +158,19 @@ // Memory usage memoryInfo := "" if cs.Memory.Usage != 0 { - memoryInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Memory (current)"), shared.GetByteSizeString(cs.Memory.Usage)) + memoryInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Memory (current)"), shared.GetByteSizeString(cs.Memory.Usage, 2)) } if cs.Memory.UsagePeak != 0 { - memoryInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Memory (peak)"), shared.GetByteSizeString(cs.Memory.UsagePeak)) + memoryInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Memory (peak)"), shared.GetByteSizeString(cs.Memory.UsagePeak, 2)) } if cs.Memory.SwapUsage != 0 { - memoryInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Swap (current)"), shared.GetByteSizeString(cs.Memory.SwapUsage)) + memoryInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Swap (current)"), shared.GetByteSizeString(cs.Memory.SwapUsage, 2)) } if cs.Memory.SwapUsagePeak != 0 { - memoryInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Swap (peak)"), shared.GetByteSizeString(cs.Memory.SwapUsagePeak)) + memoryInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Swap (peak)"), shared.GetByteSizeString(cs.Memory.SwapUsagePeak, 2)) } if memoryInfo != "" { @@ -183,8 +183,8 @@ if cs.Network != nil { for netName, net := range cs.Network { networkInfo += fmt.Sprintf(" %s:\n", netName) - networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Bytes received"), shared.GetByteSizeString(net.Counters.BytesReceived)) - networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Bytes sent"), shared.GetByteSizeString(net.Counters.BytesSent)) + networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Bytes received"), shared.GetByteSizeString(net.Counters.BytesReceived, 2)) + networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Bytes sent"), shared.GetByteSizeString(net.Counters.BytesSent, 2)) networkInfo += fmt.Sprintf(" %s: %d\n", i18n.G("Packets received"), net.Counters.PacketsReceived) networkInfo += fmt.Sprintf(" %s: %d\n", i18n.G("Packets sent"), net.Counters.PacketsSent) } @@ -211,7 +211,7 @@ fields := strings.Split(snap.Name, shared.SnapshotDelimiter) fmt.Printf(" %s", fields[len(fields)-1]) - if snap.CreationDate.UTC().Unix() != 0 { + if shared.TimeIsSet(snap.CreationDate) { fmt.Printf(" ("+i18n.G("taken at %s")+")", snap.CreationDate.UTC().Format(layout)) } diff -Nru lxd-2.7/lxc/init.go lxd-2.8/lxc/init.go --- lxd-2.7/lxc/init.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/init.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,7 +6,7 @@ "strings" "github.com/lxc/lxd" - "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" ) @@ -126,6 +126,7 @@ initRequestedEmptyProfiles = true newargs := os.Args[0 : l-2] newargs = append(newargs, os.Args[l-1]) + os.Args = newargs return } } @@ -173,7 +174,7 @@ profiles = append(profiles, p) } - var resp *lxd.Response + var resp *api.Response if name == "" { fmt.Printf(i18n.G("Creating the container") + "\n") } else { @@ -182,7 +183,7 @@ iremote, image = c.guessImage(config, d, remote, iremote, image) - devicesMap := map[string]shared.Device{} + devicesMap := map[string]map[string]string{} if c.network != "" { network, err := d.NetworkGet(c.network) if err != nil { @@ -190,9 +191,9 @@ } if network.Type == "bridge" { - devicesMap[c.network] = shared.Device{"type": "nic", "nictype": "bridged", "parent": c.network} + devicesMap[c.network] = map[string]string{"type": "nic", "nictype": "bridged", "parent": c.network} } else { - devicesMap[c.network] = shared.Device{"type": "nic", "nictype": "macvlan", "parent": c.network} + devicesMap[c.network] = map[string]string{"type": "nic", "nictype": "macvlan", "parent": c.network} } } @@ -260,7 +261,7 @@ return } - if shared.StatusCode(md["status_code"].(float64)).IsFinal() { + if api.StatusCode(md["status_code"].(float64)).IsFinal() { return } @@ -270,7 +271,7 @@ progress.Update(opMd["download_progress"].(string)) } } - go d.Monitor([]string{"operation"}, handler) + go d.Monitor([]string{"operation"}, handler, nil) } func (c *initCmd) guessImage(config *lxd.Config, d *lxd.Client, remote string, iremote string, image string) (string, string) { diff -Nru lxd-2.7/lxc/launch.go lxd-2.8/lxc/launch.go --- lxd-2.7/lxc/launch.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/launch.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,6 +6,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/i18n" "github.com/lxc/lxd/shared/version" ) @@ -62,7 +63,7 @@ * initRequestedEmptyProfiles means user requested empty * !initRequestedEmptyProfiles but len(profArgs) == 0 means use profile default */ - var resp *lxd.Response + var resp *api.Response profiles := []string{} for _, p := range c.init.profArgs { profiles = append(profiles, p) @@ -70,7 +71,7 @@ iremote, image = c.init.guessImage(config, d, remote, iremote, image) - devicesMap := map[string]shared.Device{} + devicesMap := map[string]map[string]string{} if c.init.network != "" { network, err := d.NetworkGet(c.init.network) if err != nil { @@ -78,9 +79,9 @@ } if network.Type == "bridge" { - devicesMap[c.init.network] = shared.Device{"type": "nic", "nictype": "bridged", "parent": c.init.network} + devicesMap[c.init.network] = map[string]string{"type": "nic", "nictype": "bridged", "parent": c.init.network} } else { - devicesMap[c.init.network] = shared.Device{"type": "nic", "nictype": "macvlan", "parent": c.init.network} + devicesMap[c.init.network] = map[string]string{"type": "nic", "nictype": "macvlan", "parent": c.init.network} } } diff -Nru lxd-2.7/lxc/list.go lxd-2.8/lxc/list.go --- lxd-2.7/lxc/list.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/list.go 2017-01-25 02:50:49.000000000 +0000 @@ -14,6 +14,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" ) @@ -25,7 +26,7 @@ NeedsSnapshots bool } -type columnData func(shared.ContainerInfo, *shared.ContainerState, []shared.SnapshotInfo) string +type columnData func(api.Container, *api.ContainerState, []api.ContainerSnapshot) string type byName [][]string @@ -133,7 +134,7 @@ return false } - for i, _ := range fullMembs { + for i := range fullMembs { if !strings.HasPrefix(fullMembs[i], shortMembs[i]) { return false } @@ -142,7 +143,7 @@ return true } -func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool { +func (c *listCmd) shouldShow(filters []string, state *api.Container) bool { for _, filter := range filters { if strings.Contains(filter, "=") { membs := strings.SplitN(filter, "=", 2) @@ -207,7 +208,7 @@ return true } -func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, filters []string, columns []column) error { +func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters []string, columns []column) error { headers := []string{} for _, column := range columns { headers = append(headers, column.Name) @@ -218,12 +219,12 @@ threads = len(cinfos) } - cStates := map[string]*shared.ContainerState{} + cStates := map[string]*api.ContainerState{} cStatesLock := sync.Mutex{} cStatesQueue := make(chan string, threads) cStatesWg := sync.WaitGroup{} - cSnapshots := map[string][]shared.SnapshotInfo{} + cSnapshots := map[string][]api.ContainerSnapshot{} cSnapshotsLock := sync.Mutex{} cSnapshotsQueue := make(chan string, threads) cSnapshotsWg := sync.WaitGroup{} @@ -347,7 +348,7 @@ case listFormatJSON: data := make([]listContainerItem, len(cinfos)) for i := range cinfos { - data[i].ContainerInfo = &cinfos[i] + data[i].Container = &cinfos[i] data[i].State = cStates[cinfos[i].Name] data[i].Snapshots = cSnapshots[cinfos[i].Name] } @@ -364,9 +365,10 @@ } type listContainerItem struct { - *shared.ContainerInfo - State *shared.ContainerState `json:"state"` - Snapshots []shared.SnapshotInfo `json:"snapshots"` + *api.Container + + State *api.ContainerState + Snapshots []api.ContainerSnapshot } func (c *listCmd) run(config *lxd.Config, args []string) error { @@ -396,7 +398,7 @@ return err } - var cts []shared.ContainerInfo + var cts []api.Container ctslist, err := d.ListContainers() if err != nil { return err @@ -420,17 +422,17 @@ func (c *listCmd) parseColumns() ([]column, error) { columnsShorthandMap := map[rune]column{ - '4': column{i18n.G("IPV4"), c.IP4ColumnData, true, false}, - '6': column{i18n.G("IPV6"), c.IP6ColumnData, true, false}, - 'a': column{i18n.G("ARCHITECTURE"), c.ArchitectureColumnData, false, false}, - 'c': column{i18n.G("CREATED AT"), c.CreatedColumnData, false, false}, - 'l': column{i18n.G("LAST USED AT"), c.LastUsedColumnData, false, false}, - 'n': column{i18n.G("NAME"), c.nameColumnData, false, false}, - 'p': column{i18n.G("PID"), c.PIDColumnData, true, false}, - 'P': column{i18n.G("PROFILES"), c.ProfilesColumnData, false, false}, - 'S': column{i18n.G("SNAPSHOTS"), c.numberSnapshotsColumnData, false, true}, - 's': column{i18n.G("STATE"), c.statusColumnData, false, false}, - 't': column{i18n.G("TYPE"), c.typeColumnData, false, false}, + '4': {i18n.G("IPV4"), c.IP4ColumnData, true, false}, + '6': {i18n.G("IPV6"), c.IP6ColumnData, true, false}, + 'a': {i18n.G("ARCHITECTURE"), c.ArchitectureColumnData, false, false}, + 'c': {i18n.G("CREATED AT"), c.CreatedColumnData, false, false}, + 'l': {i18n.G("LAST USED AT"), c.LastUsedColumnData, false, false}, + 'n': {i18n.G("NAME"), c.nameColumnData, false, false}, + 'p': {i18n.G("PID"), c.PIDColumnData, true, false}, + 'P': {i18n.G("PROFILES"), c.ProfilesColumnData, false, false}, + 'S': {i18n.G("SNAPSHOTS"), c.numberSnapshotsColumnData, false, true}, + 's': {i18n.G("STATE"), c.statusColumnData, false, false}, + 't': {i18n.G("TYPE"), c.typeColumnData, false, false}, } if c.fast { @@ -490,10 +492,10 @@ } } - column.Data = func(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { + column.Data = func(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { v, ok := cInfo.Config[k] if !ok { - v, ok = cInfo.ExpandedConfig[k] + v, _ = cInfo.ExpandedConfig[k] } // Truncate the data according to the max width. A negative max width @@ -509,15 +511,15 @@ return columns, nil } -func (c *listCmd) nameColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) nameColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { return cInfo.Name } -func (c *listCmd) statusColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) statusColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { return strings.ToUpper(cInfo.Status) } -func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) IP4ColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { if cInfo.IsActive() && cState != nil && cState.Network != nil { ipv4s := []string{} for netName, net := range cState.Network { @@ -535,13 +537,14 @@ } } } + sort.Sort(sort.Reverse(sort.StringSlice(ipv4s))) return strings.Join(ipv4s, "\n") } else { return "" } } -func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) IP6ColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { if cInfo.IsActive() && cState != nil && cState.Network != nil { ipv6s := []string{} for netName, net := range cState.Network { @@ -559,13 +562,14 @@ } } } + sort.Sort(sort.Reverse(sort.StringSlice(ipv6s))) return strings.Join(ipv6s, "\n") } else { return "" } } -func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) typeColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { if cInfo.Ephemeral { return i18n.G("EPHEMERAL") } else { @@ -573,7 +577,7 @@ } } -func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) numberSnapshotsColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { if cSnaps != nil { return fmt.Sprintf("%d", len(cSnaps)) } @@ -581,7 +585,7 @@ return "" } -func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) PIDColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { if cInfo.IsActive() && cState != nil { return fmt.Sprintf("%d", cState.Pid) } @@ -589,29 +593,29 @@ return "" } -func (c *listCmd) ArchitectureColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) ArchitectureColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { return cInfo.Architecture } -func (c *listCmd) ProfilesColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) ProfilesColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { return strings.Join(cInfo.Profiles, "\n") } -func (c *listCmd) CreatedColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) CreatedColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { layout := "2006/01/02 15:04 UTC" - if cInfo.CreationDate.UTC().Unix() != 0 { - return cInfo.CreationDate.UTC().Format(layout) + if shared.TimeIsSet(cInfo.CreatedAt) { + return cInfo.CreatedAt.UTC().Format(layout) } return "" } -func (c *listCmd) LastUsedColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string { +func (c *listCmd) LastUsedColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string { layout := "2006/01/02 15:04 UTC" - if !cInfo.LastUsedDate.IsZero() && cInfo.LastUsedDate.UTC().Unix() != 0 { - return cInfo.LastUsedDate.UTC().Format(layout) + if !cInfo.LastUsedAt.IsZero() && shared.TimeIsSet(cInfo.LastUsedAt) { + return cInfo.LastUsedAt.UTC().Format(layout) } return "" diff -Nru lxd-2.7/lxc/list_test.go lxd-2.8/lxc/list_test.go --- lxd-2.7/lxc/list_test.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/list_test.go 2017-01-25 02:50:49.000000000 +0000 @@ -8,6 +8,7 @@ "testing" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" ) func TestDotPrefixMatch(t *testing.T) { @@ -25,7 +26,7 @@ func TestShouldShow(t *testing.T) { list := listCmd{} - state := &shared.ContainerInfo{ + state := &api.Container{ Name: "foo", ExpandedConfig: map[string]string{ "security.privileged": "1", diff -Nru lxd-2.7/lxc/main_test.go lxd-2.8/lxc/main_test.go --- lxd-2.7/lxc/main_test.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/main_test.go 2017-01-25 02:50:49.000000000 +0000 @@ -40,15 +40,15 @@ } testcases := []aliasTestcase{ - aliasTestcase{ + { input: []string{"lxc", "list"}, expected: []string{"lxc", "list"}, }, - aliasTestcase{ + { input: []string{"lxc", "tester", "12"}, expected: []string{"lxc", "list", "--no-alias"}, }, - aliasTestcase{ + { input: []string{"lxc", "foo", "asdf"}, expected: []string{"lxc", "list", "--no-alias", "asdf", "-c", "n"}, }, diff -Nru lxd-2.7/lxc/manpage.go lxd-2.8/lxc/manpage.go --- lxd-2.7/lxc/manpage.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/manpage.go 2017-01-25 02:50:49.000000000 +0000 @@ -28,7 +28,7 @@ } keys := []string{} - for k, _ := range commands { + for k := range commands { keys = append(keys, k) } sort.Strings(keys) diff -Nru lxd-2.7/lxc/monitor.go lxd-2.8/lxc/monitor.go --- lxd-2.7/lxc/monitor.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/monitor.go 2017-01-25 02:50:49.000000000 +0000 @@ -84,5 +84,5 @@ fmt.Printf("%s\n\n", render) } - return d.Monitor(c.typeArgs, handler) + return d.Monitor(c.typeArgs, handler, nil) } diff -Nru lxd-2.7/lxc/network.go lxd-2.8/lxc/network.go --- lxd-2.7/lxc/network.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/network.go 2017-01-25 02:50:49.000000000 +0000 @@ -13,6 +13,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/i18n" "github.com/lxc/lxd/shared/termios" ) @@ -303,7 +304,7 @@ return err } - newdata := shared.NetworkConfig{} + newdata := api.NetworkPut{} err = yaml.Unmarshal(contents, &newdata) if err != nil { return err @@ -317,6 +318,10 @@ return err } + if !network.Managed { + return fmt.Errorf(i18n.G("Only managed networks can be modified.")) + } + data, err := yaml.Marshal(&network) if err != nil { return err @@ -330,7 +335,7 @@ for { // Parse the text received from the editor - newdata := shared.NetworkConfig{} + newdata := api.NetworkPut{} err = yaml.Unmarshal(content, &newdata) if err == nil { err = client.NetworkPut(name, newdata) @@ -440,6 +445,10 @@ return err } + if !network.Managed { + return fmt.Errorf(i18n.G("Only managed networks can be modified.")) + } + key := args[0] var value string if len(args) < 2 { @@ -451,14 +460,14 @@ if !termios.IsTerminal(int(syscall.Stdin)) && value == "-" { buf, err := ioutil.ReadAll(os.Stdin) if err != nil { - return fmt.Errorf("Can't read from stdin: %s", err) + return fmt.Errorf(i18n.G("Can't read from stdin: %s"), err) } value = string(buf[:]) } network.Config[key] = value - return client.NetworkPut(name, network) + return client.NetworkPut(name, network.Writable()) } func (c *networkCmd) doNetworkShow(client *lxd.Client, name string) error { @@ -467,7 +476,13 @@ return err } + sort.Strings(network.UsedBy) + data, err := yaml.Marshal(&network) + if err != nil { + return err + } + fmt.Printf("%s", data) return nil diff -Nru lxd-2.7/lxc/profile.go lxd-2.8/lxc/profile.go --- lxd-2.7/lxc/profile.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/profile.go 2017-01-25 02:50:49.000000000 +0000 @@ -12,6 +12,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/i18n" "github.com/lxc/lxd/shared/termios" ) @@ -179,7 +180,7 @@ return err } - newdata := shared.ProfileConfig{} + newdata := api.ProfilePut{} err = yaml.Unmarshal(contents, &newdata) if err != nil { return err @@ -206,7 +207,7 @@ for { // Parse the text received from the editor - newdata := shared.ProfileConfig{} + newdata := api.ProfilePut{} err = yaml.Unmarshal(content, &newdata) if err == nil { err = client.PutProfile(p, newdata) @@ -266,7 +267,7 @@ ct.Profiles = append(ct.Profiles, p) - err = client.UpdateContainerConfig(d, ct.Brief()) + err = client.UpdateContainerConfig(d, ct.Writable()) if err != nil { return err } @@ -297,7 +298,7 @@ ct.Profiles = profiles - err = client.UpdateContainerConfig(d, ct.Brief()) + err = client.UpdateContainerConfig(d, ct.Writable()) if err != nil { return err } @@ -314,6 +315,10 @@ } data, err := yaml.Marshal(&profile) + if err != nil { + return err + } + fmt.Printf("%s", data) return nil diff -Nru lxd-2.7/lxc/publish.go lxd-2.8/lxc/publish.go --- lxd-2.7/lxc/publish.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/publish.go 2017-01-25 02:50:49.000000000 +0000 @@ -5,6 +5,7 @@ "strings" "github.com/lxc/lxd" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" "github.com/lxc/lxd/shared/i18n" @@ -83,7 +84,7 @@ return err } - wasRunning := ct.StatusCode != 0 && ct.StatusCode != shared.Stopped + wasRunning := ct.StatusCode != 0 && ct.StatusCode != api.Stopped wasEphemeral := ct.Ephemeral if wasRunning { @@ -93,7 +94,7 @@ if ct.Ephemeral { ct.Ephemeral = false - err := s.UpdateContainerConfig(cName, ct.Brief()) + err := s.UpdateContainerConfig(cName, ct.Writable()) if err != nil { return err } @@ -109,14 +110,14 @@ return err } - if op.StatusCode == shared.Failure { + if op.StatusCode == api.Failure { return fmt.Errorf(i18n.G("Stopping container failed!")) } defer s.Action(cName, shared.Start, -1, true, false) if wasEphemeral { ct.Ephemeral = true - err := s.UpdateContainerConfig(cName, ct.Brief()) + err := s.UpdateContainerConfig(cName, ct.Writable()) if err != nil { return err } diff -Nru lxd-2.7/lxc/remote.go lxd-2.8/lxc/remote.go --- lxd-2.7/lxc/remote.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxc/remote.go 2017-01-25 02:50:49.000000000 +0000 @@ -38,11 +38,11 @@ `Manage remote LXD servers. lxc remote add [] [--accept-certificate] [--password=PASSWORD] - [--public] [--protocol=PROTOCOL] Add the remote at . -lxc remote remove Remove the remote . + [--public] [--protocol=PROTOCOL] Add the remote at . +lxc remote remove Remove the remote . lxc remote list List all remotes. -lxc remote rename Rename remote to . -lxc remote set-url Update 's url to . +lxc remote rename Rename remote to . +lxc remote set-url Update 's url to . lxc remote set-default Set the default remote. lxc remote get-default Print the default remote.`) } diff -Nru lxd-2.7/lxd/api_1.0.go lxd-2.8/lxd/api_1.0.go --- lxd-2.7/lxd/api_1.0.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/api_1.0.go 2017-01-25 02:50:49.000000000 +0000 @@ -11,6 +11,7 @@ "gopkg.in/lxc/go-lxc.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/osarch" "github.com/lxc/lxd/shared/version" ) @@ -46,7 +47,7 @@ } func api10Get(d *Daemon, r *http.Request) Response { - body := shared.Jmap{ + srv := api.ServerUntrusted{ /* List of API extensions in the order they were added. * * The following kind of changes require an addition to api_extensions: @@ -56,7 +57,7 @@ * - New argument inside an existing REST API call * - New HTTPs authentication mechanisms or protocols */ - "api_extensions": []string{ + APIExtensions: []string{ "storage_zfs_remove_snapshots", "container_host_shutdown_timeout", "container_syscall_filtering", @@ -84,98 +85,95 @@ "network_firewall_filtering", "network_routes", }, - - "api_status": "stable", - "api_version": version.APIVersion, + APIStatus: "stable", + APIVersion: version.APIVersion, + Public: false, + Auth: "untrusted", + } + + // If untrusted, return now + if !d.isTrustedClient(r) { + return SyncResponseETag(true, srv, nil) + } + + srv.Auth = "trusted" + + /* + * Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8 + * there is really no better way to do this, which is + * unfortunate. Also, we ditch the more accepted CharsToString + * version in that thread, since it doesn't seem as portable, + * viz. github issue #206. + */ + uname := syscall.Utsname{} + if err := syscall.Uname(&uname); err != nil { + return InternalError(err) } - if d.isTrustedClient(r) { - body["auth"] = "trusted" - - /* - * Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8 - * there is really no better way to do this, which is - * unfortunate. Also, we ditch the more accepted CharsToString - * version in that thread, since it doesn't seem as portable, - * viz. github issue #206. - */ - uname := syscall.Utsname{} - if err := syscall.Uname(&uname); err != nil { - return InternalError(err) - } - - kernel := "" - for _, c := range uname.Sysname { - if c == 0 { - break - } - kernel += string(byte(c)) + kernel := "" + for _, c := range uname.Sysname { + if c == 0 { + break } + kernel += string(byte(c)) + } - kernelVersion := "" - for _, c := range uname.Release { - if c == 0 { - break - } - kernelVersion += string(byte(c)) + kernelVersion := "" + for _, c := range uname.Release { + if c == 0 { + break } + kernelVersion += string(byte(c)) + } - kernelArchitecture := "" - for _, c := range uname.Machine { - if c == 0 { - break - } - kernelArchitecture += string(byte(c)) + kernelArchitecture := "" + for _, c := range uname.Machine { + if c == 0 { + break } + kernelArchitecture += string(byte(c)) + } - addresses, err := d.ListenAddresses() - if err != nil { - return InternalError(err) - } + addresses, err := d.ListenAddresses() + if err != nil { + return InternalError(err) + } - var certificate string - if len(d.tlsConfig.Certificates) != 0 { - certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: d.tlsConfig.Certificates[0].Certificate[0]})) - } + var certificate string + if len(d.tlsConfig.Certificates) != 0 { + certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: d.tlsConfig.Certificates[0].Certificate[0]})) + } - architectures := []string{} + architectures := []string{} - for _, architecture := range d.architectures { - architectureName, err := osarch.ArchitectureName(architecture) - if err != nil { - return InternalError(err) - } - architectures = append(architectures, architectureName) + for _, architecture := range d.architectures { + architectureName, err := osarch.ArchitectureName(architecture) + if err != nil { + return InternalError(err) } - - env := shared.Jmap{ - "addresses": addresses, - "architectures": architectures, - "certificate": certificate, - "driver": "lxc", - "driver_version": lxc.Version(), - "kernel": kernel, - "kernel_architecture": kernelArchitecture, - "kernel_version": kernelVersion, - "storage": d.Storage.GetStorageTypeName(), - "storage_version": d.Storage.GetStorageTypeVersion(), - "server": "lxd", - "server_pid": os.Getpid(), - "server_version": version.Version} - - body["environment"] = env - body["public"] = false - body["config"] = daemonConfigRender() - } else { - body["auth"] = "untrusted" - body["public"] = false + architectures = append(architectures, architectureName) } - return SyncResponseETag(true, body, body["config"]) -} + env := api.ServerEnvironment{ + Addresses: addresses, + Architectures: architectures, + Certificate: certificate, + Driver: "lxc", + DriverVersion: lxc.Version(), + Kernel: kernel, + KernelArchitecture: kernelArchitecture, + KernelVersion: kernelVersion, + Storage: d.Storage.GetStorageTypeName(), + StorageVersion: d.Storage.GetStorageTypeVersion(), + Server: "lxd", + ServerPid: os.Getpid(), + ServerVersion: version.Version} + + fullSrv := api.Server{ServerUntrusted: srv} + fullSrv.Environment = env + fullSrv.Config = daemonConfigRender() -type apiPut struct { - Config shared.Jmap `json:"config"` + return SyncResponseETag(true, fullSrv, fullSrv.Config) } func api10Put(d *Daemon, r *http.Request) Response { @@ -189,7 +187,7 @@ return PreconditionFailed(err) } - req := apiPut{} + req := api.ServerPut{} if err := shared.ReadToJSON(r.Body, &req); err != nil { return BadRequest(err) } @@ -208,7 +206,7 @@ return PreconditionFailed(err) } - req := apiPut{} + req := api.ServerPut{} if err := shared.ReadToJSON(r.Body, &req); err != nil { return BadRequest(err) } @@ -227,7 +225,7 @@ return doApi10Update(d, oldConfig, req) } -func doApi10Update(d *Daemon, oldConfig map[string]string, req apiPut) Response { +func doApi10Update(d *Daemon, oldConfig map[string]string, req api.ServerPut) Response { // Deal with special keys for k, v := range req.Config { config := daemonConfig[k] diff -Nru lxd-2.7/lxd/api_internal.go lxd-2.8/lxd/api_internal.go --- lxd-2.7/lxd/api_internal.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/api_internal.go 2017-01-25 02:50:49.000000000 +0000 @@ -150,8 +150,8 @@ Architecture: arch, BaseImage: baseImage, Config: sf.Container.Config, - CreationDate: sf.Container.CreationDate, - LastUsedDate: sf.Container.LastUsedDate, + CreationDate: sf.Container.CreatedAt, + LastUsedDate: sf.Container.LastUsedAt, Ctype: cTypeRegular, Devices: sf.Container.Devices, Ephemeral: sf.Container.Ephemeral, diff -Nru lxd-2.7/lxd/certificates.go lxd-2.8/lxd/certificates.go --- lxd-2.7/lxd/certificates.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/certificates.go 2017-01-25 02:50:49.000000000 +0000 @@ -12,6 +12,7 @@ "github.com/gorilla/mux" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" ) @@ -19,14 +20,14 @@ recursion := d.isRecursionRequest(r) if recursion { - certResponses := []shared.CertInfo{} + certResponses := []api.Certificate{} baseCerts, err := dbCertsGet(d.db) if err != nil { return SmartError(err) } for _, baseCert := range baseCerts { - resp := shared.CertInfo{} + resp := api.Certificate{} resp.Fingerprint = baseCert.Fingerprint resp.Certificate = baseCert.Certificate if baseCert.Type == 1 { @@ -48,13 +49,6 @@ return SyncResponse(true, body) } -type certificatesPostBody struct { - Type string `json:"type"` - Certificate string `json:"certificate"` - Name string `json:"name"` - Password string `json:"password"` -} - func readSavedClientCAList(d *Daemon) { d.clientCerts = []x509.Certificate{} @@ -94,7 +88,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response { // Parse the request - req := certificatesPostBody{} + req := api.CertificatesPost{} if err := shared.ReadToJSON(r.Body, &req); err != nil { return BadRequest(err) } @@ -168,8 +162,8 @@ return SyncResponseETag(true, cert, cert) } -func doCertificateGet(d *Daemon, fingerprint string) (shared.CertInfo, error) { - resp := shared.CertInfo{} +func doCertificateGet(d *Daemon, fingerprint string) (api.Certificate, error) { + resp := api.Certificate{} dbCertInfo, err := dbCertGet(d.db, fingerprint) if err != nil { @@ -202,7 +196,7 @@ return PreconditionFailed(err) } - req := shared.CertInfo{} + req := api.CertificatePut{} if err := shared.ReadToJSON(r.Body, &req); err != nil { return BadRequest(err) } @@ -242,10 +236,10 @@ req.Type = value } - return doCertificateUpdate(d, fingerprint, req) + return doCertificateUpdate(d, fingerprint, req.Writable()) } -func doCertificateUpdate(d *Daemon, fingerprint string, req shared.CertInfo) Response { +func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut) Response { if req.Type != "client" { return BadRequest(fmt.Errorf("Unknown request type %s", req.Type)) } diff -Nru lxd-2.7/lxd/container_exec.go lxd-2.8/lxd/container_exec.go --- lxd-2.7/lxd/container_exec.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container_exec.go 2017-01-25 02:50:49.000000000 +0000 @@ -16,21 +16,12 @@ "github.com/gorilla/websocket" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" log "gopkg.in/inconshreveable/log15.v2" ) -type commandPostContent struct { - Command []string `json:"command"` - WaitForWS bool `json:"wait-for-websocket"` - RecordOutput bool `json:"record-output"` - Interactive bool `json:"interactive"` - Environment map[string]string `json:"environment"` - Width int `json:"width"` - Height int `json:"height"` -} - type execWs struct { command []string container container @@ -174,7 +165,7 @@ break } - command := shared.ContainerExecControl{} + command := api.ContainerExecControl{} if err := json.Unmarshal(buf, &command); err != nil { shared.LogDebugf("Failed to unmarshal control socket command: %s", err) @@ -200,11 +191,11 @@ continue } } else if command.Command == "signal" { - if err := syscall.Kill(attachedChildPid, command.Signal); err != nil { + if err := syscall.Kill(attachedChildPid, syscall.Signal(command.Signal)); err != nil { shared.LogDebugf("Failed forwarding signal '%s' to PID %d.", command.Signal, attachedChildPid) continue } - shared.LogDebugf("Forwarded signal '%s' to PID %d.", command.Signal, attachedChildPid) + shared.LogDebugf("Forwarded signal '%d' to PID %d.", command.Signal, attachedChildPid) } } }() @@ -291,12 +282,10 @@ if status.Exited() { return finisher(status.ExitStatus(), nil) } - // Backwards compatible behavior. Report success when we exited - // due to a signal. Otherwise this may break Jenkins, e.g. when - // lxc exec foo reboot receives SIGTERM and status.Exitstats() - // would report -1. + if status.Signaled() { - return finisher(0, nil) + // COMMENT(brauner): 128 + n == Fatal error signal "n" + return finisher(128+int(status.Signal()), nil) } } @@ -318,7 +307,7 @@ return BadRequest(fmt.Errorf("Container is frozen.")) } - post := commandPostContent{} + post := api.ContainerExecPost{} buf, err := ioutil.ReadAll(r.Body) if err != nil { return BadRequest(err) diff -Nru lxd-2.7/lxd/container.go lxd-2.8/lxd/container.go --- lxd-2.7/lxd/container.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container.go 2017-01-25 02:50:49.000000000 +0000 @@ -9,7 +9,9 @@ "gopkg.in/lxc/go-lxc.v2" + "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/osarch" ) @@ -215,7 +217,7 @@ return nil } -func containerValidDevices(devices shared.Devices, profile bool, expanded bool) error { +func containerValidDevices(devices types.Devices, profile bool, expanded bool) error { // Empty device list if devices == nil { return nil @@ -231,7 +233,7 @@ return fmt.Errorf("Invalid device type for device '%s'", name) } - for k, _ := range m { + for k := range m { if !containerValidDeviceConfigKey(m["type"], k) { return fmt.Errorf("Invalid device configuration key for %s: %s", m["type"], k) } @@ -315,7 +317,7 @@ CreationDate time.Time LastUsedDate time.Time Ctype containerType - Devices shared.Devices + Devices types.Devices Ephemeral bool Name string Profiles []string @@ -373,7 +375,7 @@ // Status Render() (interface{}, interface{}, error) - RenderState() (*shared.ContainerState, error) + RenderState() (*api.ContainerState, error) IsPrivileged() bool IsRunning() bool IsFrozen() bool @@ -393,9 +395,9 @@ CreationDate() time.Time LastUsedDate() time.Time ExpandedConfig() map[string]string - ExpandedDevices() shared.Devices + ExpandedDevices() types.Devices LocalConfig() map[string]string - LocalDevices() shared.Devices + LocalDevices() types.Devices Profiles() []string InitPID() int State() string @@ -596,7 +598,7 @@ } if args.Devices == nil { - args.Devices = shared.Devices{} + args.Devices = types.Devices{} } if args.Architecture == 0 { diff -Nru lxd-2.7/lxd/container_lxc.go lxd-2.8/lxd/container_lxc.go --- lxd-2.7/lxd/container_lxc.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container_lxc.go 2017-01-25 02:50:49.000000000 +0000 @@ -24,7 +24,9 @@ "gopkg.in/lxc/go-lxc.v2" "gopkg.in/yaml.v2" + "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" @@ -163,17 +165,17 @@ return nil } -func lxcStatusCode(state lxc.State) shared.StatusCode { - return map[int]shared.StatusCode{ - 1: shared.Stopped, - 2: shared.Starting, - 3: shared.Running, - 4: shared.Stopping, - 5: shared.Aborting, - 6: shared.Freezing, - 7: shared.Frozen, - 8: shared.Thawed, - 9: shared.Error, +func lxcStatusCode(state lxc.State) api.StatusCode { + return map[int]api.StatusCode{ + 1: api.Stopped, + 2: api.Starting, + 3: api.Running, + 4: api.Stopping, + 5: api.Aborting, + 6: api.Freezing, + 7: api.Frozen, + 8: api.Thawed, + 9: api.Error, }[int(state)] } @@ -231,7 +233,7 @@ deviceName += "_" } - c.localDevices[deviceName] = shared.Device{"type": "disk", "path": "/"} + c.localDevices[deviceName] = types.Device{"type": "disk", "path": "/"} updateArgs := containerArgs{ Architecture: c.architecture, @@ -382,10 +384,10 @@ // Config expandedConfig map[string]string - expandedDevices shared.Devices + expandedDevices types.Devices fromHook bool localConfig map[string]string - localDevices shared.Devices + localDevices types.Devices profiles []string // Cache @@ -638,8 +640,8 @@ mkIdmap := func(offset int, size int) *shared.IdmapSet { set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{ - shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size}, - shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size}, + {Isuid: true, Nsid: 0, Hostid: offset, Maprange: size}, + {Isgid: true, Nsid: 0, Hostid: offset, Maprange: size}, }} for _, ent := range rawMaps { @@ -1398,7 +1400,7 @@ } func (c *containerLXC) expandDevices() error { - devices := shared.Devices{} + devices := types.Devices{} // Apply all the profiles for _, p := range c.profiles { @@ -1423,7 +1425,7 @@ // setupUnixDevice() creates the unix device and sets up the necessary low-level // liblxc configuration items. -func (c *containerLXC) setupUnixDevice(devType string, dev shared.Device, major int, minor int, path string, createMustSucceed bool) error { +func (c *containerLXC) setupUnixDevice(devType string, dev types.Device, major int, minor int, path string, createMustSucceed bool) error { if c.IsPrivileged() && !runningInUserns && cgDevicesController { err := lxcSetConfigItem(c.c, "lxc.cgroup.devices.allow", fmt.Sprintf("c %d:%d rwm", major, minor)) if err != nil { @@ -1431,7 +1433,7 @@ } } - temp := shared.Device{} + temp := types.Device{} if err := shared.DeepCopy(&dev, &temp); err != nil { return err } @@ -1592,7 +1594,7 @@ var usbs []usbDevice var gpus []gpuDevice var nvidiaDevices []nvidiaGpuDevices - diskDevices := map[string]shared.Device{} + diskDevices := map[string]types.Device{} // Create the devices for _, k := range c.expandedDevices.DeviceNames() { @@ -1722,7 +1724,7 @@ } } - err = c.addDiskDevices(diskDevices, func(name string, d shared.Device) error { + err = c.addDiskDevices(diskDevices, func(name string, d types.Device) error { _, err := c.createDiskDevice(name, d) return err }) @@ -1755,7 +1757,7 @@ } } - for k, _ := range c.localConfig { + for k := range c.localConfig { // We only care about volatile if !strings.HasPrefix(k, "volatile.") { continue @@ -2030,7 +2032,7 @@ continue } - go func(c *containerLXC, name string, m shared.Device) { + go func(c *containerLXC, name string, m types.Device) { c.fromHook = false err = c.setNetworkLimits(name, m) if err != nil { @@ -2401,7 +2403,7 @@ etag := []interface{}{c.architecture, c.localConfig, c.localDevices, c.ephemeral, c.profiles} if c.IsSnapshot() { - return &shared.SnapshotInfo{ + return &api.ContainerSnapshot{ Architecture: architectureName, Config: c.localConfig, CreationDate: c.creationDate, @@ -2422,25 +2424,28 @@ } statusCode := lxcStatusCode(cState) - return &shared.ContainerInfo{ - Architecture: architectureName, - Config: c.localConfig, - CreationDate: c.creationDate, - Devices: c.localDevices, - Ephemeral: c.ephemeral, + ct := api.Container{ ExpandedConfig: c.expandedConfig, ExpandedDevices: c.expandedDevices, - LastUsedDate: c.lastUsedDate, Name: c.name, - Profiles: c.profiles, Status: statusCode.String(), StatusCode: statusCode, Stateful: c.stateful, - }, etag, nil + } + + ct.Architecture = architectureName + ct.Config = c.localConfig + ct.CreatedAt = c.creationDate + ct.Devices = c.localDevices + ct.Ephemeral = c.ephemeral + ct.LastUsedAt = c.lastUsedDate + ct.Profiles = c.profiles + + return &ct, etag, nil } } -func (c *containerLXC) RenderState() (*shared.ContainerState, error) { +func (c *containerLXC) RenderState() (*api.ContainerState, error) { // Load the go-lxc struct err := c.initLXC() if err != nil { @@ -2452,7 +2457,7 @@ return nil, err } statusCode := lxcStatusCode(cState) - status := shared.ContainerState{ + status := api.ContainerState{ Status: statusCode.String(), StatusCode: statusCode, } @@ -2644,8 +2649,20 @@ return err } - // Update lease files + // Update network files networkUpdateStatic(c.daemon, "") + for k, m := range c.expandedDevices { + if m["type"] != "nic" || m["nictype"] != "bridged" || (m["ipv4.address"] == "" && m["ipv6.address"] == "") { + continue + } + + m, err := c.fillNetworkDevice(k, m) + if err != nil { + continue + } + + networkClearLease(c.daemon, m["parent"], m["hwaddr"]) + } shared.LogInfo("Deleted container", ctxMap) @@ -2784,8 +2801,8 @@ } type backupFile struct { - Container *shared.ContainerInfo `yaml:"container"` - Snapshots []*shared.SnapshotInfo `yaml:"snapshots"` + Container *api.Container `yaml:"container"` + Snapshots []*api.ContainerSnapshot `yaml:"snapshots"` } func writeBackupFile(c container) error { @@ -2799,7 +2816,7 @@ return os.ErrNotExist } - /* deal with the container occasionaly not being monuted */ + /* deal with the container occasionally not being monuted */ if !shared.PathExists(c.RootfsPath()) { shared.LogWarn("Unable to update backup.yaml at this time.", log.Ctx{"name": c.Name()}) return nil @@ -2815,7 +2832,7 @@ return err } - var sis []*shared.SnapshotInfo + var sis []*api.ContainerSnapshot for _, s := range snapshots { si, _, err := s.Render() @@ -2823,11 +2840,11 @@ return err } - sis = append(sis, si.(*shared.SnapshotInfo)) + sis = append(sis, si.(*api.ContainerSnapshot)) } data, err := yaml.Marshal(&backupFile{ - Container: ci.(*shared.ContainerInfo), + Container: ci.(*api.Container), Snapshots: sis, }) if err != nil { @@ -2864,7 +2881,7 @@ } if args.Devices == nil { - args.Devices = shared.Devices{} + args.Devices = types.Devices{} } if args.Profiles == nil { @@ -2939,7 +2956,7 @@ return err } - oldExpandedDevices := shared.Devices{} + oldExpandedDevices := types.Devices{} err = shared.DeepCopy(&c.expandedDevices, &oldExpandedDevices) if err != nil { return err @@ -2951,7 +2968,7 @@ return err } - oldLocalDevices := shared.Devices{} + oldLocalDevices := types.Devices{} err = shared.DeepCopy(&c.localDevices, &oldLocalDevices) if err != nil { return err @@ -3009,7 +3026,7 @@ // Diff the configurations changedConfig := []string{} - for key, _ := range oldExpandedConfig { + for key := range oldExpandedConfig { if oldExpandedConfig[key] != c.expandedConfig[key] { if !shared.StringInSlice(key, changedConfig) { changedConfig = append(changedConfig, key) @@ -3017,7 +3034,7 @@ } } - for key, _ := range c.expandedConfig { + for key := range c.expandedConfig { if oldExpandedConfig[key] != c.expandedConfig[key] { if !shared.StringInSlice(key, changedConfig) { changedConfig = append(changedConfig, key) @@ -3112,7 +3129,7 @@ // Apply the live changes if c.IsRunning() { // Confirm that the rootfs source didn't change - var oldRootfs shared.Device + var oldRootfs types.Device for _, m := range oldExpandedDevices { if m["type"] == "disk" && m["path"] == "/" { oldRootfs = m @@ -3120,7 +3137,7 @@ } } - var newRootfs shared.Device + var newRootfs types.Device for _, name := range c.expandedDevices.DeviceNames() { m := c.expandedDevices[name] if m["type"] == "disk" && m["path"] == "/" { @@ -3432,7 +3449,7 @@ } } - diskDevices := map[string]shared.Device{} + diskDevices := map[string]types.Device{} for k, m := range addDevices { if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) { @@ -3828,21 +3845,30 @@ // Include all the rootfs files fnam = c.RootfsPath() - filepath.Walk(fnam, writeToTar) + err = filepath.Walk(fnam, writeToTar) + if err != nil { + shared.LogError("Failed exporting container", ctxMap) + return err + } // Include all the templates fnam = c.TemplatesPath() if shared.PathExists(fnam) { - filepath.Walk(fnam, writeToTar) + err = filepath.Walk(fnam, writeToTar) + if err != nil { + shared.LogError("Failed exporting container", ctxMap) + return err + } } err = tw.Close() if err != nil { shared.LogError("Failed exporting container", ctxMap) + return err } shared.LogInfo("Exported container", ctxMap) - return err + return nil } func collectCRIULogFile(c container, imagesDir string, function string, method string) error { @@ -4551,6 +4577,11 @@ if ok { return status.ExitStatus(), attachedPid, nil } + + if status.Signaled() { + // COMMENT(brauner): 128 + n == Fatal error signal "n" + return 128 + int(status.Signal()), attachedPid, nil + } } return -1, -1, err } @@ -4558,8 +4589,8 @@ return 0, attachedPid, nil } -func (c *containerLXC) cpuState() shared.ContainerStateCPU { - cpu := shared.ContainerStateCPU{} +func (c *containerLXC) cpuState() api.ContainerStateCPU { + cpu := api.ContainerStateCPU{} if !cgCpuacctController { return cpu @@ -4577,8 +4608,8 @@ return cpu } -func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk { - disk := map[string]shared.ContainerStateDisk{} +func (c *containerLXC) diskState() map[string]api.ContainerStateDisk { + disk := map[string]api.ContainerStateDisk{} for _, name := range c.expandedDevices.DeviceNames() { d := c.expandedDevices[name] @@ -4595,14 +4626,14 @@ continue } - disk[name] = shared.ContainerStateDisk{Usage: usage} + disk[name] = api.ContainerStateDisk{Usage: usage} } return disk } -func (c *containerLXC) memoryState() shared.ContainerStateMemory { - memory := shared.ContainerStateMemory{} +func (c *containerLXC) memoryState() api.ContainerStateMemory { + memory := api.ContainerStateMemory{} if !cgMemoryController { return memory @@ -4648,8 +4679,8 @@ return memory } -func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork { - result := map[string]shared.ContainerStateNetwork{} +func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork { + result := map[string]api.ContainerStateNetwork{} pid := c.InitPID() if pid < 1 { @@ -4668,7 +4699,7 @@ return result } - networks := map[string]shared.ContainerStateNetwork{} + networks := map[string]api.ContainerStateNetwork{} err = json.Unmarshal(out, &networks) if err != nil { @@ -4734,13 +4765,15 @@ if fi.Mode()&os.ModeSymlink == os.ModeSymlink { link, err = os.Readlink(path) if err != nil { - return err + return fmt.Errorf("failed to resolve symlink: %s", err) } } + hdr, err := tar.FileInfoHeader(fi, link) if err != nil { - return err + return fmt.Errorf("failed to create tar info header: %s", err) } + hdr.Name = path[offset:] if fi.IsDir() || fi.Mode()&os.ModeSymlink == os.ModeSymlink { hdr.Size = 0 @@ -4750,7 +4783,7 @@ hdr.Uid, hdr.Gid, major, minor, ino, nlink, err = shared.GetFileStat(path) if err != nil { - return fmt.Errorf("error getting file info: %s", err) + return fmt.Errorf("failed to get file stat: %s", err) } // Unshift the id under /rootfs/ for unpriv containers @@ -4760,6 +4793,7 @@ return nil } } + if major != -1 { hdr.Devmajor = int64(major) hdr.Devminor = int64(minor) @@ -4776,26 +4810,30 @@ } } - // Handle xattrs. - hdr.Xattrs, err = shared.GetAllXattr(path) - if err != nil { - return err + // Handle xattrs (for real files only) + if link == "" { + hdr.Xattrs, err = shared.GetAllXattr(path) + if err != nil { + return fmt.Errorf("failed to read xattr: %s", err) + } } if err := tw.WriteHeader(hdr); err != nil { - return fmt.Errorf("error writing header: %s", err) + return fmt.Errorf("failed to write tar header: %s", err) } if hdr.Typeflag == tar.TypeReg { f, err := os.Open(path) if err != nil { - return fmt.Errorf("tarStoreFile: error opening file: %s", err) + return fmt.Errorf("failed to open the file: %s", err) } defer f.Close() + if _, err := io.Copy(tw, f); err != nil { - return fmt.Errorf("error copying file %s", err) + return fmt.Errorf("failed to copy file content: %s", err) } } + return nil } @@ -4918,7 +4956,7 @@ } // Unix devices handling -func (c *containerLXC) createUnixDevice(m shared.Device) ([]string, error) { +func (c *containerLXC) createUnixDevice(m types.Device) ([]string, error) { var err error var major, minor int @@ -5049,7 +5087,7 @@ return []string{devPath, tgtPath}, nil } -func (c *containerLXC) insertUnixDevice(m shared.Device) error { +func (c *containerLXC) insertUnixDevice(m types.Device) error { // Check that the container is running if !c.IsRunning() { return fmt.Errorf("Can't insert device into stopped container") @@ -5111,8 +5149,8 @@ return nil } -func (c *containerLXC) insertUnixDeviceNum(m shared.Device, major int, minor int, path string) error { - temp := shared.Device{} +func (c *containerLXC) insertUnixDeviceNum(m types.Device, major int, minor int, path string) error { + temp := types.Device{} if err := shared.DeepCopy(&m, &temp); err != nil { return err } @@ -5124,7 +5162,7 @@ return c.insertUnixDevice(temp) } -func (c *containerLXC) removeUnixDevice(m shared.Device) error { +func (c *containerLXC) removeUnixDevice(m types.Device) error { // Check that the container is running pid := c.InitPID() if pid == -1 { @@ -5202,13 +5240,13 @@ return nil } -func (c *containerLXC) removeUnixDeviceNum(m shared.Device, major int, minor int, path string) error { +func (c *containerLXC) removeUnixDeviceNum(m types.Device, major int, minor int, path string) error { pid := c.InitPID() if pid == -1 { return fmt.Errorf("Can't remove device from stopped container") } - temp := shared.Device{} + temp := types.Device{} if err := shared.DeepCopy(&m, &temp); err != nil { return err } @@ -5258,7 +5296,7 @@ } // Network device handling -func (c *containerLXC) createNetworkDevice(name string, m shared.Device) (string, error) { +func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string, error) { var dev, n1 string if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p", "macvlan"}) { @@ -5338,8 +5376,8 @@ return dev, nil } -func (c *containerLXC) fillNetworkDevice(name string, m shared.Device) (shared.Device, error) { - newDevice := shared.Device{} +func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Device, error) { + newDevice := types.Device{} err := shared.DeepCopy(&m, &newDevice) if err != nil { return nil, err @@ -5549,7 +5587,7 @@ return nil } -func (c *containerLXC) insertNetworkDevice(name string, m shared.Device) error { +func (c *containerLXC) insertNetworkDevice(name string, m types.Device) error { // Load the go-lxc struct err := c.initLXC() if err != nil { @@ -5586,7 +5624,7 @@ return nil } -func (c *containerLXC) removeNetworkDevice(name string, m shared.Device) error { +func (c *containerLXC) removeNetworkDevice(name string, m types.Device) error { // Load the go-lxc struct err := c.initLXC() if err != nil { @@ -5641,7 +5679,7 @@ } // Disk device handling -func (c *containerLXC) createDiskDevice(name string, m shared.Device) (string, error) { +func (c *containerLXC) createDiskDevice(name string, m types.Device) (string, error) { // Prepare all the paths srcPath := m["source"] tgtPath := strings.TrimPrefix(m["path"], "/") @@ -5702,7 +5740,7 @@ return devPath, nil } -func (c *containerLXC) insertDiskDevice(name string, m shared.Device) error { +func (c *containerLXC) insertDiskDevice(name string, m types.Device) error { // Check that the container is running if !c.IsRunning() { return fmt.Errorf("Can't insert device into stopped container") @@ -5731,7 +5769,7 @@ return nil } -type byPath []shared.Device +type byPath []types.Device func (a byPath) Len() int { return len(a) @@ -5745,7 +5783,7 @@ return a[i]["path"] < a[j]["path"] } -func (c *containerLXC) addDiskDevices(devices map[string]shared.Device, handler func(string, shared.Device) error) error { +func (c *containerLXC) addDiskDevices(devices map[string]types.Device, handler func(string, types.Device) error) error { ordered := byPath{} for _, d := range devices { @@ -5763,7 +5801,7 @@ return nil } -func (c *containerLXC) removeDiskDevice(name string, m shared.Device) error { +func (c *containerLXC) removeDiskDevice(name string, m types.Device) error { // Check that the container is running pid := c.InitPID() if pid == -1 { @@ -6059,7 +6097,7 @@ return "" } -func (c *containerLXC) setNetworkLimits(name string, m shared.Device) error { +func (c *containerLXC) setNetworkLimits(name string, m types.Device) error { // We can only do limits on some network type if m["nictype"] != "bridged" && m["nictype"] != "p2p" { return fmt.Errorf("Network limits are only supported on bridged and p2p interfaces") @@ -6194,7 +6232,7 @@ return c.expandedConfig } -func (c *containerLXC) ExpandedDevices() shared.Devices { +func (c *containerLXC) ExpandedDevices() types.Devices { return c.expandedDevices } @@ -6220,7 +6258,7 @@ return c.localConfig } -func (c *containerLXC) LocalDevices() shared.Devices { +func (c *containerLXC) LocalDevices() types.Devices { return c.localDevices } @@ -6277,12 +6315,12 @@ // Load the go-lxc struct err := c.initLXC() if err != nil { - return "BROKEN" + return "Broken" } state, err := c.getLxcState() if err != nil { - return shared.Error.String() + return api.Error.String() } return state.String() } diff -Nru lxd-2.7/lxd/container_patch.go lxd-2.8/lxd/container_patch.go --- lxd-2.7/lxd/container_patch.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container_patch.go 2017-01-25 02:50:49.000000000 +0000 @@ -10,6 +10,7 @@ "github.com/gorilla/mux" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/osarch" ) @@ -41,7 +42,7 @@ return BadRequest(err) } - req := containerPutReq{} + req := api.ContainerPut{} if err := json.NewDecoder(rdr2).Decode(&req); err != nil { return BadRequest(err) } diff -Nru lxd-2.7/lxd/container_post.go lxd-2.8/lxd/container_post.go --- lxd-2.7/lxd/container_post.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container_post.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,12 +6,9 @@ "net/http" "github.com/gorilla/mux" -) -type containerPostBody struct { - Migration bool `json:"migration"` - Name string `json:"name"` -} + "github.com/lxc/lxd/shared/api" +) func containerPost(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] @@ -25,7 +22,7 @@ return InternalError(err) } - body := containerPostBody{} + body := api.ContainerPost{} if err := json.Unmarshal(buf, &body); err != nil { return BadRequest(err) } diff -Nru lxd-2.7/lxd/container_put.go lxd-2.8/lxd/container_put.go --- lxd-2.7/lxd/container_put.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container_put.go 2017-01-25 02:50:49.000000000 +0000 @@ -9,20 +9,12 @@ "github.com/gorilla/mux" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" ) -type containerPutReq struct { - Architecture string `json:"architecture"` - Config map[string]string `json:"config"` - Devices shared.Devices `json:"devices"` - Ephemeral bool `json:"ephemeral"` - Profiles []string `json:"profiles"` - Restore string `json:"restore"` -} - /* * Update configuration, or, if 'restore:snapshot-name' is present, restore * the named snapshot @@ -42,7 +34,7 @@ return PreconditionFailed(err) } - configRaw := containerPutReq{} + configRaw := api.ContainerPut{} if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil { return BadRequest(err) } diff -Nru lxd-2.7/lxd/containers_get.go lxd-2.8/lxd/containers_get.go --- lxd-2.7/lxd/containers_get.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/containers_get.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,6 +6,7 @@ "time" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" ) @@ -36,7 +37,7 @@ } resultString := []string{} - resultList := []*shared.ContainerInfo{} + resultList := []*api.Container{} if err != nil { return []string{}, err } @@ -48,10 +49,10 @@ } else { c, err := doContainerGet(d, container) if err != nil { - c = &shared.ContainerInfo{ + c = &api.Container{ Name: container, - Status: shared.Error.String(), - StatusCode: shared.Error} + Status: api.Error.String(), + StatusCode: api.Error} } resultList = append(resultList, c) } @@ -64,7 +65,7 @@ return resultList, nil } -func doContainerGet(d *Daemon, cname string) (*shared.ContainerInfo, error) { +func doContainerGet(d *Daemon, cname string) (*api.Container, error) { c, err := containerLoadByName(d, cname) if err != nil { return nil, err @@ -75,5 +76,5 @@ return nil, err } - return cts.(*shared.ContainerInfo), nil + return cts.(*api.Container), nil } diff -Nru lxd-2.7/lxd/container_snapshot.go lxd-2.8/lxd/container_snapshot.go --- lxd-2.7/lxd/container_snapshot.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container_snapshot.go 2017-01-25 02:50:49.000000000 +0000 @@ -10,14 +10,10 @@ "github.com/gorilla/mux" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" ) -type containerSnapshotPostReq struct { - Name string `json:"name"` - Stateful bool `json:"stateful"` -} - func containerSnapshotsGet(d *Daemon, r *http.Request) Response { recursionStr := r.FormValue("recursion") recursion, err := strconv.Atoi(recursionStr) @@ -37,7 +33,7 @@ } resultString := []string{} - resultMap := []*shared.SnapshotInfo{} + resultMap := []*api.ContainerSnapshot{} for _, snap := range snaps { snapName := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)[1] @@ -50,7 +46,7 @@ continue } - resultMap = append(resultMap, render.(*shared.SnapshotInfo)) + resultMap = append(resultMap, render.(*api.ContainerSnapshot)) } } @@ -111,7 +107,7 @@ return SmartError(err) } - req := containerSnapshotPostReq{} + req := api.ContainerSnapshotsPost{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } @@ -189,7 +185,7 @@ return SmartError(err) } - return SyncResponse(true, render.(*shared.SnapshotInfo)) + return SyncResponse(true, render.(*api.ContainerSnapshot)) } func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) Response { diff -Nru lxd-2.7/lxd/containers_post.go lxd-2.8/lxd/containers_post.go --- lxd-2.7/lxd/containers_post.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/containers_post.go 2017-01-25 02:50:49.000000000 +0000 @@ -11,55 +11,15 @@ "github.com/dustinkirkland/golang-petname" "github.com/gorilla/websocket" + "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/osarch" log "gopkg.in/inconshreveable/log15.v2" ) -type containerImageSource struct { - Type string `json:"type"` - Certificate string `json:"certificate"` - - /* for "image" type */ - Alias string `json:"alias"` - Fingerprint string `json:"fingerprint"` - Properties map[string]string `json:"properties"` - Server string `json:"server"` - Secret string `json:"secret"` - Protocol string `json:"protocol"` - - /* - * for "migration" and "copy" types, as an optimization users can - * provide an image hash to extract before the filesystem is rsync'd, - * potentially cutting down filesystem transfer time. LXD will not go - * and fetch this image, it will simply use it if it exists in the - * image store. - */ - BaseImage string `json:"base-image"` - - /* for "migration" type */ - Mode string `json:"mode"` - Operation string `json:"operation"` - Websockets map[string]string `json:"secrets"` - - /* for "copy" type */ - Source string `json:"source"` - /* for "migration" type. Whether the migration is live. */ - Live bool `json:"live"` -} - -type containerPostReq struct { - Architecture string `json:"architecture"` - Config map[string]string `json:"config"` - Devices shared.Devices `json:"devices"` - Ephemeral bool `json:"ephemeral"` - Name string `json:"name"` - Profiles []string `json:"profiles"` - Source containerImageSource `json:"source"` -} - -func createFromImage(d *Daemon, req *containerPostReq) Response { +func createFromImage(d *Daemon, req *api.ContainersPost) Response { var hash string var err error @@ -88,7 +48,7 @@ return InternalError(err) } - var image *shared.ImageInfo + var image *api.Image for _, hash := range hashes { _, img, err := dbImageGet(d.db, hash, false, true) @@ -96,7 +56,7 @@ continue } - if image != nil && img.CreationDate.Before(image.CreationDate) { + if image != nil && img.CreatedAt.Before(image.CreatedAt) { continue } @@ -172,7 +132,7 @@ return OperationResponse(op) } -func createFromNone(d *Daemon, req *containerPostReq) Response { +func createFromNone(d *Daemon, req *api.ContainersPost) Response { architecture, err := osarch.ArchitectureId(req.Architecture) if err != nil { architecture = 0 @@ -204,7 +164,7 @@ return OperationResponse(op) } -func createFromMigration(d *Daemon, req *containerPostReq) Response { +func createFromMigration(d *Daemon, req *api.ContainersPost) Response { if req.Source.Mode != "pull" && req.Source.Mode != "push" { return NotImplemented } @@ -297,7 +257,7 @@ } run := func(op *operation) error { - // And finaly run the migration. + // And finally run the migration. err = sink.Do(op) if err != nil { shared.LogError("Error during migration sink", log.Ctx{"err": err}) @@ -333,7 +293,7 @@ return OperationResponse(op) } -func createFromCopy(d *Daemon, req *containerPostReq) Response { +func createFromCopy(d *Daemon, req *api.ContainersPost) Response { if req.Source.Source == "" { return BadRequest(fmt.Errorf("must specify a source container")) } @@ -404,7 +364,7 @@ func containersPost(d *Daemon, r *http.Request) Response { shared.LogDebugf("Responding to container create") - req := containerPostReq{} + req := api.ContainersPost{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } @@ -431,7 +391,7 @@ } if req.Devices == nil { - req.Devices = shared.Devices{} + req.Devices = types.Devices{} } if req.Config == nil { diff -Nru lxd-2.7/lxd/container_state.go lxd-2.8/lxd/container_state.go --- lxd-2.7/lxd/container_state.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container_state.go 2017-01-25 02:50:49.000000000 +0000 @@ -9,15 +9,9 @@ "github.com/gorilla/mux" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" ) -type containerStatePutReq struct { - Action string `json:"action"` - Timeout int `json:"timeout"` - Force bool `json:"force"` - Stateful bool `json:"stateful"` -} - func containerState(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] c, err := containerLoadByName(d, name) @@ -36,7 +30,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] - raw := containerStatePutReq{} + raw := api.ContainerStatePut{} // We default to -1 (i.e. no timeout) here instead of 0 (instant // timeout). diff -Nru lxd-2.7/lxd/container_test.go lxd-2.8/lxd/container_test.go --- lxd-2.7/lxd/container_test.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/container_test.go 2017-01-25 02:50:49.000000000 +0000 @@ -3,7 +3,9 @@ import ( "fmt" + "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" ) func (suite *lxdTestSuite) TestContainer_ProfilesDefault() { @@ -36,7 +38,7 @@ "unprivileged", "unprivileged", map[string]string{"security.privileged": "true"}, - shared.Devices{}) + types.Devices{}) suite.Req.Nil(err, "Failed to create the unprivileged profile.") defer func() { @@ -70,8 +72,8 @@ Ctype: cTypeRegular, Ephemeral: false, Config: map[string]string{"security.privileged": "true"}, - Devices: shared.Devices{ - "eth0": shared.Device{ + Devices: types.Devices{ + "eth0": types.Device{ "type": "nic", "nictype": "bridged", "parent": "unknownbr0"}}, @@ -86,7 +88,7 @@ out, _, err := c.Render() suite.Req.Nil(err) - state := out.(*shared.ContainerInfo) + state := out.(*api.Container) defer c.Delete() suite.Equal( @@ -100,8 +102,8 @@ Ctype: cTypeRegular, Ephemeral: false, Config: map[string]string{"security.privileged": "true"}, - Devices: shared.Devices{ - "eth0": shared.Device{ + Devices: types.Devices{ + "eth0": types.Device{ "type": "nic", "nictype": "bridged", "parent": "unknownbr0"}}, diff -Nru lxd-2.7/lxd/daemon_config.go lxd-2.8/lxd/daemon_config.go --- lxd-2.7/lxd/daemon_config.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/daemon_config.go 2017-01-25 02:50:49.000000000 +0000 @@ -173,29 +173,29 @@ func daemonConfigInit(db *sql.DB) error { // Set all the keys daemonConfig = map[string]*daemonConfigKey{ - "core.https_address": &daemonConfigKey{valueType: "string", setter: daemonConfigSetAddress}, - "core.https_allowed_headers": &daemonConfigKey{valueType: "string"}, - "core.https_allowed_methods": &daemonConfigKey{valueType: "string"}, - "core.https_allowed_origin": &daemonConfigKey{valueType: "string"}, - "core.https_allowed_credentials": &daemonConfigKey{valueType: "bool"}, - "core.proxy_http": &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy}, - "core.proxy_https": &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy}, - "core.proxy_ignore_hosts": &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy}, - "core.trust_password": &daemonConfigKey{valueType: "string", hiddenValue: true, setter: daemonConfigSetPassword}, - - "images.auto_update_cached": &daemonConfigKey{valueType: "bool", defaultValue: "true"}, - "images.auto_update_interval": &daemonConfigKey{valueType: "int", defaultValue: "6"}, - "images.compression_algorithm": &daemonConfigKey{valueType: "string", validator: daemonConfigValidateCompression, defaultValue: "gzip"}, - "images.remote_cache_expiry": &daemonConfigKey{valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry}, - - "storage.lvm_fstype": &daemonConfigKey{valueType: "string", defaultValue: "ext4", validValues: []string{"ext4", "xfs"}}, - "storage.lvm_mount_options": &daemonConfigKey{valueType: "string", defaultValue: "discard"}, - "storage.lvm_thinpool_name": &daemonConfigKey{valueType: "string", defaultValue: "LXDPool", validator: storageLVMValidateThinPoolName}, - "storage.lvm_vg_name": &daemonConfigKey{valueType: "string", validator: storageLVMValidateVolumeGroupName, setter: daemonConfigSetStorage}, - "storage.lvm_volume_size": &daemonConfigKey{valueType: "string", defaultValue: "10GiB"}, - "storage.zfs_pool_name": &daemonConfigKey{valueType: "string", validator: storageZFSValidatePoolName, setter: daemonConfigSetStorage}, - "storage.zfs_remove_snapshots": &daemonConfigKey{valueType: "bool"}, - "storage.zfs_use_refquota": &daemonConfigKey{valueType: "bool"}, + "core.https_address": {valueType: "string", setter: daemonConfigSetAddress}, + "core.https_allowed_headers": {valueType: "string"}, + "core.https_allowed_methods": {valueType: "string"}, + "core.https_allowed_origin": {valueType: "string"}, + "core.https_allowed_credentials": {valueType: "bool"}, + "core.proxy_http": {valueType: "string", setter: daemonConfigSetProxy}, + "core.proxy_https": {valueType: "string", setter: daemonConfigSetProxy}, + "core.proxy_ignore_hosts": {valueType: "string", setter: daemonConfigSetProxy}, + "core.trust_password": {valueType: "string", hiddenValue: true, setter: daemonConfigSetPassword}, + + "images.auto_update_cached": {valueType: "bool", defaultValue: "true"}, + "images.auto_update_interval": {valueType: "int", defaultValue: "6"}, + "images.compression_algorithm": {valueType: "string", validator: daemonConfigValidateCompression, defaultValue: "gzip"}, + "images.remote_cache_expiry": {valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry}, + + "storage.lvm_fstype": {valueType: "string", defaultValue: "ext4", validValues: []string{"ext4", "xfs"}}, + "storage.lvm_mount_options": {valueType: "string", defaultValue: "discard"}, + "storage.lvm_thinpool_name": {valueType: "string", defaultValue: "LXDPool", validator: storageLVMValidateThinPoolName}, + "storage.lvm_vg_name": {valueType: "string", validator: storageLVMValidateVolumeGroupName, setter: daemonConfigSetStorage}, + "storage.lvm_volume_size": {valueType: "string", defaultValue: "10GiB"}, + "storage.zfs_pool_name": {valueType: "string", validator: storageZFSValidatePoolName, setter: daemonConfigSetStorage}, + "storage.zfs_remove_snapshots": {valueType: "bool"}, + "storage.zfs_use_refquota": {valueType: "bool"}, } // Load the values from the DB @@ -311,7 +311,7 @@ // Clear the simplestreams cache as it's tied to the old proxy config imageStreamCacheLock.Lock() - for k, _ := range imageStreamCache { + for k := range imageStreamCache { delete(imageStreamCache, k) } imageStreamCacheLock.Unlock() diff -Nru lxd-2.7/lxd/daemon.go lxd-2.8/lxd/daemon.go --- lxd-2.7/lxd/daemon.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/daemon.go 2017-01-25 02:50:49.000000000 +0000 @@ -31,6 +31,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/logging" "github.com/lxc/lxd/shared/osarch" "github.com/lxc/lxd/shared/version" @@ -90,7 +91,7 @@ SetupMode bool imagesDownloading map[string]chan bool - imagesDownloadingLock sync.RWMutex + imagesDownloadingLock sync.Mutex tlsConfig *tls.Config @@ -144,7 +145,7 @@ return &myhttp, nil } -func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) { +func (d *Daemon) httpGetSync(url string, certificate string) (*api.Response, error) { var err error req, err := http.NewRequest("GET", url, nil) @@ -169,7 +170,7 @@ return nil, err } - if resp.Type != lxd.Sync { + if resp.Type != api.SyncResponse { return nil, fmt.Errorf("unexpected non-sync response") } @@ -197,7 +198,7 @@ } if raw.StatusCode != 200 { - _, err := lxd.HoistResponse(raw, lxd.Error) + _, err := lxd.HoistResponse(raw, api.ErrorResponse) if err != nil { return nil, err } @@ -768,6 +769,9 @@ if err := os.MkdirAll(shared.VarPath("snapshots"), 0700); err != nil { return err } + if err := os.MkdirAll(shared.VarPath("networks"), 0711); err != nil { + return err + } /* Detect the filesystem */ d.BackingFs, err = filesystemDetect(d.lxcpath) diff -Nru lxd-2.7/lxd/daemon_images.go lxd-2.8/lxd/daemon_images.go --- lxd-2.7/lxd/daemon_images.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/daemon_images.go 2017-01-25 02:50:49.000000000 +0000 @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "fmt" "io" "io/ioutil" @@ -16,6 +15,7 @@ "gopkg.in/yaml.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/ioprogress" "github.com/lxc/lxd/shared/simplestreams" "github.com/lxc/lxd/shared/version" @@ -25,8 +25,8 @@ // Simplestream cache type imageStreamCacheEntry struct { - Aliases shared.ImageAliases `yaml:"aliases"` - Fingerprints []string `yaml:"fingerprints"` + Aliases []api.ImageAliasesEntry `yaml:"aliases"` + Fingerprints []string `yaml:"fingerprints"` expiry time.Time ss *simplestreams.SimpleStreams } @@ -188,10 +188,10 @@ } // Now check if we already downloading the image - d.imagesDownloadingLock.RLock() + d.imagesDownloadingLock.Lock() if waitChannel, ok := d.imagesDownloading[fp]; ok { // We already download the image - d.imagesDownloadingLock.RUnlock() + d.imagesDownloadingLock.Unlock() shared.LogDebug( "Already downloading the image, waiting for it to succeed", @@ -217,18 +217,7 @@ return fp, nil } - d.imagesDownloadingLock.RUnlock() - - if op == nil { - ctxMap = log.Ctx{"alias": alias, "server": server} - } else { - ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server} - } - - shared.LogInfo("Downloading image", ctxMap) - // Add the download to the queue - d.imagesDownloadingLock.Lock() d.imagesDownloading[fp] = make(chan bool) d.imagesDownloadingLock.Unlock() @@ -242,9 +231,18 @@ d.imagesDownloadingLock.Unlock() }() + // Begin downloading + if op == nil { + ctxMap = log.Ctx{"alias": alias, "server": server} + } else { + ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server} + } + + shared.LogInfo("Downloading image", ctxMap) + exporturl := server - var info shared.ImageInfo + var info api.Image info.Fingerprint = fp destDir := shared.VarPath("images") @@ -263,7 +261,7 @@ meta = make(map[string]interface{}) } - progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(speedInt)) + progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(speedInt, 2)) if meta["download_progress"] != progress { meta["download_progress"] = progress @@ -291,7 +289,7 @@ return "", err } - if err := json.Unmarshal(resp.Metadata, &info); err != nil { + if err := resp.MetadataAsStruct(&info); err != nil { return "", err } @@ -481,8 +479,8 @@ } info.Architecture = imageMeta.Architecture - info.CreationDate = time.Unix(imageMeta.CreationDate, 0) - info.ExpiryDate = time.Unix(imageMeta.ExpiryDate, 0) + info.CreatedAt = time.Unix(imageMeta.CreationDate, 0) + info.ExpiresAt = time.Unix(imageMeta.ExpiryDate, 0) info.Properties = imageMeta.Properties } diff -Nru lxd-2.7/lxd/db_containers.go lxd-2.8/lxd/db_containers.go --- lxd-2.7/lxd/db_containers.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/db_containers.go 2017-01-25 02:50:49.000000000 +0000 @@ -5,6 +5,7 @@ "fmt" "time" + "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" log "gopkg.in/inconshreveable/log15.v2" @@ -108,7 +109,7 @@ args.Profiles = profiles /* get container_devices */ - args.Devices = shared.Devices{} + args.Devices = types.Devices{} newdevs, err := dbDevices(db, name, false) if err != nil { return args, err diff -Nru lxd-2.7/lxd/db_devices.go lxd-2.8/lxd/db_devices.go --- lxd-2.7/lxd/db_devices.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/db_devices.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,7 +6,7 @@ _ "github.com/mattn/go-sqlite3" - "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/lxd/types" ) func dbDeviceTypeToString(t int) (string, error) { @@ -51,7 +51,7 @@ } } -func dbDevicesAdd(tx *sql.Tx, w string, cID int64, devices shared.Devices) error { +func dbDevicesAdd(tx *sql.Tx, w string, cID int64, devices types.Devices) error { // Prepare the devices entry SQL str1 := fmt.Sprintf("INSERT INTO %ss_devices (%s_id, name, type) VALUES (?, ?, ?)", w, w) stmt1, err := tx.Prepare(str1) @@ -102,10 +102,10 @@ return nil } -func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (shared.Device, error) { +func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (types.Device, error) { var query string var key, value string - newdev := shared.Device{} // That's a map[string]string + newdev := types.Device{} // That's a map[string]string inargs := []interface{}{id} outfmt := []interface{}{key, value} @@ -130,7 +130,7 @@ return newdev, nil } -func dbDevices(db *sql.DB, qName string, isprofile bool) (shared.Devices, error) { +func dbDevices(db *sql.DB, qName string, isprofile bool) (types.Devices, error) { var q string if isprofile { q = `SELECT profiles_devices.id, profiles_devices.name, profiles_devices.type @@ -152,7 +152,7 @@ return nil, err } - devices := shared.Devices{} + devices := types.Devices{} for _, r := range results { id = r[0].(int) name = r[1].(string) diff -Nru lxd-2.7/lxd/db.go lxd-2.8/lxd/db.go --- lxd-2.7/lxd/db.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/db.go 2017-01-25 02:50:49.000000000 +0000 @@ -25,15 +25,6 @@ NoSuchObjectError = fmt.Errorf("No such object") ) -// Profile is here to order Profiles. -type Profile struct { - name string - order int -} - -// Profiles will contain a list of all Profiles. -type Profiles []Profile - // CURRENT_SCHEMA contains the current SQLite SQL Schema. const CURRENT_SCHEMA string = ` CREATE TABLE IF NOT EXISTS certificates ( @@ -210,6 +201,11 @@ return err } + // Mark all existing patches as applied + for _, p := range patches { + dbPatchesMarkApplied(db, p.name) + } + err = dbProfileCreateDefault(db) if err != nil { return err diff -Nru lxd-2.7/lxd/db_images.go lxd-2.8/lxd/db_images.go --- lxd-2.7/lxd/db_images.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/db_images.go 2017-01-25 02:50:49.000000000 +0000 @@ -7,7 +7,7 @@ _ "github.com/mattn/go-sqlite3" - "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/osarch" ) @@ -76,27 +76,27 @@ return err } -func dbImageSourceGet(db *sql.DB, imageId int) (int, shared.ImageSource, error) { +func dbImageSourceGet(db *sql.DB, imageId int) (int, api.ImageSource, error) { q := `SELECT id, server, protocol, certificate, alias FROM images_source WHERE image_id=?` id := 0 protocolInt := -1 - result := shared.ImageSource{} + result := api.ImageSource{} arg1 := []interface{}{imageId} arg2 := []interface{}{&id, &result.Server, &protocolInt, &result.Certificate, &result.Alias} err := dbQueryRowScan(db, q, arg1, arg2) if err != nil { if err == sql.ErrNoRows { - return -1, shared.ImageSource{}, NoSuchObjectError + return -1, api.ImageSource{}, NoSuchObjectError } - return -1, shared.ImageSource{}, err + return -1, api.ImageSource{}, err } protocol, found := dbImageSourceProtocol[protocolInt] if !found { - return -1, shared.ImageSource{}, fmt.Errorf("Invalid protocol: %d", protocolInt) + return -1, api.ImageSource{}, fmt.Errorf("Invalid protocol: %d", protocolInt) } result.Protocol = protocol @@ -110,12 +110,12 @@ // pass a shortform and will get the full fingerprint. // There can never be more than one image with a given fingerprint, as it is // enforced by a UNIQUE constraint in the schema. -func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool) (int, *shared.ImageInfo, error) { +func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool) (int, *api.Image, error) { var err error var create, expire, used, upload *time.Time // These hold the db-returned times // The object we'll actually return - image := shared.ImageInfo{} + image := api.Image{} id := -1 arch := -1 @@ -159,27 +159,27 @@ // Some of the dates can be nil in the DB, let's process them. if create != nil { - image.CreationDate = *create + image.CreatedAt = *create } else { - image.CreationDate = time.Time{} + image.CreatedAt = time.Time{} } if expire != nil { - image.ExpiryDate = *expire + image.ExpiresAt = *expire } else { - image.ExpiryDate = time.Time{} + image.ExpiresAt = time.Time{} } if used != nil { - image.LastUsedDate = *used + image.LastUsedAt = *used } else { - image.LastUsedDate = time.Time{} + image.LastUsedAt = time.Time{} } image.Architecture, _ = osarch.ArchitectureName(arch) // The upload date is enforced by NOT NULL in the schema, so it can never be nil. - image.UploadDate = *upload + image.UploadedAt = *upload // Get the properties q := "SELECT key, value FROM images_properties where image_id=?" @@ -209,11 +209,11 @@ return -1, nil, err } - aliases := []shared.ImageAlias{} + aliases := []api.ImageAlias{} for _, r := range results { name = r[0].(string) desc = r[0].(string) - a := shared.ImageAlias{Name: name, Description: desc} + a := api.ImageAlias{Name: name, Description: desc} aliases = append(aliases, a) } @@ -221,7 +221,7 @@ _, source, err := dbImageSourceGet(db, id) if err == nil { - image.Source = &source + image.UpdateSource = &source } return id, &image, nil @@ -245,7 +245,7 @@ return nil } -func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, shared.ImageAliasesEntry, error) { +func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, api.ImageAliasesEntry, error) { q := `SELECT images_aliases.id, images.fingerprint, images_aliases.description FROM images_aliases INNER JOIN images @@ -257,19 +257,24 @@ var fingerprint, description string id := -1 + entry := api.ImageAliasesEntry{} arg1 := []interface{}{name} arg2 := []interface{}{&id, &fingerprint, &description} err := dbQueryRowScan(db, q, arg1, arg2) if err != nil { if err == sql.ErrNoRows { - return -1, shared.ImageAliasesEntry{}, NoSuchObjectError + return -1, entry, NoSuchObjectError } - return -1, shared.ImageAliasesEntry{}, err + return -1, entry, err } - return id, shared.ImageAliasesEntry{Name: name, Target: fingerprint, Description: description}, nil + entry.Name = name + entry.Target = fingerprint + entry.Description = description + + return id, entry, nil } func dbImageAliasRename(db *sql.DB, id int, name string) error { @@ -312,7 +317,7 @@ return err } -func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error { +func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error { arch, err := osarch.ArchitectureId(architecture) if err != nil { arch = 0 @@ -340,7 +345,7 @@ } defer stmt.Close() - _, err = stmt.Exec(fname, sz, publicInt, autoUpdateInt, arch, creationDate, expiryDate, id) + _, err = stmt.Exec(fname, sz, publicInt, autoUpdateInt, arch, createdAt, expiresAt, id) if err != nil { tx.Rollback() return err @@ -369,7 +374,7 @@ return nil } -func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error { +func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error { arch, err := osarch.ArchitectureId(architecture) if err != nil { arch = 0 @@ -397,7 +402,7 @@ } defer stmt.Close() - result, err := stmt.Exec(fp, fname, sz, publicInt, autoUpdateInt, arch, creationDate, expiryDate) + result, err := stmt.Exec(fp, fname, sz, publicInt, autoUpdateInt, arch, createdAt, expiresAt) if err != nil { tx.Rollback() return err diff -Nru lxd-2.7/lxd/db_networks.go lxd-2.8/lxd/db_networks.go --- lxd-2.7/lxd/db_networks.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/db_networks.go 2017-01-25 02:50:49.000000000 +0000 @@ -7,7 +7,7 @@ _ "github.com/mattn/go-sqlite3" - "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" ) func dbNetworks(db *sql.DB) ([]string, error) { @@ -28,11 +28,11 @@ return response, nil } -func dbNetworkGet(db *sql.DB, network string) (int64, *shared.NetworkConfig, error) { +func dbNetworkGet(db *sql.DB, name string) (int64, *api.Network, error) { id := int64(-1) q := "SELECT id FROM networks WHERE name=?" - arg1 := []interface{}{network} + arg1 := []interface{}{name} arg2 := []interface{}{&id} err := dbQueryRowScan(db, q, arg1, arg2) if err != nil { @@ -44,15 +44,17 @@ return -1, nil, err } - return id, &shared.NetworkConfig{ - Name: network, + network := api.Network{ + Name: name, Managed: true, Type: "bridge", - Config: config, - }, nil + } + network.Config = config + + return id, &network, nil } -func dbNetworkGetInterface(db *sql.DB, devName string) (int64, *shared.NetworkConfig, error) { +func dbNetworkGetInterface(db *sql.DB, devName string) (int64, *api.Network, error) { id := int64(-1) name := "" value := "" @@ -85,12 +87,14 @@ return -1, nil, err } - return id, &shared.NetworkConfig{ + network := api.Network{ Name: name, Managed: true, Type: "bridge", - Config: config, - }, nil + } + network.Config = config + + return id, &network, nil } func dbNetworkConfigGet(db *sql.DB, id int64) (map[string]string, error) { diff -Nru lxd-2.7/lxd/db_profiles.go lxd-2.8/lxd/db_profiles.go --- lxd-2.7/lxd/db_profiles.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/db_profiles.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,7 +6,8 @@ _ "github.com/mattn/go-sqlite3" - "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/lxd/types" + "github.com/lxc/lxd/shared/api" ) // dbProfiles returns a string list of profiles. @@ -28,38 +29,41 @@ return response, nil } -func dbProfileGet(db *sql.DB, profile string) (int64, *shared.ProfileConfig, error) { +func dbProfileGet(db *sql.DB, name string) (int64, *api.Profile, error) { id := int64(-1) description := sql.NullString{} q := "SELECT id, description FROM profiles WHERE name=?" - arg1 := []interface{}{profile} + arg1 := []interface{}{name} arg2 := []interface{}{&id, &description} err := dbQueryRowScan(db, q, arg1, arg2) if err != nil { return -1, nil, err } - config, err := dbProfileConfig(db, profile) + config, err := dbProfileConfig(db, name) if err != nil { return -1, nil, err } - devices, err := dbDevices(db, profile, true) + devices, err := dbDevices(db, name, true) if err != nil { return -1, nil, err } - return id, &shared.ProfileConfig{ - Name: profile, - Config: config, - Description: description.String, - Devices: devices, - }, nil + profile := api.Profile{ + Name: name, + } + + profile.Config = config + profile.Description = description.String + profile.Devices = devices + + return id, &profile, nil } func dbProfileCreate(db *sql.DB, profile string, description string, config map[string]string, - devices shared.Devices) (int64, error) { + devices types.Devices) (int64, error) { tx, err := dbBegin(db) if err != nil { @@ -104,7 +108,7 @@ return nil } - id, err := dbProfileCreate(db, "default", "Default LXD profile", map[string]string{}, shared.Devices{}) + _, err := dbProfileCreate(db, "default", "Default LXD profile", map[string]string{}, types.Devices{}) if err != nil { return err } @@ -128,7 +132,7 @@ "type": "disk", "source": "/dev/null", } - devices := map[string]shared.Device{"aadisable": aadisable} + devices := map[string]map[string]string{"aadisable": aadisable} _, err = dbProfileCreate(db, "docker", "Profile supporting docker in containers", config, devices) return err diff -Nru lxd-2.7/lxd/db_test.go lxd-2.8/lxd/db_test.go --- lxd-2.7/lxd/db_test.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/db_test.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,7 +6,9 @@ "testing" "time" + "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/logging" ) @@ -97,6 +99,9 @@ // Make sure there are 0 containers_devices_config entries left. statements = `SELECT count(*) FROM containers_devices_config;` err = db.QueryRow(statements).Scan(&count) + if err != nil { + t.Error(err) + } if count != 0 { t.Errorf("Deleting a container didn't delete the associated container_devices_config! There are %d left", count) @@ -149,6 +154,9 @@ // Make sure there are 0 profiles_devices_config entries left. statements = `SELECT count(*) FROM profiles_devices_config WHERE profile_device_id == 4;` err = db.QueryRow(statements).Scan(&count) + if err != nil { + t.Error(err) + } if count != 0 { t.Errorf("Deleting a profile didn't delete the related profiles_devices_config! There are %d left", count) @@ -184,6 +192,9 @@ // Make sure there are 0 images_properties entries left. statements = `SELECT count(*) FROM images_properties;` err = db.QueryRow(statements).Scan(&count) + if err != nil { + t.Error(err) + } if count != 0 { t.Errorf("Deleting an image didn't delete the related images_properties! There are %d left", count) @@ -283,6 +294,9 @@ // Make sure there are 0 container_profiles entries left. statements = `SELECT count(*) FROM containers_profiles;` err = d.db.QueryRow(statements).Scan(&count) + if err != nil { + t.Error(err) + } if count != 0 { t.Errorf("Deleting a container didn't delete the profile association! There are %d left", count) @@ -390,6 +404,9 @@ // Make sure there are 0 containers_config entries left. statements = `SELECT count(*) FROM containers_config;` err = db.QueryRow(statements).Scan(&count) + if err != nil { + t.Error(err) + } if count != 0 { t.Fatal("updateDb did not delete orphaned child entries after adding ON DELETE CASCADE!") @@ -400,7 +417,7 @@ func Test_dbImageGet_finds_image_for_fingerprint(t *testing.T) { var db *sql.DB var err error - var result *shared.ImageInfo + var result *api.Image db = createTestDb(t) defer db.Close() @@ -419,16 +436,16 @@ t.Fatal("Filename should be set.") } - if result.CreationDate.UTC() != time.Unix(1431547174, 0).UTC() { - t.Fatal(fmt.Sprintf("%s != %s", result.CreationDate, time.Unix(1431547174, 0))) + if result.CreatedAt.UTC() != time.Unix(1431547174, 0).UTC() { + t.Fatal(fmt.Sprintf("%s != %s", result.CreatedAt, time.Unix(1431547174, 0))) } - if result.ExpiryDate.UTC() != time.Unix(1431547175, 0).UTC() { // It was short lived - t.Fatal(fmt.Sprintf("%s != %s", result.ExpiryDate, time.Unix(1431547175, 0))) + if result.ExpiresAt.UTC() != time.Unix(1431547175, 0).UTC() { // It was short lived + t.Fatal(fmt.Sprintf("%s != %s", result.ExpiresAt, time.Unix(1431547175, 0))) } - if result.UploadDate.UTC() != time.Unix(1431547176, 0).UTC() { - t.Fatal(fmt.Sprintf("%s != %s", result.UploadDate, time.Unix(1431547176, 0))) + if result.UploadedAt.UTC() != time.Unix(1431547176, 0).UTC() { + t.Fatal(fmt.Sprintf("%s != %s", result.UploadedAt, time.Unix(1431547176, 0))) } } @@ -580,9 +597,9 @@ func Test_dbDevices_profiles(t *testing.T) { var db *sql.DB var err error - var result shared.Devices - var subresult shared.Device - var expected shared.Device + var result types.Devices + var subresult types.Device + var expected types.Device db = createTestDb(t) defer db.Close() @@ -592,7 +609,7 @@ t.Fatal(err) } - expected = shared.Device{"type": "nic", "devicekey": "devicevalue"} + expected = types.Device{"type": "nic", "devicekey": "devicevalue"} subresult = result["devicename"] for key, value := range expected { @@ -606,9 +623,9 @@ func Test_dbDevices_containers(t *testing.T) { var db *sql.DB var err error - var result shared.Devices - var subresult shared.Device - var expected shared.Device + var result types.Devices + var subresult types.Device + var expected types.Device db = createTestDb(t) defer db.Close() @@ -618,7 +635,7 @@ t.Fatal(err) } - expected = shared.Device{"type": "nic", "configkey": "configvalue"} + expected = types.Device{"type": "nic", "configkey": "configvalue"} subresult = result["somename"] for key, value := range expected { diff -Nru lxd-2.7/lxd/db_update.go lxd-2.8/lxd/db_update.go --- lxd-2.7/lxd/db_update.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/db_update.go 2017-01-25 02:50:49.000000000 +0000 @@ -34,40 +34,40 @@ */ var dbUpdates = []dbUpdate{ - dbUpdate{version: 1, run: dbUpdateFromV0}, - dbUpdate{version: 2, run: dbUpdateFromV1}, - dbUpdate{version: 3, run: dbUpdateFromV2}, - dbUpdate{version: 4, run: dbUpdateFromV3}, - dbUpdate{version: 5, run: dbUpdateFromV4}, - dbUpdate{version: 6, run: dbUpdateFromV5}, - dbUpdate{version: 7, run: dbUpdateFromV6}, - dbUpdate{version: 8, run: dbUpdateFromV7}, - dbUpdate{version: 9, run: dbUpdateFromV8}, - dbUpdate{version: 10, run: dbUpdateFromV9}, - dbUpdate{version: 11, run: dbUpdateFromV10}, - dbUpdate{version: 12, run: dbUpdateFromV11}, - dbUpdate{version: 13, run: dbUpdateFromV12}, - dbUpdate{version: 14, run: dbUpdateFromV13}, - dbUpdate{version: 15, run: dbUpdateFromV14}, - dbUpdate{version: 16, run: dbUpdateFromV15}, - dbUpdate{version: 17, run: dbUpdateFromV16}, - dbUpdate{version: 18, run: dbUpdateFromV17}, - dbUpdate{version: 19, run: dbUpdateFromV18}, - dbUpdate{version: 20, run: dbUpdateFromV19}, - dbUpdate{version: 21, run: dbUpdateFromV20}, - dbUpdate{version: 22, run: dbUpdateFromV21}, - dbUpdate{version: 23, run: dbUpdateFromV22}, - dbUpdate{version: 24, run: dbUpdateFromV23}, - dbUpdate{version: 25, run: dbUpdateFromV24}, - dbUpdate{version: 26, run: dbUpdateFromV25}, - dbUpdate{version: 27, run: dbUpdateFromV26}, - dbUpdate{version: 28, run: dbUpdateFromV27}, - dbUpdate{version: 29, run: dbUpdateFromV28}, - dbUpdate{version: 30, run: dbUpdateFromV29}, - dbUpdate{version: 31, run: dbUpdateFromV30}, - dbUpdate{version: 32, run: dbUpdateFromV31}, - dbUpdate{version: 33, run: dbUpdateFromV32}, - dbUpdate{version: 34, run: dbUpdateFromV33}, + {version: 1, run: dbUpdateFromV0}, + {version: 2, run: dbUpdateFromV1}, + {version: 3, run: dbUpdateFromV2}, + {version: 4, run: dbUpdateFromV3}, + {version: 5, run: dbUpdateFromV4}, + {version: 6, run: dbUpdateFromV5}, + {version: 7, run: dbUpdateFromV6}, + {version: 8, run: dbUpdateFromV7}, + {version: 9, run: dbUpdateFromV8}, + {version: 10, run: dbUpdateFromV9}, + {version: 11, run: dbUpdateFromV10}, + {version: 12, run: dbUpdateFromV11}, + {version: 13, run: dbUpdateFromV12}, + {version: 14, run: dbUpdateFromV13}, + {version: 15, run: dbUpdateFromV14}, + {version: 16, run: dbUpdateFromV15}, + {version: 17, run: dbUpdateFromV16}, + {version: 18, run: dbUpdateFromV17}, + {version: 19, run: dbUpdateFromV18}, + {version: 20, run: dbUpdateFromV19}, + {version: 21, run: dbUpdateFromV20}, + {version: 22, run: dbUpdateFromV21}, + {version: 23, run: dbUpdateFromV22}, + {version: 24, run: dbUpdateFromV23}, + {version: 25, run: dbUpdateFromV24}, + {version: 26, run: dbUpdateFromV25}, + {version: 27, run: dbUpdateFromV26}, + {version: 28, run: dbUpdateFromV27}, + {version: 29, run: dbUpdateFromV28}, + {version: 30, run: dbUpdateFromV29}, + {version: 31, run: dbUpdateFromV30}, + {version: 32, run: dbUpdateFromV31}, + {version: 33, run: dbUpdateFromV32}, + {version: 34, run: dbUpdateFromV33}, } type dbUpdate struct { diff -Nru lxd-2.7/lxd/devices.go lxd-2.8/lxd/devices.go --- lxd-2.7/lxd/devices.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/devices.go 2017-01-25 02:50:49.000000000 +0000 @@ -187,6 +187,10 @@ tmpGpu.minor = minorInt isCard, err := regexp.MatchString("^card[0-9]+", drmEnt.Name()) + if err != nil { + continue + } + if isCard { // If it is a card it's minor number will be its id. tmpGpu.id = strconv.Itoa(minorInt) @@ -1210,7 +1214,7 @@ "devnum": "", } - for k, _ := range values { + for k := range values { v, err := ioutil.ReadFile(path.Join(p, k)) if err != nil { return nil, err diff -Nru lxd-2.7/lxd/devlxd.go lxd-2.8/lxd/devlxd.go --- lxd-2.7/lxd/devlxd.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/devlxd.go 2017-01-25 02:50:49.000000000 +0000 @@ -43,7 +43,7 @@ var configGet = devLxdHandler{"/1.0/config", func(c container, r *http.Request) *devLxdResponse { filtered := []string{} - for k, _ := range c.ExpandedConfig() { + for k := range c.ExpandedConfig() { if strings.HasPrefix(k, "user.") { filtered = append(filtered, fmt.Sprintf("/1.0/config/%s", k)) } @@ -71,10 +71,10 @@ }} var handlers = []devLxdHandler{ - devLxdHandler{"/", func(c container, r *http.Request) *devLxdResponse { + {"/", func(c container, r *http.Request) *devLxdResponse { return okResponse([]string{"/1.0"}, "json") }}, - devLxdHandler{"/1.0", func(c container, r *http.Request) *devLxdResponse { + {"/1.0", func(c container, r *http.Request) *devLxdResponse { return okResponse(shared.Jmap{"api_version": version.APIVersion}, "json") }}, configGet, @@ -269,7 +269,7 @@ /* * As near as I can tell, there is no nice way of extracting an underlying * net.Conn (or in our case, net.UnixConn) from an http.Request or - * ResponseWriter without hijacking it [1]. Since we want to send and recieve + * ResponseWriter without hijacking it [1]. Since we want to send and receive * unix creds to figure out which container this request came from, we need to * do this. * @@ -301,7 +301,7 @@ * an lxc monitor process and extract its name from there. * * 2. If this fails, it may be that someone did an `lxc exec foo bash`, - * so the process isn't actually a decendant of the container's + * so the process isn't actually a descendant of the container's * init. In this case we just look through all the containers until * we find an init with a matching pid namespace. This is probably * uncommon, so hopefully the slowness won't hurt us. diff -Nru lxd-2.7/lxd/images.go lxd-2.8/lxd/images.go --- lxd-2.7/lxd/images.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/images.go 2017-01-25 02:50:49.000000000 +0000 @@ -23,6 +23,7 @@ "gopkg.in/yaml.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/logging" "github.com/lxc/lxd/shared/osarch" "github.com/lxc/lxd/shared/version" @@ -34,7 +35,7 @@ The CPU and I/O load of publish is such that running multiple ones in parallel takes longer than running them serially. - Additionaly, publishing the same container or container snapshot + Additionally, publishing the same container or container snapshot twice would lead to storage problem, not to mention a conflict at the end for whichever finishes last. */ var imagePublishLock sync.Mutex @@ -208,15 +209,6 @@ Properties map[string]string `yaml:"properties"` } -type imagePostReq struct { - Filename string `json:"filename"` - Public bool `json:"public"` - Source map[string]string `json:"source"` - Properties map[string]string `json:"properties"` - AutoUpdate bool `json:"auto_update"` - CompressionAlgorithm string `json:"compression_algorithm"` -} - type imageMetadata struct { Architecture string `yaml:"architecture"` CreationDate int64 `yaml:"creation_date"` @@ -229,8 +221,8 @@ * This function takes a container or snapshot from the local image server and * exports it as an image. */ -func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq, - builddir string) (info shared.ImageInfo, err error) { +func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, + builddir string) (info api.Image, err error) { info.Properties = map[string]string{} name := req.Source["name"] @@ -327,7 +319,7 @@ return info, nil } -func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error { +func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) error { var err error var hash string @@ -356,7 +348,7 @@ // Update the DB record if needed if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 { - err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties) + err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties) if err != nil { return err } @@ -370,7 +362,7 @@ return nil } -func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error { +func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) error { var err error if req.Source["url"] == "" { @@ -429,7 +421,7 @@ } if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 { - err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties) + err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties) if err != nil { return err } @@ -444,7 +436,7 @@ } func getImgPostInfo(d *Daemon, r *http.Request, - builddir string, post *os.File) (info shared.ImageInfo, err error) { + builddir string, post *os.File) (info api.Image, err error) { var imageMeta *imageMetadata logger := logging.AddContext(shared.Log, log.Ctx{"function": "getImgPostInfo"}) @@ -618,8 +610,8 @@ } info.Architecture = imageMeta.Architecture - info.CreationDate = time.Unix(imageMeta.CreationDate, 0) - info.ExpiryDate = time.Unix(imageMeta.ExpiryDate, 0) + info.CreatedAt = time.Unix(imageMeta.CreationDate, 0) + info.ExpiresAt = time.Unix(imageMeta.ExpiryDate, 0) info.Properties = imageMeta.Properties if len(propHeaders) > 0 { @@ -634,7 +626,7 @@ return info, nil } -func imageBuildFromInfo(d *Daemon, info shared.ImageInfo) (metadata map[string]string, err error) { +func imageBuildFromInfo(d *Daemon, info api.Image) (metadata map[string]string, err error) { err = d.Storage.ImageCreate(info.Fingerprint) if err != nil { os.Remove(shared.VarPath("images", info.Fingerprint)) @@ -651,8 +643,8 @@ info.Public, info.AutoUpdate, info.Architecture, - info.CreationDate, - info.ExpiryDate, + info.CreatedAt, + info.ExpiresAt, info.Properties) if err != nil { return metadata, err @@ -702,7 +694,7 @@ decoder := json.NewDecoder(post) imageUpload := false - req := imagePostReq{} + req := api.ImagesPost{} err = decoder.Decode(&req) if err != nil { if r.Header.Get("Content-Type") == "application/json" { @@ -718,7 +710,7 @@ // Begin background operation run := func(op *operation) error { - var info shared.ImageInfo + var info api.Image // Setup the cleanup function defer cleanup(builddir, post) @@ -825,7 +817,7 @@ } resultString := make([]string, len(results)) - resultMap := make([]*shared.ImageInfo, len(results)) + resultMap := make([]*api.Image, len(results)) i := 0 for _, name := range results { if !recursion { @@ -903,7 +895,7 @@ continue } - err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedDate) + err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedAt) if err != nil { shared.LogError("Error setting last use date", log.Ctx{"err": err, "fp": hash}) continue @@ -1007,7 +999,7 @@ return OperationResponse(op) } -func doImageGet(d *Daemon, fingerprint string, public bool) (*shared.ImageInfo, Response) { +func doImageGet(d *Daemon, fingerprint string, public bool) (*api.Image, Response) { _, imgInfo, err := dbImageGet(d.db, fingerprint, public, false) if err != nil { return nil, SmartError(err) @@ -1064,12 +1056,6 @@ return SyncResponseETag(true, info, etag) } -type imagePutReq struct { - Properties map[string]string `json:"properties"` - Public bool `json:"public"` - AutoUpdate bool `json:"auto_update"` -} - func imagePut(d *Daemon, r *http.Request) Response { // Get current value fingerprint := mux.Vars(r)["fingerprint"] @@ -1085,12 +1071,12 @@ return PreconditionFailed(err) } - req := imagePutReq{} + req := api.ImagePut{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } - err = dbImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, req.Properties) + err = dbImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties) if err != nil { return SmartError(err) } @@ -1126,7 +1112,7 @@ return BadRequest(err) } - req := imagePutReq{} + req := api.ImagePut{} if err := json.NewDecoder(rdr2).Decode(&req); err != nil { return BadRequest(err) } @@ -1156,7 +1142,7 @@ info.Properties = properties } - err = dbImageUpdate(d.db, id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties) + err = dbImageUpdate(d.db, id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties) if err != nil { return SmartError(err) } @@ -1166,19 +1152,8 @@ var imageCmd = Command{name: "images/{fingerprint}", untrustedGet: true, get: imageGet, put: imagePut, delete: imageDelete, patch: imagePatch} -type aliasPostReq struct { - Name string `json:"name"` - Description string `json:"description"` - Target string `json:"target"` -} - -type aliasPutReq struct { - Description string `json:"description"` - Target string `json:"target"` -} - func aliasesPost(d *Daemon, r *http.Request) Response { - req := aliasPostReq{} + req := api.ImageAliasesPost{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } @@ -1218,7 +1193,7 @@ return BadRequest(err) } responseStr := []string{} - responseMap := shared.ImageAliases{} + responseMap := []api.ImageAliasesEntry{} for _, res := range results { name = res[0].(string) if !recursion { @@ -1281,7 +1256,7 @@ return PreconditionFailed(err) } - req := aliasPutReq{} + req := api.ImageAliasesEntryPut{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } @@ -1358,7 +1333,7 @@ func aliasPost(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] - req := aliasPostReq{} + req := api.ImageAliasesEntryPost{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } diff -Nru lxd-2.7/lxd/main_activateifneeded.go lxd-2.8/lxd/main_activateifneeded.go --- lxd-2.7/lxd/main_activateifneeded.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_activateifneeded.go 2017-01-25 02:50:49.000000000 +0000 @@ -11,7 +11,7 @@ // Don't start a full daemon, we just need DB access d := &Daemon{ imagesDownloading: map[string]chan bool{}, - imagesDownloadingLock: sync.RWMutex{}, + imagesDownloadingLock: sync.Mutex{}, lxcpath: shared.VarPath("containers"), } diff -Nru lxd-2.7/lxd/main_callhook.go lxd-2.8/lxd/main_callhook.go --- lxd-2.7/lxd/main_callhook.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_callhook.go 2017-01-25 02:50:49.000000000 +0000 @@ -7,6 +7,7 @@ "time" "github.com/lxc/lxd" + "github.com/lxc/lxd/shared/api" ) func cmdCallHook(args []string) error { @@ -52,7 +53,7 @@ return } - _, err = lxd.HoistResponse(raw, lxd.Sync) + _, err = lxd.HoistResponse(raw, api.SyncResponse) if err != nil { hook <- err return diff -Nru lxd-2.7/lxd/main_forkexec.go lxd-2.8/lxd/main_forkexec.go --- lxd-2.7/lxd/main_forkexec.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_forkexec.go 2017-01-25 02:50:49.000000000 +0000 @@ -63,7 +63,7 @@ cmd := []string{} section := "" - for _, arg := range args[5:len(args)] { + for _, arg := range args[5:] { // The "cmd" section must come last as it may contain a -- if arg == "--" && section != "cmd" { section = "" @@ -119,16 +119,14 @@ exCode, ok := procState.Sys().(syscall.WaitStatus) if ok { + if exCode.Signaled() { + // COMMENT(brauner): 128 + n == Fatal error signal "n" + return 128 + int(exCode.Signal()), nil + } + if exCode.Exited() { return exCode.ExitStatus(), nil } - // Backwards compatible behavior. Report success when we exited - // due to a signal. Otherwise this may break Jenkins, e.g. when - // lxc exec foo reboot receives SIGTERM and exCode.Exitstats() - // would report -1. - if exCode.Signaled() { - return 0, nil - } } return -1, fmt.Errorf("Command failed") diff -Nru lxd-2.7/lxd/main_forkgetnet.go lxd-2.8/lxd/main_forkgetnet.go --- lxd-2.7/lxd/main_forkgetnet.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_forkgetnet.go 2017-01-25 02:50:49.000000000 +0000 @@ -8,11 +8,11 @@ "strconv" "strings" - "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" ) func cmdForkGetNet() error { - networks := map[string]shared.ContainerStateNetwork{} + networks := map[string]api.ContainerStateNetwork{} interfaces, err := net.Interfaces() if err != nil { @@ -75,9 +75,9 @@ netState = "up" } - network := shared.ContainerStateNetwork{ - Addresses: []shared.ContainerStateNetworkAddress{}, - Counters: shared.ContainerStateNetworkCounters{}, + network := api.ContainerStateNetwork{ + Addresses: []api.ContainerStateNetworkAddress{}, + Counters: api.ContainerStateNetworkCounters{}, Hwaddr: netIf.HardwareAddr.String(), Mtu: netIf.MTU, State: netState, @@ -114,7 +114,7 @@ scope = "link" } - address := shared.ContainerStateNetworkAddress{} + address := api.ContainerStateNetworkAddress{} address.Family = family address.Address = fields[0] address.Netmask = fields[1] diff -Nru lxd-2.7/lxd/main_import.go lxd-2.8/lxd/main_import.go --- lxd-2.7/lxd/main_import.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_import.go 2017-01-25 02:50:49.000000000 +0000 @@ -5,6 +5,7 @@ "net/http" "github.com/lxc/lxd" + "github.com/lxc/lxd/shared/api" ) func cmdImport(args []string) error { @@ -23,7 +24,7 @@ } raw, err := c.Http.Do(req) - _, err = lxd.HoistResponse(raw, lxd.Sync) + _, err = lxd.HoistResponse(raw, api.SyncResponse) if err != nil { return err } diff -Nru lxd-2.7/lxd/main_init.go lxd-2.8/lxd/main_init.go --- lxd-2.7/lxd/main_init.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_init.go 2017-01-25 02:50:49.000000000 +0000 @@ -335,7 +335,7 @@ if askBool("Would you like to create a new network bridge (yes/no) [default=yes]? ", "yes") { bridgeName = askString("What should the new bridge be called [default=lxdbr0]? ", "lxdbr0", networkValidName) - bridgeIPv4 = askString("What IPv4 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error { + bridgeIPv4 = askString("What IPv4 address should be used (CIDR subnet notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error { if shared.StringInSlice(value, []string{"auto", "none"}) { return nil } @@ -346,7 +346,7 @@ bridgeIPv4Nat = askBool("Would you like LXD to NAT IPv4 traffic on your bridge? [default=yes]? ", "yes") } - bridgeIPv6 = askString("What IPv6 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error { + bridgeIPv6 = askString("What IPv6 address should be used (CIDR subnet notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error { if shared.StringInSlice(value, []string{"auto", "none"}) { return nil } diff -Nru lxd-2.7/lxd/main_ready.go lxd-2.8/lxd/main_ready.go --- lxd-2.7/lxd/main_ready.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_ready.go 2017-01-25 02:50:49.000000000 +0000 @@ -4,6 +4,7 @@ "net/http" "github.com/lxc/lxd" + "github.com/lxc/lxd/shared/api" ) func cmdReady() error { @@ -22,7 +23,7 @@ return err } - _, err = lxd.HoistResponse(raw, lxd.Sync) + _, err = lxd.HoistResponse(raw, api.SyncResponse) if err != nil { return err } diff -Nru lxd-2.7/lxd/main_shutdown.go lxd-2.8/lxd/main_shutdown.go --- lxd-2.7/lxd/main_shutdown.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_shutdown.go 2017-01-25 02:50:49.000000000 +0000 @@ -34,7 +34,7 @@ monitor := make(chan error, 1) go func() { - monitor <- c.Monitor(nil, func(m interface{}) {}) + monitor <- c.Monitor(nil, func(m interface{}) {}, nil) }() select { diff -Nru lxd-2.7/lxd/main_test.go lxd-2.8/lxd/main_test.go --- lxd-2.7/lxd/main_test.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_test.go 2017-01-25 02:50:49.000000000 +0000 @@ -16,7 +16,7 @@ d := &Daemon{ MockMode: true, imagesDownloading: map[string]chan bool{}, - imagesDownloadingLock: sync.RWMutex{}, + imagesDownloadingLock: sync.Mutex{}, } if err := d.Init(); err != nil { @@ -24,8 +24,8 @@ } d.IdmapSet = &shared.IdmapSet{Idmap: []shared.IdmapEntry{ - shared.IdmapEntry{Isuid: true, Hostid: 100000, Nsid: 0, Maprange: 500000}, - shared.IdmapEntry{Isgid: true, Hostid: 100000, Nsid: 0, Maprange: 500000}, + {Isuid: true, Hostid: 100000, Nsid: 0, Maprange: 500000}, + {Isgid: true, Hostid: 100000, Nsid: 0, Maprange: 500000}, }} // Call this after Init so we have a log object. diff -Nru lxd-2.7/lxd/main_waitready.go lxd-2.8/lxd/main_waitready.go --- lxd-2.7/lxd/main_waitready.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/main_waitready.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,6 +6,7 @@ "time" "github.com/lxc/lxd" + "github.com/lxc/lxd/shared/api" ) func cmdWaitReady() error { @@ -38,7 +39,7 @@ continue } - _, err = lxd.HoistResponse(raw, lxd.Sync) + _, err = lxd.HoistResponse(raw, api.SyncResponse) if err != nil { time.Sleep(500 * time.Millisecond) continue diff -Nru lxd-2.7/lxd/migrate.go lxd-2.8/lxd/migrate.go --- lxd-2.7/lxd/migrate.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/migrate.go 2017-01-25 02:50:49.000000000 +0000 @@ -793,7 +793,8 @@ } else { fsConn = c.src.fsConn } - if err := mySink(live, c.src.container, header.Snapshots, fsConn, srcIdmap, migrateOp); err != nil { + + if err := mySink(live, c.src.container, snapshots, fsConn, srcIdmap, migrateOp); err != nil { fsTransfer <- err return } diff -Nru lxd-2.7/lxd/networks.go lxd-2.8/lxd/networks.go --- lxd-2.7/lxd/networks.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/networks.go 2017-01-25 02:50:49.000000000 +0000 @@ -16,6 +16,7 @@ log "gopkg.in/inconshreveable/log15.v2" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" ) @@ -33,7 +34,7 @@ } resultString := []string{} - resultMap := []shared.NetworkConfig{} + resultMap := []api.Network{} for _, iface := range ifs { if recursion == 0 { resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", version.APIVersion, iface)) @@ -54,7 +55,7 @@ } func networksPost(d *Daemon, r *http.Request) Response { - req := shared.NetworkConfig{} + req := api.NetworksPost{} // Parse the request err := json.NewDecoder(r.Body).Decode(&req) @@ -161,18 +162,18 @@ return SyncResponseETag(true, &n, etag) } -func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) { +func doNetworkGet(d *Daemon, name string) (api.Network, error) { // Get some information osInfo, _ := net.InterfaceByName(name) _, dbInfo, _ := dbNetworkGet(d.db, name) // Sanity check if osInfo == nil && dbInfo == nil { - return shared.NetworkConfig{}, os.ErrNotExist + return api.Network{}, os.ErrNotExist } // Prepare the response - n := shared.NetworkConfig{} + n := api.Network{} n.Name = name n.UsedBy = []string{} n.Config = map[string]string{} @@ -180,13 +181,13 @@ // Look for containers using the interface cts, err := dbContainersList(d.db, cTypeRegular) if err != nil { - return shared.NetworkConfig{}, err + return api.Network{}, err } for _, ct := range cts { c, err := containerLoadByName(d, ct) if err != nil { - return shared.NetworkConfig{}, err + return api.Network{}, err } if networkIsInUse(c, n.Name) { @@ -204,6 +205,8 @@ } n.Type = "bridge" + } else if shared.PathExists(fmt.Sprintf("/proc/net/vlan/%s", n.Name)) { + n.Type = "vlan" } else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/device", n.Name)) { n.Type = "physical" } else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bonding", n.Name)) { @@ -245,7 +248,7 @@ func networkPost(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] - req := shared.NetworkConfig{} + req := api.NetworkPost{} // Parse the request err := json.NewDecoder(r.Body).Decode(&req) @@ -305,7 +308,7 @@ return PreconditionFailed(err) } - req := shared.NetworkConfig{} + req := api.NetworkPut{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } @@ -330,7 +333,7 @@ return PreconditionFailed(err) } - req := shared.NetworkConfig{} + req := api.NetworkPut{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } @@ -370,7 +373,7 @@ return NotFound } - err = n.Update(shared.NetworkConfig{Config: newConfig}) + err = n.Update(api.NetworkPut{Config: newConfig}) if err != nil { return SmartError(err) } @@ -522,7 +525,7 @@ func (n *network) Start() error { // Create directory if !shared.PathExists(shared.VarPath("networks", n.name)) { - err := os.MkdirAll(shared.VarPath("networks", n.name), 0700) + err := os.MkdirAll(shared.VarPath("networks", n.name), 0711) if err != nil { return err } @@ -665,12 +668,12 @@ if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) { // Setup basic iptables overrides rules := [][]string{ - []string{"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "67", "-j", "ACCEPT"}, - []string{"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, - []string{"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, - []string{"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "67", "-j", "ACCEPT"}, - []string{"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "53", "-j", "ACCEPT"}, - []string{"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "tcp", "--sport", "53", "-j", "ACCEPT"}} + {"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "67", "-j", "ACCEPT"}, + {"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, + {"ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, + {"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "67", "-j", "ACCEPT"}, + {"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "53", "-j", "ACCEPT"}, + {"ipv4", n.name, "", "OUTPUT", "-o", n.name, "-p", "tcp", "--sport", "53", "-j", "ACCEPT"}} for _, rule := range rules { err = networkIptablesPrepend(rule[0], rule[1], rule[2], rule[3], rule[4:]...) @@ -719,7 +722,7 @@ } // Start building the dnsmasq command line - dnsmasqCmd := []string{"dnsmasq", "-u", "root", "--strict-order", "--bind-interfaces", + dnsmasqCmd := []string{"dnsmasq", "--strict-order", "--bind-interfaces", fmt.Sprintf("--pid-file=%s", shared.VarPath("networks", n.name, "dnsmasq.pid")), "--except-interface=lo", fmt.Sprintf("--interface=%s", n.name)} @@ -836,12 +839,12 @@ // Setup basic iptables overrides rules := [][]string{ - []string{"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "546", "-j", "ACCEPT"}, - []string{"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, - []string{"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, - []string{"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "546", "-j", "ACCEPT"}, - []string{"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "53", "-j", "ACCEPT"}, - []string{"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "tcp", "--sport", "53", "-j", "ACCEPT"}} + {"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "546", "-j", "ACCEPT"}, + {"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "53", "-j", "ACCEPT"}, + {"ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"}, + {"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "546", "-j", "ACCEPT"}, + {"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "udp", "--sport", "53", "-j", "ACCEPT"}, + {"ipv6", n.name, "", "OUTPUT", "-o", n.name, "-p", "tcp", "--sport", "53", "-j", "ACCEPT"}} for _, rule := range rules { err = networkIptablesPrepend(rule[0], rule[1], rule[2], rule[3], rule[4:]...) @@ -1122,12 +1125,12 @@ // Configure dnsmasq if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) || !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) { + // Setup the dnsmasq domain dnsDomain := n.config["dns.domain"] if dnsDomain == "" { dnsDomain = "lxd" } - // Setup the dnsmasq domain if n.config["dns.mode"] != "none" { dnsmasqCmd = append(dnsmasqCmd, []string{"-s", dnsDomain, "-S", fmt.Sprintf("/%s/", dnsDomain)}...) } @@ -1143,13 +1146,24 @@ // Create DHCP hosts file if !shared.PathExists(shared.VarPath("networks", n.name, "dnsmasq.hosts")) { - err = ioutil.WriteFile(shared.VarPath("networks", n.name, "dnsmasq.hosts"), []byte(""), 0) + err = ioutil.WriteFile(shared.VarPath("networks", n.name, "dnsmasq.hosts"), []byte(""), 0644) if err != nil { return err } } - // Start dnsmasq (occasionaly races, try a few times) + // Attempt to drop privileges + for _, user := range []string{"lxd", "nobody"} { + _, err := shared.UserId(user) + if err != nil { + continue + } + + dnsmasqCmd = append(dnsmasqCmd, []string{"-u", user}...) + break + } + + // Start dnsmasq (occasionally races, try a few times) output, err := tryExec(dnsmasqCmd[0], dnsmasqCmd[1:]...) if err != nil { return fmt.Errorf("Failed to run: %s: %s", strings.Join(dnsmasqCmd, " "), strings.TrimSpace(string(output))) @@ -1234,7 +1248,7 @@ return nil } -func (n *network) Update(newNetwork shared.NetworkConfig) error { +func (n *network) Update(newNetwork api.NetworkPut) error { err := networkFillAuto(newNetwork.Config) if err != nil { return err @@ -1262,7 +1276,7 @@ // Diff the configurations changedConfig := []string{} userOnly := true - for key, _ := range oldConfig { + for key := range oldConfig { if oldConfig[key] != newConfig[key] { if !strings.HasPrefix(key, "user.") { userOnly = false @@ -1274,7 +1288,7 @@ } } - for key, _ := range newConfig { + for key := range newConfig { if oldConfig[key] != newConfig[key] { if !strings.HasPrefix(key, "user.") { userOnly = false diff -Nru lxd-2.7/lxd/networks_utils.go lxd-2.8/lxd/networks_utils.go --- lxd-2.7/lxd/networks_utils.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/networks_utils.go 2017-01-25 02:50:49.000000000 +0000 @@ -151,7 +151,7 @@ func networkGetTunnels(config map[string]string) []string { tunnels := []string{} - for k, _ := range config { + for k := range config { if !strings.HasPrefix(k, "tunnel.") { continue } @@ -617,6 +617,17 @@ } pid := strings.TrimSpace(string(content)) + // Check for empty string + if pid == "" { + os.Remove(pidPath) + + if reload { + return fmt.Errorf("dnsmasq isn't running") + } + + return nil + } + // Check if the process still exists if !shared.PathExists(fmt.Sprintf("/proc/%s", pid)) { os.Remove(pidPath) @@ -799,3 +810,59 @@ return ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/%s", path), []byte(value), 0) } + +func networkClearLease(d *Daemon, network string, hwaddr string) error { + leaseFile := shared.VarPath("networks", network, "dnsmasq.leases") + + // Check that we are in fact running a dnsmasq for the network + if !shared.PathExists(leaseFile) { + return nil + } + + // Restart the network when we're done here + n, err := networkLoadByName(d, network) + if err != nil { + return err + } + defer n.Start() + + // Stop dnsmasq + err = networkKillDnsmasq(network, false) + if err != nil { + return err + } + + // Mangle the lease file + leases, err := ioutil.ReadFile(leaseFile) + if err != nil { + return err + } + + fd, err := os.Create(leaseFile) + if err != nil { + return err + } + + for _, lease := range strings.Split(string(leases), "\n") { + if lease == "" { + continue + } + + fields := strings.Fields(lease) + if len(fields) > 2 && strings.ToLower(fields[1]) == strings.ToLower(hwaddr) { + continue + } + + _, err := fd.WriteString(fmt.Sprintf("%s\n", lease)) + if err != nil { + return err + } + } + + err = fd.Close() + if err != nil { + return err + } + + return nil +} diff -Nru lxd-2.7/lxd/operations.go lxd-2.8/lxd/operations.go --- lxd-2.7/lxd/operations.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/operations.go 2017-01-25 02:50:49.000000000 +0000 @@ -12,6 +12,7 @@ "github.com/pborman/uuid" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" ) @@ -39,7 +40,7 @@ class operationClass createdAt time.Time updatedAt time.Time - status shared.StatusCode + status api.StatusCode url string resources map[string][]string metadata map[string]interface{} @@ -97,21 +98,21 @@ } func (op *operation) Run() (chan error, error) { - if op.status != shared.Pending { + if op.status != api.Pending { return nil, fmt.Errorf("Only pending operations can be started") } chanRun := make(chan error, 1) op.lock.Lock() - op.status = shared.Running + op.status = api.Running if op.onRun != nil { go func(op *operation, chanRun chan error) { err := op.onRun(op) if err != nil { op.lock.Lock() - op.status = shared.Failure + op.status = api.Failure op.err = SmartError(err).String() op.lock.Unlock() op.done() @@ -125,7 +126,7 @@ } op.lock.Lock() - op.status = shared.Success + op.status = api.Success op.lock.Unlock() op.done() chanRun <- nil @@ -147,7 +148,7 @@ } func (op *operation) Cancel() (chan error, error) { - if op.status != shared.Running { + if op.status != api.Running { return nil, fmt.Errorf("Only running operations can be cancelled") } @@ -159,11 +160,11 @@ op.lock.Lock() oldStatus := op.status - op.status = shared.Cancelling + op.status = api.Cancelling op.lock.Unlock() if op.onCancel != nil { - go func(op *operation, oldStatus shared.StatusCode, chanCancel chan error) { + go func(op *operation, oldStatus api.StatusCode, chanCancel chan error) { err := op.onCancel(op) if err != nil { op.lock.Lock() @@ -178,7 +179,7 @@ } op.lock.Lock() - op.status = shared.Cancelled + op.status = api.Cancelled op.lock.Unlock() op.done() chanCancel <- nil @@ -195,7 +196,7 @@ if op.onCancel == nil { op.lock.Lock() - op.status = shared.Cancelled + op.status = api.Cancelled op.lock.Unlock() op.done() chanCancel <- nil @@ -213,7 +214,7 @@ return nil, fmt.Errorf("Only websocket operations can be connected") } - if op.status != shared.Running { + if op.status != api.Running { return nil, fmt.Errorf("Only running operations can be connected") } @@ -245,7 +246,7 @@ return op.onCancel != nil || op.class == operationClassToken } -func (op *operation) Render() (string, *shared.Operation, error) { +func (op *operation) Render() (string, *api.Operation, error) { // Setup the resource URLs resources := op.resources if resources != nil { @@ -260,17 +261,15 @@ resources = tmpResources } - md := shared.Jmap(op.metadata) - - return op.url, &shared.Operation{ - Id: op.id, + return op.url, &api.Operation{ + ID: op.id, Class: op.class.String(), CreatedAt: op.createdAt, UpdatedAt: op.updatedAt, Status: op.status.String(), StatusCode: op.status, Resources: resources, - Metadata: &md, + Metadata: op.metadata, MayCancel: op.mayCancel(), Err: op.err, }, nil @@ -308,7 +307,7 @@ } func (op *operation) UpdateResources(opResources map[string][]string) error { - if op.status != shared.Pending && op.status != shared.Running { + if op.status != api.Pending && op.status != api.Running { return fmt.Errorf("Only pending or running operations can be updated") } @@ -329,7 +328,7 @@ } func (op *operation) UpdateMetadata(opMetadata interface{}) error { - if op.status != shared.Pending && op.status != shared.Running { + if op.status != api.Pending && op.status != api.Running { return fmt.Errorf("Only pending or running operations can be updated") } @@ -365,7 +364,7 @@ op.class = opClass op.createdAt = time.Now() op.updatedAt = op.createdAt - op.status = shared.Pending + op.status = api.Pending op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id) op.resources = opResources op.chanDone = make(chan error) @@ -472,7 +471,7 @@ _, ok := md[status] if !ok { if recursion { - md[status] = make([]*shared.Operation, 0) + md[status] = make([]*api.Operation, 0) } else { md[status] = make([]string, 0) } @@ -488,7 +487,7 @@ continue } - md[status] = append(md[status].([]*shared.Operation), body) + md[status] = append(md[status].([]*api.Operation), body) } return SyncResponse(true, md) @@ -544,7 +543,7 @@ return fmt.Sprintf("error: %s", err) } - return md.Id + return md.ID } func operationAPIWebsocketGet(d *Daemon, r *http.Request) Response { diff -Nru lxd-2.7/lxd/patches.go lxd-2.8/lxd/patches.go --- lxd-2.7/lxd/patches.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/patches.go 2017-01-25 02:50:49.000000000 +0000 @@ -1,6 +1,7 @@ package main import ( + "os" "strings" "github.com/lxc/lxd/shared" @@ -26,8 +27,9 @@ */ var patches = []patch{ - patch{name: "invalid_profile_names", run: patchInvalidProfileNames}, - patch{name: "leftover_profile_config", run: patchLeftoverProfileConfig}, + {name: "invalid_profile_names", run: patchInvalidProfileNames}, + {name: "leftover_profile_config", run: patchLeftoverProfileConfig}, + {name: "network_permissions", run: patchNetworkPermissions}, } type patch struct { @@ -100,6 +102,40 @@ if err != nil { return err } + } + } + + return nil +} + +func patchNetworkPermissions(name string, d *Daemon) error { + // Get the list of networks + networks, err := dbNetworks(d.db) + if err != nil { + return err + } + + // Fix the permissions + err = os.Chmod(shared.VarPath("networks"), 0711) + if err != nil { + return err + } + + for _, network := range networks { + if !shared.PathExists(shared.VarPath("networks", network)) { + continue + } + + err = os.Chmod(shared.VarPath("networks", network), 0711) + if err != nil { + return err + } + + if shared.PathExists(shared.VarPath("networks", network, "dnsmasq.hosts")) { + err = os.Chmod(shared.VarPath("networks", network, "dnsmasq.hosts"), 0644) + if err != nil { + return err + } } } diff -Nru lxd-2.7/lxd/profiles.go lxd-2.8/lxd/profiles.go --- lxd-2.7/lxd/profiles.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/profiles.go 2017-01-25 02:50:49.000000000 +0000 @@ -13,19 +13,13 @@ _ "github.com/mattn/go-sqlite3" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" log "gopkg.in/inconshreveable/log15.v2" ) /* This is used for both profiles post and profile put */ -type profilesPostReq struct { - Name string `json:"name"` - Config map[string]string `json:"config"` - Description string `json:"description"` - Devices shared.Devices `json:"devices"` -} - func profilesGet(d *Daemon, r *http.Request) Response { results, err := dbProfiles(d.db) if err != nil { @@ -35,7 +29,7 @@ recursion := d.isRecursionRequest(r) resultString := make([]string, len(results)) - resultMap := make([]*shared.ProfileConfig, len(results)) + resultMap := make([]*api.Profile, len(results)) i := 0 for _, name := range results { if !recursion { @@ -60,7 +54,7 @@ } func profilesPost(d *Daemon, r *http.Request) Response { - req := profilesPostReq{} + req := api.ProfilesPost{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } @@ -108,7 +102,7 @@ get: profilesGet, post: profilesPost} -func doProfileGet(d *Daemon, name string) (*shared.ProfileConfig, error) { +func doProfileGet(d *Daemon, name string) (*api.Profile, error) { _, profile, err := dbProfileGet(d.db, name) if err != nil { return nil, err @@ -173,7 +167,7 @@ return PreconditionFailed(err) } - req := profilesPostReq{} + req := api.ProfilePut{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } @@ -208,7 +202,7 @@ return BadRequest(err) } - req := profilesPostReq{} + req := api.ProfilePut{} if err := json.NewDecoder(rdr2).Decode(&req); err != nil { return BadRequest(err) } @@ -246,7 +240,7 @@ return doProfileUpdate(d, name, id, profile, req) } -func doProfileUpdate(d *Daemon, name string, id int64, profile *shared.ProfileConfig, req profilesPostReq) Response { +func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req api.ProfilePut) Response { // Sanity checks err := containerValidConfig(d, req.Config, true, false) if err != nil { @@ -338,7 +332,7 @@ func profilePost(d *Daemon, r *http.Request) Response { name := mux.Vars(r)["name"] - req := profilesPostReq{} + req := api.ProfilePost{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return BadRequest(err) } diff -Nru lxd-2.7/lxd/remote.go lxd-2.8/lxd/remote.go --- lxd-2.7/lxd/remote.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/remote.go 2017-01-25 02:50:49.000000000 +0000 @@ -1,10 +1,9 @@ package main import ( - "encoding/json" "fmt" - "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/version" ) @@ -18,8 +17,8 @@ return "", err } - var result shared.ImageAliasesEntry - if err = json.Unmarshal(resp.Metadata, &result); err != nil { + var result api.ImageAliasesEntry + if err = resp.MetadataAsStruct(&result); err != nil { return "", fmt.Errorf("Error reading alias") } diff -Nru lxd-2.7/lxd/response.go lxd-2.8/lxd/response.go --- lxd-2.7/lxd/response.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/response.go 2017-01-25 02:50:49.000000000 +0000 @@ -9,28 +9,14 @@ "mime/multipart" "net/http" "os" + "time" "github.com/mattn/go-sqlite3" - "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" ) -type syncResp struct { - Type lxd.ResponseType `json:"type"` - Status string `json:"status"` - StatusCode shared.StatusCode `json:"status_code"` - Metadata interface{} `json:"metadata"` -} - -type asyncResp struct { - Type lxd.ResponseType `json:"type"` - Status string `json:"status"` - StatusCode shared.StatusCode `json:"status_code"` - Metadata interface{} `json:"metadata"` - Operation string `json:"operation"` -} - type Response interface { Render(w http.ResponseWriter) error String() string @@ -55,9 +41,9 @@ } // Prepare the JSON response - status := shared.Success + status := api.Success if !r.success { - status = shared.Failure + status = api.Failure } if r.headers != nil { @@ -71,7 +57,14 @@ w.WriteHeader(201) } - resp := syncResp{Type: lxd.Sync, Status: status.String(), StatusCode: status, Metadata: r.metadata} + resp := api.ResponseRaw{ + Response: api.Response{ + Type: api.SyncResponse, + Status: status.String(), + StatusCode: int(status)}, + Metadata: r.metadata, + } + return WriteJSON(w, resp) } @@ -106,6 +99,7 @@ identifier string path string filename string + buffer []byte /* either a path or a buffer must be provided */ } type fileResponse struct { @@ -129,24 +123,38 @@ // For a single file, return it inline if len(r.files) == 1 { - f, err := os.Open(r.files[0].path) - if err != nil { - return err - } - defer f.Close() + var rs io.ReadSeeker + var mt time.Time + var sz int64 + + if r.files[0].path == "" { + rs = bytes.NewReader(r.files[0].buffer) + mt = time.Now() + sz = int64(len(r.files[0].buffer)) + } else { + f, err := os.Open(r.files[0].path) + if err != nil { + return err + } + defer f.Close() - fi, err := f.Stat() - if err != nil { - return err + fi, err := f.Stat() + if err != nil { + return err + } + + mt = fi.ModTime() + sz = fi.Size() + rs = f } w.Header().Set("Content-Type", "application/octet-stream") - w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size())) + w.Header().Set("Content-Length", fmt.Sprintf("%d", sz)) w.Header().Set("Content-Disposition", fmt.Sprintf("inline;filename=%s", r.files[0].filename)) - http.ServeContent(w, r.req, r.files[0].filename, fi.ModTime(), f) - if r.removeAfterServe { - err = os.Remove(r.files[0].path) + http.ServeContent(w, r.req, r.files[0].filename, mt, rs) + if r.files[0].path != "" && r.removeAfterServe { + err := os.Remove(r.files[0].path) if err != nil { return err } @@ -160,18 +168,24 @@ mw := multipart.NewWriter(body) for _, entry := range r.files { - fd, err := os.Open(entry.path) - if err != nil { - return err + var rd io.Reader + if entry.path != "" { + fd, err := os.Open(entry.path) + if err != nil { + return err + } + defer fd.Close() + rd = fd + } else { + rd = bytes.NewReader(entry.buffer) } - defer fd.Close() fw, err := mw.CreateFormFile(entry.identifier, entry.filename) if err != nil { return err } - _, err = io.Copy(fw, fd) + _, err = io.Copy(fw, rd) if err != nil { return err } @@ -209,12 +223,15 @@ return err } - body := asyncResp{ - Type: lxd.Async, - Status: shared.OperationCreated.String(), - StatusCode: shared.OperationCreated, - Operation: url, - Metadata: md} + body := api.ResponseRaw{ + Response: api.Response{ + Type: api.AsyncResponse, + Status: api.OperationCreated.String(), + StatusCode: int(api.OperationCreated), + Operation: url, + }, + Metadata: md, + } w.Header().Set("Location", url) w.WriteHeader(202) @@ -228,7 +245,7 @@ return fmt.Sprintf("error: %s", err) } - return md.Id + return md.ID } func OperationResponse(op *operation) Response { @@ -256,7 +273,7 @@ output = io.MultiWriter(buf, captured) } - err := json.NewEncoder(output).Encode(shared.Jmap{"type": lxd.Error, "error": r.msg, "error_code": r.code}) + err := json.NewEncoder(output).Encode(shared.Jmap{"type": api.ErrorResponse, "error": r.msg, "error_code": r.code}) if err != nil { return err diff -Nru lxd-2.7/lxd/storage.go lxd-2.8/lxd/storage.go --- lxd-2.7/lxd/storage.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/lxd/storage.go 2017-01-25 02:50:49.000000000 +0000 @@ -13,7 +13,9 @@ "github.com/gorilla/websocket" + "github.com/lxc/lxd/lxd/types" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/ioprogress" "github.com/lxc/lxd/shared/logging" @@ -255,6 +257,12 @@ if err != nil { return nil, fmt.Errorf("couldn't detect filesystem for '%s': %v", filename, err) } + + if filesystem == "btrfs" { + if !(*storageBtrfs).isSubvolume(nil, filename) { + filesystem = "" + } + } } if shared.PathExists(filename + ".lv") { @@ -274,7 +282,7 @@ return newStorageWithConfig(d, storageType, config) } -func storageForImage(d *Daemon, imgInfo *shared.ImageInfo) (storage, error) { +func storageForImage(d *Daemon, imgInfo *api.Image) (storage, error) { imageFilename := shared.VarPath("images", imgInfo.Fingerprint) return storageForFilename(d, imageFilename) } @@ -654,7 +662,7 @@ config[ent.GetKey()] = ent.GetValue() } - devices := shared.Devices{} + devices := types.Devices{} for _, ent := range snap.LocalDevices { props := map[string]string{} for _, prop := range ent.Config { @@ -813,9 +821,9 @@ meta = make(map[string]interface{}) } - progress := fmt.Sprintf("%s (%s/s)", shared.GetByteSizeString(progressInt), shared.GetByteSizeString(speedInt)) + progress := fmt.Sprintf("%s (%s/s)", shared.GetByteSizeString(progressInt, 2), shared.GetByteSizeString(speedInt, 2)) if description != "" { - progress = fmt.Sprintf("%s: %s (%s/s)", description, shared.GetByteSizeString(progressInt), shared.GetByteSizeString(speedInt)) + progress = fmt.Sprintf("%s: %s (%s/s)", description, shared.GetByteSizeString(progressInt, 2), shared.GetByteSizeString(speedInt, 2)) } if meta[key] != progress { diff -Nru lxd-2.7/lxd/types/devices.go lxd-2.8/lxd/types/devices.go --- lxd-2.7/lxd/types/devices.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/lxd/types/devices.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,166 @@ +package types + +import ( + "sort" + + "github.com/lxc/lxd/shared" +) + +type Device map[string]string +type Devices map[string]map[string]string + +func (list Devices) ContainsName(k string) bool { + if list[k] != nil { + return true + } + return false +} + +func (d Device) get(key string) string { + return d[key] +} + +func (list Devices) Contains(k string, d Device) bool { + // If it didn't exist, it's different + if list[k] == nil { + return false + } + + old := list[k] + + return deviceEquals(old, d) +} + +func deviceEquals(old Device, d Device) bool { + // Check for any difference and addition/removal of properties + for k := range d { + if d[k] != old[k] { + return false + } + } + + for k := range old { + if d[k] != old[k] { + return false + } + } + + return true +} + +func (old Devices) Update(newlist Devices) (map[string]Device, map[string]Device, map[string]Device) { + rmlist := map[string]Device{} + addlist := map[string]Device{} + updatelist := map[string]Device{} + + for key, d := range old { + if !newlist.Contains(key, d) { + rmlist[key] = d + } + } + + for key, d := range newlist { + if !old.Contains(key, d) { + addlist[key] = d + } + } + + for key, d := range addlist { + srcOldDevice := rmlist[key] + var oldDevice Device + err := shared.DeepCopy(&srcOldDevice, &oldDevice) + if err != nil { + continue + } + + srcNewDevice := newlist[key] + var newDevice Device + err = shared.DeepCopy(&srcNewDevice, &newDevice) + if err != nil { + continue + } + + for _, k := range []string{"limits.max", "limits.read", "limits.write", "limits.egress", "limits.ingress", "ipv4.address", "ipv6.address"} { + delete(oldDevice, k) + delete(newDevice, k) + } + + if deviceEquals(oldDevice, newDevice) { + delete(rmlist, key) + delete(addlist, key) + updatelist[key] = d + } + } + + return rmlist, addlist, updatelist +} + +func (newBaseDevices Devices) ExtendFromProfile(currentFullDevices Devices, newDevicesFromProfile Devices) error { + // For any entry which exists in a profile and doesn't in the container config, add it + + for name, newDev := range newDevicesFromProfile { + if curDev, ok := currentFullDevices[name]; ok { + newBaseDevices[name] = curDev + } else { + newBaseDevices[name] = newDev + } + } + + return nil +} + +type namedDevice struct { + name string + device Device +} +type sortableDevices []namedDevice + +func (devices Devices) toSortable() sortableDevices { + named := []namedDevice{} + for k, d := range devices { + named = append(named, namedDevice{k, d}) + } + + return named +} + +func (devices sortableDevices) Len() int { + return len(devices) +} + +func (devices sortableDevices) Less(i, j int) bool { + a := devices[i] + b := devices[j] + + if a.device["type"] == "disk" && b.device["type"] == "disk" { + if a.device["path"] == b.device["path"] { + return a.name < b.name + } + + return a.device["path"] < b.device["path"] + } + + return a.name < b.name +} + +func (devices sortableDevices) Swap(i, j int) { + tmp := devices[i] + devices[i] = devices[j] + devices[j] = tmp +} + +func (devices sortableDevices) Names() []string { + result := []string{} + for _, d := range devices { + result = append(result, d.name) + } + + return result +} + +/* DeviceNames returns the device names for this Devices in sorted order */ +func (devices Devices) DeviceNames() []string { + sortable := devices.toSortable() + sort.Sort(sortable) + return sortable.Names() +} diff -Nru lxd-2.7/lxd/types/devices_test.go lxd-2.8/lxd/types/devices_test.go --- lxd-2.7/lxd/types/devices_test.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/lxd/types/devices_test.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,22 @@ +package types + +import ( + "reflect" + "testing" +) + +func TestSortableDevices(t *testing.T) { + devices := Devices{ + "1": Device{"type": "nic"}, + "3": Device{"type": "disk", "path": "/foo/bar"}, + "4": Device{"type": "disk", "path": "/foo"}, + "2": Device{"type": "nic"}, + } + + expected := []string{"1", "2", "4", "3"} + + result := devices.DeviceNames() + if !reflect.DeepEqual(result, expected) { + t.Error("devices sorted incorrectly") + } +} diff -Nru lxd-2.7/po/de.po lxd-2.8/po/de.po --- lxd-2.7/po/de.po 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/po/de.po 2017-01-25 02:50:49.000000000 +0000 @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: LXD\n" "Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n" -"POT-Creation-Date: 2016-12-15 13:20-0500\n" +"POT-Creation-Date: 2017-01-24 18:22-0500\n" "PO-Revision-Date: 2015-06-13 06:10+0200\n" "Last-Translator: Felix Engelmann \n" "Language-Team: \n" @@ -32,7 +32,7 @@ msgid " Network usage:" msgstr "" -#: lxc/config.go:36 +#: lxc/config.go:37 #, fuzzy msgid "" "### This is a yaml representation of the configuration.\n" @@ -71,7 +71,7 @@ "###\n" "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n" -#: lxc/image.go:85 +#: lxc/image.go:86 #, fuzzy msgid "" "### This is a yaml representation of the image properties.\n" @@ -88,7 +88,7 @@ "### Zum Beispiel:\n" "### description: Mein eigenes Abbild\n" -#: lxc/network.go:28 +#: lxc/network.go:29 #, fuzzy msgid "" "### This is a yaml representation of the network.\n" @@ -126,7 +126,7 @@ "###\n" "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n" -#: lxc/profile.go:27 +#: lxc/profile.go:28 #, fuzzy msgid "" "### This is a yaml representation of the profile.\n" @@ -165,7 +165,7 @@ "###\n" "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n" -#: lxc/image.go:614 +#: lxc/image.go:620 #, c-format msgid "%s (%d more)" msgstr "" @@ -175,19 +175,19 @@ msgid "'/' not allowed in snapshot name" msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n" -#: lxc/profile.go:253 +#: lxc/profile.go:254 msgid "(none)" msgstr "" -#: lxc/image.go:635 lxc/image.go:677 +#: lxc/image.go:641 lxc/image.go:683 msgid "ALIAS" msgstr "" -#: lxc/image.go:639 +#: lxc/image.go:645 msgid "ARCH" msgstr "" -#: lxc/list.go:425 +#: lxc/list.go:427 msgid "ARCHITECTURE" msgstr "" @@ -200,17 +200,17 @@ msgid "Admin password for %s: " msgstr "Administrator Passwort für %s: " -#: lxc/image.go:362 +#: lxc/image.go:368 #, fuzzy msgid "Aliases:" msgstr "Aliasse:\n" -#: lxc/image.go:345 lxc/info.go:93 +#: lxc/image.go:346 lxc/info.go:93 #, fuzzy, c-format msgid "Architecture: %s" msgstr "Architektur: %s\n" -#: lxc/image.go:366 +#: lxc/image.go:372 #, c-format msgid "Auto update: %s" msgstr "" @@ -227,7 +227,7 @@ msgid "Bytes sent" msgstr "" -#: lxc/config.go:273 +#: lxc/config.go:274 msgid "COMMON NAME" msgstr "" @@ -235,21 +235,21 @@ msgid "CPU usage (in seconds)" msgstr "" -#: lxc/list.go:426 +#: lxc/list.go:428 msgid "CREATED AT" msgstr "" -#: lxc/config.go:113 +#: lxc/config.go:114 lxc/network.go:463 #, c-format msgid "Can't read from stdin: %s" msgstr "" -#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181 +#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182 #, c-format msgid "Can't unset key '%s', it's not currently set." msgstr "" -#: lxc/network.go:385 lxc/profile.go:419 +#: lxc/network.go:390 lxc/profile.go:424 msgid "Cannot provide container name to list" msgstr "" @@ -258,7 +258,7 @@ msgid "Certificate fingerprint: %x" msgstr "Fingerabdruck des Zertifikats: % x\n" -#: lxc/action.go:33 +#: lxc/action.go:34 #, fuzzy, c-format msgid "" "Change state of one or more containers to %s.\n" @@ -284,18 +284,17 @@ msgid "Client certificate stored at server: " msgstr "Gespeichertes Nutzerzertifikat auf dem Server: " -#: lxc/list.go:121 lxc/list.go:122 +#: lxc/list.go:122 lxc/list.go:123 msgid "Columns" msgstr "" -#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:135 lxc/init.go:136 -#: lxc/launch.go:40 lxc/launch.go:41 +#: lxc/copy.go:32 lxc/copy.go:33 lxc/init.go:136 lxc/init.go:137 #, fuzzy msgid "Config key/value to apply to the new container" msgstr "kann nicht zum selben Container Namen kopieren" -#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:731 lxc/network.go:341 -#: lxc/profile.go:217 +#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:737 lxc/network.go:346 +#: lxc/profile.go:218 #, fuzzy, c-format msgid "Config parsing error: %s" msgstr "YAML Analyse Fehler %v\n" @@ -304,25 +303,25 @@ msgid "Connection refused; is LXD running?" msgstr "" -#: lxc/publish.go:61 +#: lxc/publish.go:62 msgid "Container name is mandatory" msgstr "" -#: lxc/copy.go:140 lxc/copy.go:241 lxc/init.go:229 +#: lxc/copy.go:141 lxc/copy.go:242 lxc/init.go:230 #, c-format msgid "Container name is: %s" msgstr "" -#: lxc/publish.go:151 lxc/publish.go:166 +#: lxc/publish.go:152 lxc/publish.go:167 #, fuzzy, c-format msgid "Container published with fingerprint: %s" msgstr "Abbild mit Fingerabdruck %s importiert\n" -#: lxc/image.go:165 +#: lxc/image.go:166 msgid "Copy aliases from source" msgstr "Kopiere Aliasse von der Quelle" -#: lxc/copy.go:24 +#: lxc/copy.go:25 #, fuzzy msgid "" "Copy containers within or in between LXD instances.\n" @@ -334,7 +333,7 @@ "\n" "lxc copy \n" -#: lxc/image.go:278 +#: lxc/image.go:279 #, c-format msgid "Copying the image: %s" msgstr "" @@ -372,30 +371,30 @@ msgid "Create any directories necessary" msgstr "" -#: lxc/image.go:350 lxc/info.go:95 +#: lxc/image.go:351 lxc/info.go:95 #, c-format msgid "Created: %s" msgstr "" -#: lxc/init.go:180 lxc/launch.go:135 +#: lxc/init.go:181 lxc/launch.go:127 #, c-format msgid "Creating %s" msgstr "" -#: lxc/init.go:178 +#: lxc/init.go:179 #, fuzzy msgid "Creating the container" msgstr "kann nicht zum selben Container Namen kopieren" -#: lxc/image.go:638 lxc/image.go:679 +#: lxc/image.go:644 lxc/image.go:685 msgid "DESCRIPTION" msgstr "" -#: lxc/publish.go:37 +#: lxc/publish.go:38 msgid "Define a compression algorithm: for image or none" msgstr "" -#: lxc/delete.go:25 +#: lxc/delete.go:26 #, fuzzy msgid "" "Delete containers or snapshots.\n" @@ -411,21 +410,21 @@ "Entfernt einen Container (oder Sicherungspunkt) und alle dazugehörigen\n" "Daten (Konfiguration, Sicherungspunkte, ...).\n" -#: lxc/config.go:647 +#: lxc/config.go:652 #, fuzzy, c-format msgid "Device %s added to %s" msgstr "Gerät %s wurde zu %s hinzugefügt\n" -#: lxc/config.go:834 +#: lxc/config.go:839 #, fuzzy, c-format msgid "Device %s removed from %s" msgstr "Gerät %s wurde von %s entfernt\n" -#: lxc/list.go:570 +#: lxc/list.go:574 msgid "EPHEMERAL" msgstr "" -#: lxc/config.go:275 +#: lxc/config.go:276 msgid "EXPIRY DATE" msgstr "" @@ -439,7 +438,7 @@ msgid "Enable verbose mode" msgstr "Aktiviert ausführliche Ausgabe" -#: lxc/exec.go:54 +#: lxc/exec.go:55 msgid "Environment variable to set (e.g. HOME=/home/foo)" msgstr "" @@ -447,8 +446,7 @@ msgid "Environment:" msgstr "" -#: lxc/copy.go:35 lxc/copy.go:36 lxc/init.go:139 lxc/init.go:140 -#: lxc/launch.go:44 lxc/launch.go:45 +#: lxc/copy.go:36 lxc/copy.go:37 lxc/init.go:140 lxc/init.go:141 msgid "Ephemeral container" msgstr "Flüchtiger Container" @@ -456,7 +454,7 @@ msgid "Event type to listen for" msgstr "" -#: lxc/exec.go:45 +#: lxc/exec.go:46 #, fuzzy msgid "" "Execute the specified command in a container.\n" @@ -471,34 +469,34 @@ "\n" "lxc exec [--env EDITOR=/usr/bin/vim]... \n" -#: lxc/image.go:354 +#: lxc/image.go:355 #, c-format msgid "Expires: %s" msgstr "" -#: lxc/image.go:356 +#: lxc/image.go:357 msgid "Expires: never" msgstr "" -#: lxc/config.go:272 lxc/image.go:636 lxc/image.go:678 +#: lxc/config.go:273 lxc/image.go:642 lxc/image.go:684 msgid "FINGERPRINT" msgstr "" -#: lxc/list.go:124 +#: lxc/list.go:125 msgid "Fast mode (same as --columns=nsacPt" msgstr "" -#: lxc/image.go:343 +#: lxc/image.go:344 #, fuzzy, c-format msgid "Fingerprint: %s" msgstr "Fingerabdruck: %s\n" -#: lxc/action.go:42 lxc/action.go:43 +#: lxc/action.go:43 lxc/action.go:44 #, fuzzy msgid "Force the container to shutdown" msgstr "Herunterfahren des Containers erzwingen." -#: lxc/delete.go:34 lxc/delete.go:35 +#: lxc/delete.go:35 lxc/delete.go:36 msgid "Force the removal of stopped containers" msgstr "" @@ -506,7 +504,7 @@ msgid "Force using the local unix socket" msgstr "" -#: lxc/image.go:168 lxc/list.go:123 +#: lxc/image.go:169 lxc/list.go:124 msgid "Format" msgstr "" @@ -526,15 +524,15 @@ "\n" "lxd help [—all]\n" -#: lxc/list.go:423 +#: lxc/list.go:425 msgid "IPV4" msgstr "" -#: lxc/list.go:424 +#: lxc/list.go:426 msgid "IPV6" msgstr "" -#: lxc/config.go:274 +#: lxc/config.go:275 msgid "ISSUE DATE" msgstr "" @@ -547,21 +545,21 @@ msgid "Ignore aliases when determining what command to run" msgstr "" -#: lxc/action.go:46 +#: lxc/action.go:47 #, fuzzy msgid "Ignore the container state (only for start)" msgstr "Herunterfahren des Containers erzwingen." -#: lxc/image.go:281 +#: lxc/image.go:282 msgid "Image copied successfully!" msgstr "" -#: lxc/image.go:421 lxc/image.go:433 +#: lxc/image.go:427 lxc/image.go:439 #, fuzzy, c-format msgid "Image imported with fingerprint: %s" msgstr "Abbild mit Fingerabdruck %s importiert\n" -#: lxc/image.go:418 +#: lxc/image.go:424 #, c-format msgid "Importing the image: %s" msgstr "" @@ -580,7 +578,7 @@ "Specifying \"-p\" with no argument will result in no profile.\n" "\n" "Example:\n" -" lxc init ubuntu u1" +" lxc init ubuntu:16.04 u1" msgstr "" "Starte Container von gegebenem Abbild.\n" "\n" @@ -599,7 +597,7 @@ msgid "Invalid URL scheme \"%s\" in \"%s\"" msgstr "" -#: lxc/config.go:253 +#: lxc/config.go:254 #, fuzzy msgid "Invalid certificate" msgstr "Akzeptiere Zertifikat" @@ -608,7 +606,7 @@ msgid "Invalid configuration key" msgstr "" -#: lxc/file.go:249 +#: lxc/file.go:285 #, c-format msgid "Invalid source %s" msgstr "Ungültige Quelle %s" @@ -622,11 +620,11 @@ msgid "Ips:" msgstr "" -#: lxc/image.go:166 +#: lxc/image.go:167 msgid "Keep the image up to date after initial copy" msgstr "" -#: lxc/list.go:427 +#: lxc/list.go:429 msgid "LAST USED AT" msgstr "" @@ -634,7 +632,16 @@ msgid "LXD socket not found; is LXD installed and running?" msgstr "" -#: lxc/launch.go:22 +#: lxc/image.go:360 +#, c-format +msgid "Last used: %s" +msgstr "" + +#: lxc/image.go:362 +msgid "Last used: never" +msgstr "" + +#: lxc/launch.go:23 #, fuzzy msgid "" "Launch a container from a particular image.\n" @@ -681,7 +688,7 @@ "\n" "lxc info [:]Container [--show-log]\n" -#: lxc/list.go:68 +#: lxc/list.go:69 #, fuzzy msgid "" "Lists the containers.\n" @@ -759,20 +766,20 @@ msgid "Log:" msgstr "" -#: lxc/network.go:423 +#: lxc/network.go:428 msgid "MANAGED" msgstr "" -#: lxc/image.go:164 +#: lxc/image.go:165 msgid "Make image public" msgstr "Veröffentliche Abbild" -#: lxc/publish.go:33 +#: lxc/publish.go:34 #, fuzzy msgid "Make the image public" msgstr "Veröffentliche Abbild" -#: lxc/profile.go:48 +#: lxc/profile.go:49 #, fuzzy msgid "" "Manage configuration profiles.\n" @@ -867,7 +874,7 @@ "Containern hinzu,\n" " die dieses Profil verwenden.\n" -#: lxc/config.go:57 +#: lxc/config.go:58 #, fuzzy msgid "" "Manage configuration.\n" @@ -996,7 +1003,7 @@ " bei pull und bei push sind jeweils von der Form /\n" -#: lxc/network.go:48 +#: lxc/network.go:49 msgid "" "Manage networks.\n" "\n" @@ -1033,15 +1040,15 @@ "lxc remote add [] [--accept-certificate] [--" "password=PASSWORD]\n" " [--public] [--protocol=PROTOCOL] " -"Add the remote at .\n" +"Add the remote at .\n" "lxc remote remove " -"Remove the remote .\n" +"Remove the remote .\n" "lxc remote list " "List all remotes.\n" "lxc remote rename " -"Rename remote to .\n" +"Rename remote to .\n" "lxc remote set-url " -"Update 's url to .\n" +"Update 's url to .\n" "lxc remote set-default " "Set the default remote.\n" "lxc remote get-default " @@ -1064,7 +1071,7 @@ "lxc remote get-default " "Gibt die Standard Instanz aus.\n" -#: lxc/image.go:95 +#: lxc/image.go:96 msgid "" "Manipulate container images.\n" "\n" @@ -1164,11 +1171,11 @@ " lxc monitor --type=logging" msgstr "" -#: lxc/network.go:215 lxc/network.go:264 +#: lxc/network.go:216 lxc/network.go:265 msgid "More than one device matches, specify the device name." msgstr "" -#: lxc/file.go:237 +#: lxc/file.go:273 msgid "More than one file to download, but target is not a directory" msgstr "" "Mehr als eine Datei herunterzuladen, aber das Ziel ist kein Verzeichnis" @@ -1192,16 +1199,16 @@ "\n" "lxc move \n" -#: lxc/action.go:69 +#: lxc/action.go:70 #, fuzzy msgid "Must supply container name for: " msgstr "der Name des Ursprung Containers muss angegeben werden" -#: lxc/list.go:428 lxc/network.go:421 lxc/profile.go:446 lxc/remote.go:380 +#: lxc/list.go:430 lxc/network.go:426 lxc/profile.go:451 lxc/remote.go:380 msgid "NAME" msgstr "" -#: lxc/network.go:407 lxc/remote.go:354 lxc/remote.go:359 +#: lxc/network.go:412 lxc/remote.go:354 lxc/remote.go:359 msgid "NO" msgstr "" @@ -1210,35 +1217,35 @@ msgid "Name: %s" msgstr "" -#: lxc/network.go:189 +#: lxc/network.go:190 #, fuzzy, c-format msgid "Network %s created" msgstr "Profil %s erstellt\n" -#: lxc/network.go:292 +#: lxc/network.go:293 #, fuzzy, c-format msgid "Network %s deleted" msgstr "Profil %s gelöscht\n" -#: lxc/init.go:141 lxc/init.go:142 lxc/launch.go:46 lxc/launch.go:47 +#: lxc/init.go:142 lxc/init.go:143 msgid "Network name" msgstr "" -#: lxc/image.go:167 lxc/publish.go:34 +#: lxc/image.go:168 lxc/publish.go:35 msgid "New alias to define at target" msgstr "" -#: lxc/config.go:284 +#: lxc/config.go:285 #, fuzzy msgid "No certificate provided to add" msgstr "Kein Zertifikat zum hinzufügen bereitgestellt" -#: lxc/network.go:224 lxc/network.go:273 +#: lxc/network.go:225 lxc/network.go:274 #, fuzzy msgid "No device found for this network" msgstr "Kein Zertifikat für diese Verbindung" -#: lxc/config.go:307 +#: lxc/config.go:308 msgid "No fingerprint specified." msgstr "Kein Fingerabdruck angegeben." @@ -1246,32 +1253,36 @@ msgid "Only https URLs are supported for simplestreams" msgstr "" -#: lxc/image.go:424 +#: lxc/image.go:430 msgid "Only https:// is supported for remote image import." msgstr "" +#: lxc/network.go:322 lxc/network.go:449 +msgid "Only managed networks can be modified." +msgstr "" + #: lxc/help.go:63 lxc/main.go:112 msgid "Options:" msgstr "" -#: lxc/image.go:535 +#: lxc/image.go:541 #, c-format msgid "Output is in %s" msgstr "" -#: lxc/exec.go:55 +#: lxc/exec.go:56 msgid "Override the terminal mode (auto, interactive or non-interactive)" msgstr "" -#: lxc/list.go:572 +#: lxc/list.go:576 msgid "PERSISTENT" msgstr "" -#: lxc/list.go:429 +#: lxc/list.go:431 msgid "PID" msgstr "" -#: lxc/list.go:430 +#: lxc/list.go:432 msgid "PROFILES" msgstr "" @@ -1279,7 +1290,7 @@ msgid "PROTOCOL" msgstr "" -#: lxc/image.go:637 lxc/remote.go:383 +#: lxc/image.go:643 lxc/remote.go:383 msgid "PUBLIC" msgstr "" @@ -1310,11 +1321,11 @@ msgid "Pid: %d" msgstr "" -#: lxc/network.go:342 lxc/profile.go:218 +#: lxc/network.go:347 lxc/profile.go:219 msgid "Press enter to open the editor again" msgstr "" -#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:732 +#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:738 msgid "Press enter to start the editor again" msgstr "" @@ -1350,33 +1361,32 @@ msgid "Processes: %d" msgstr "Profil %s erstellt\n" -#: lxc/profile.go:274 +#: lxc/profile.go:275 #, fuzzy, c-format msgid "Profile %s added to %s" msgstr "Profil %s wurde auf %s angewandt\n" -#: lxc/profile.go:169 +#: lxc/profile.go:170 #, fuzzy, c-format msgid "Profile %s created" msgstr "Profil %s erstellt\n" -#: lxc/profile.go:239 +#: lxc/profile.go:240 #, fuzzy, c-format msgid "Profile %s deleted" msgstr "Profil %s gelöscht\n" -#: lxc/profile.go:305 +#: lxc/profile.go:306 #, fuzzy, c-format msgid "Profile %s removed from %s" msgstr "Gerät %s wurde von %s entfernt\n" -#: lxc/copy.go:33 lxc/copy.go:34 lxc/init.go:137 lxc/init.go:138 -#: lxc/launch.go:42 lxc/launch.go:43 +#: lxc/copy.go:34 lxc/copy.go:35 lxc/init.go:138 lxc/init.go:139 #, fuzzy msgid "Profile to apply to the new container" msgstr "kann nicht zum selben Container Namen kopieren" -#: lxc/profile.go:255 +#: lxc/profile.go:256 #, fuzzy, c-format msgid "Profiles %s applied to %s" msgstr "Profil %s wurde auf %s angewandt\n" @@ -1386,7 +1396,7 @@ msgid "Profiles: %s" msgstr "Profil %s erstellt\n" -#: lxc/image.go:358 +#: lxc/image.go:364 #, fuzzy msgid "Properties:" msgstr "Eigenschaften:\n" @@ -1395,12 +1405,12 @@ msgid "Public image server" msgstr "" -#: lxc/image.go:346 +#: lxc/image.go:347 #, fuzzy, c-format msgid "Public: %s" msgstr "Öffentlich: %s\n" -#: lxc/publish.go:26 +#: lxc/publish.go:27 msgid "" "Publish containers as images.\n" "\n" @@ -1421,12 +1431,12 @@ msgid "Remote: %s" msgstr "" -#: lxc/delete.go:42 +#: lxc/delete.go:43 #, c-format msgid "Remove %s (yes/no): " msgstr "" -#: lxc/delete.go:36 lxc/delete.go:37 +#: lxc/delete.go:37 lxc/delete.go:38 msgid "Require user confirmation" msgstr "" @@ -1451,20 +1461,20 @@ " lxc restore u1 snap0" msgstr "" -#: lxc/init.go:239 +#: lxc/init.go:240 #, c-format msgid "Retrieving image: %s" msgstr "" -#: lxc/image.go:640 +#: lxc/image.go:646 msgid "SIZE" msgstr "" -#: lxc/list.go:431 +#: lxc/list.go:433 msgid "SNAPSHOTS" msgstr "" -#: lxc/list.go:432 +#: lxc/list.go:434 msgid "STATE" msgstr "" @@ -1509,11 +1519,11 @@ msgid "Show the container's last 100 log lines?" msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?" -#: lxc/config.go:32 +#: lxc/config.go:33 msgid "Show the expanded configuration" msgstr "" -#: lxc/image.go:344 +#: lxc/image.go:345 #, fuzzy, c-format msgid "Size: %.2fMB" msgstr "Größe: %.2vMB\n" @@ -1522,11 +1532,11 @@ msgid "Snapshots:" msgstr "" -#: lxc/image.go:368 +#: lxc/image.go:374 msgid "Source:" msgstr "" -#: lxc/launch.go:144 +#: lxc/launch.go:136 #, c-format msgid "Starting %s" msgstr "" @@ -1536,15 +1546,15 @@ msgid "Status: %s" msgstr "" -#: lxc/publish.go:35 lxc/publish.go:36 +#: lxc/publish.go:36 lxc/publish.go:37 msgid "Stop the container if currently running" msgstr "" -#: lxc/delete.go:106 lxc/publish.go:113 +#: lxc/delete.go:107 lxc/publish.go:114 msgid "Stopping container failed!" msgstr "Anhalten des Containers fehlgeschlagen!" -#: lxc/action.go:45 +#: lxc/action.go:46 #, fuzzy msgid "Store the container state (only for stop)" msgstr "Herunterfahren des Containers erzwingen." @@ -1557,31 +1567,31 @@ msgid "Swap (peak)" msgstr "" -#: lxc/list.go:433 lxc/network.go:422 +#: lxc/list.go:435 lxc/network.go:427 msgid "TYPE" msgstr "" -#: lxc/delete.go:92 +#: lxc/delete.go:93 msgid "The container is currently running, stop it first or pass --force." msgstr "" -#: lxc/publish.go:91 +#: lxc/publish.go:92 msgid "" "The container is currently running. Use --force to have it stopped and " "restarted." msgstr "" -#: lxc/init.go:312 +#: lxc/init.go:313 msgid "The container you are starting doesn't have any network attached to it." msgstr "" -#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738 -#: lxc/config.go:776 lxc/config.go:794 +#: lxc/config.go:680 lxc/config.go:692 lxc/config.go:725 lxc/config.go:743 +#: lxc/config.go:781 lxc/config.go:799 #, fuzzy msgid "The device doesn't exist" msgstr "entfernte Instanz %s existiert nicht" -#: lxc/init.go:296 +#: lxc/init.go:297 #, c-format msgid "The local image '%s' couldn't be found, trying '%s:' instead." msgstr "" @@ -1590,35 +1600,35 @@ msgid "The opposite of `lxc pause` is `lxc start`." msgstr "" -#: lxc/network.go:229 lxc/network.go:278 +#: lxc/network.go:230 lxc/network.go:279 #, fuzzy msgid "The specified device doesn't exist" msgstr "entfernte Instanz %s existiert nicht" -#: lxc/network.go:233 lxc/network.go:282 +#: lxc/network.go:234 lxc/network.go:283 #, fuzzy msgid "The specified device doesn't match the network" msgstr "entfernte Instanz %s existiert nicht" -#: lxc/publish.go:64 +#: lxc/publish.go:65 msgid "There is no \"image name\". Did you want an alias?" msgstr "" -#: lxc/action.go:41 +#: lxc/action.go:42 #, fuzzy msgid "Time to wait for the container before killing it" msgstr "Wartezeit bevor der Container gestoppt wird." -#: lxc/image.go:347 +#: lxc/image.go:348 #, fuzzy msgid "Timestamps:" msgstr "Zeitstempel:\n" -#: lxc/init.go:314 +#: lxc/init.go:315 msgid "To attach a network to a container, use: lxc network attach" msgstr "" -#: lxc/init.go:313 +#: lxc/init.go:314 msgid "To create a new network, use: lxc network create" msgstr "" @@ -1626,12 +1636,12 @@ msgid "To start your first container, try: lxc launch ubuntu:16.04" msgstr "" -#: lxc/image.go:426 +#: lxc/image.go:432 #, c-format msgid "Transferring image: %s" msgstr "" -#: lxc/action.go:99 lxc/launch.go:157 +#: lxc/action.go:100 lxc/launch.go:149 #, c-format msgid "Try `lxc info --show-log %s` for more info" msgstr "" @@ -1644,7 +1654,7 @@ msgid "Type: persistent" msgstr "" -#: lxc/image.go:641 +#: lxc/image.go:647 msgid "UPLOAD DATE" msgstr "" @@ -1652,7 +1662,7 @@ msgid "URL" msgstr "" -#: lxc/network.go:424 lxc/profile.go:447 +#: lxc/network.go:429 lxc/profile.go:452 msgid "USED BY" msgstr "" @@ -1660,7 +1670,7 @@ msgid "Unable to read remote TLS certificate" msgstr "" -#: lxc/image.go:352 +#: lxc/image.go:353 #, c-format msgid "Uploaded: %s" msgstr "" @@ -1681,7 +1691,7 @@ "Benutzung: lxc [Unterbefehl] [Optionen]\n" "Verfügbare Befehle:\n" -#: lxc/delete.go:46 +#: lxc/delete.go:47 msgid "User aborted delete operation." msgstr "" @@ -1697,7 +1707,7 @@ msgid "Whether or not to snapshot the container's running state" msgstr "Zustand des laufenden Containers sichern oder nicht" -#: lxc/network.go:409 lxc/remote.go:356 lxc/remote.go:361 +#: lxc/network.go:414 lxc/remote.go:356 lxc/remote.go:361 msgid "YES" msgstr "" @@ -1705,20 +1715,20 @@ msgid "`lxc config profile` is deprecated, please use `lxc profile`" msgstr "" -#: lxc/launch.go:128 +#: lxc/launch.go:120 msgid "bad number of things scanned from image, container or snapshot" msgstr "" "Falsche Anzahl an Objekten im Abbild, Container oder Sicherungspunkt gelesen." -#: lxc/action.go:95 +#: lxc/action.go:96 msgid "bad result type from action" msgstr "" -#: lxc/copy.go:115 +#: lxc/copy.go:116 msgid "can't copy to the same container name" msgstr "kann nicht zum selben Container Namen kopieren" -#: lxc/file.go:272 +#: lxc/file.go:308 msgid "can't pull a directory without --recursive" msgstr "" @@ -1734,16 +1744,16 @@ msgid "default" msgstr "" -#: lxc/copy.go:131 lxc/copy.go:136 lxc/copy.go:232 lxc/copy.go:237 -#: lxc/init.go:219 lxc/init.go:224 lxc/launch.go:112 lxc/launch.go:117 +#: lxc/copy.go:132 lxc/copy.go:137 lxc/copy.go:233 lxc/copy.go:238 +#: lxc/init.go:220 lxc/init.go:225 lxc/launch.go:104 lxc/launch.go:109 msgid "didn't get any affected image, container or snapshot from server" msgstr "" -#: lxc/image.go:338 +#: lxc/image.go:339 msgid "disabled" msgstr "" -#: lxc/image.go:340 +#: lxc/image.go:341 msgid "enabled" msgstr "" @@ -1757,15 +1767,15 @@ msgid "error: unknown command: %s" msgstr "Fehler: unbekannter Befehl: %s\n" -#: lxc/launch.go:132 +#: lxc/launch.go:124 msgid "got bad version" msgstr "Versionskonflikt" -#: lxc/image.go:333 lxc/image.go:617 +#: lxc/image.go:334 lxc/image.go:623 msgid "no" msgstr "" -#: lxc/copy.go:164 +#: lxc/copy.go:165 msgid "not all the profiles from the source exist on the target" msgstr "nicht alle Profile der Quelle sind am Ziel vorhanden." @@ -1779,7 +1789,7 @@ msgid "processing aliases failed %s\n" msgstr "" -#: lxc/file.go:313 +#: lxc/file.go:349 msgid "recursive edit doesn't make sense :(" msgstr "" @@ -1816,7 +1826,7 @@ msgid "taken at %s" msgstr "" -#: lxc/exec.go:185 +#: lxc/exec.go:186 msgid "unreachable return reached" msgstr "" @@ -1824,11 +1834,11 @@ msgid "wrong number of subcommand arguments" msgstr "falsche Anzahl an Parametern für Unterbefehl" -#: lxc/delete.go:45 lxc/image.go:335 lxc/image.go:621 +#: lxc/delete.go:46 lxc/image.go:336 lxc/image.go:627 msgid "yes" msgstr "" -#: lxc/copy.go:44 +#: lxc/copy.go:45 msgid "you must specify a source container name" msgstr "der Name des Ursprung Containers muss angegeben werden" diff -Nru lxd-2.7/po/el.po lxd-2.8/po/el.po --- lxd-2.7/po/el.po 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/po/el.po 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,1574 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: lxd\n" +"Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n" +"POT-Creation-Date: 2017-01-24 18:22-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: el\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: lxc/info.go:154 +msgid " CPU usage:" +msgstr "" + +#: lxc/info.go:143 +msgid " Disk usage:" +msgstr "" + +#: lxc/info.go:177 +msgid " Memory usage:" +msgstr "" + +#: lxc/info.go:194 +msgid " Network usage:" +msgstr "" + +#: lxc/config.go:37 +msgid "" +"### This is a yaml representation of the configuration.\n" +"### Any line starting with a '# will be ignored.\n" +"###\n" +"### A sample configuration looks like:\n" +"### name: container1\n" +"### profiles:\n" +"### - default\n" +"### config:\n" +"### volatile.eth0.hwaddr: 00:16:3e:e9:f8:7f\n" +"### devices:\n" +"### homedir:\n" +"### path: /extra\n" +"### source: /home/user\n" +"### type: disk\n" +"### ephemeral: false\n" +"###\n" +"### Note that the name is shown but cannot be changed" +msgstr "" + +#: lxc/image.go:86 +msgid "" +"### This is a yaml representation of the image properties.\n" +"### Any line starting with a '# will be ignored.\n" +"###\n" +"### Each property is represented by a single line:\n" +"### An example would be:\n" +"### description: My custom image" +msgstr "" + +#: lxc/network.go:29 +msgid "" +"### This is a yaml representation of the network.\n" +"### Any line starting with a '# will be ignored.\n" +"###\n" +"### A network consists of a set of configuration items.\n" +"###\n" +"### An example would look like:\n" +"### name: lxdbr0\n" +"### config:\n" +"### ipv4.address: 10.62.42.1/24\n" +"### ipv4.nat: true\n" +"### ipv6.address: fd00:56ad:9f7a:9800::1/64\n" +"### ipv6.nat: true\n" +"### managed: true\n" +"### type: bridge\n" +"###\n" +"### Note that only the configuration can be changed." +msgstr "" + +#: lxc/profile.go:28 +msgid "" +"### This is a yaml representation of the profile.\n" +"### Any line starting with a '# will be ignored.\n" +"###\n" +"### A profile consists of a set of configuration items followed by a set of\n" +"### devices.\n" +"###\n" +"### An example would look like:\n" +"### name: onenic\n" +"### config:\n" +"### raw.lxc: lxc.aa_profile=unconfined\n" +"### devices:\n" +"### eth0:\n" +"### nictype: bridged\n" +"### parent: lxdbr0\n" +"### type: nic\n" +"###\n" +"### Note that the name is shown but cannot be changed" +msgstr "" + +#: lxc/image.go:620 +#, c-format +msgid "%s (%d more)" +msgstr "" + +#: lxc/snapshot.go:61 +msgid "'/' not allowed in snapshot name" +msgstr "" + +#: lxc/profile.go:254 +msgid "(none)" +msgstr "" + +#: lxc/image.go:641 lxc/image.go:683 +msgid "ALIAS" +msgstr "" + +#: lxc/image.go:645 +msgid "ARCH" +msgstr "" + +#: lxc/list.go:427 +msgid "ARCHITECTURE" +msgstr "" + +#: lxc/remote.go:51 +msgid "Accept certificate" +msgstr "" + +#: lxc/remote.go:267 +#, c-format +msgid "Admin password for %s: " +msgstr "" + +#: lxc/image.go:368 +msgid "Aliases:" +msgstr "" + +#: lxc/image.go:346 lxc/info.go:93 +#, c-format +msgid "Architecture: %s" +msgstr "" + +#: lxc/image.go:372 +#, c-format +msgid "Auto update: %s" +msgstr "" + +#: lxc/help.go:49 +msgid "Available commands:" +msgstr "" + +#: lxc/info.go:186 +msgid "Bytes received" +msgstr "" + +#: lxc/info.go:187 +msgid "Bytes sent" +msgstr "" + +#: lxc/config.go:274 +msgid "COMMON NAME" +msgstr "" + +#: lxc/info.go:150 +msgid "CPU usage (in seconds)" +msgstr "" + +#: lxc/list.go:428 +msgid "CREATED AT" +msgstr "" + +#: lxc/config.go:114 lxc/network.go:463 +#, c-format +msgid "Can't read from stdin: %s" +msgstr "" + +#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182 +#, c-format +msgid "Can't unset key '%s', it's not currently set." +msgstr "" + +#: lxc/network.go:390 lxc/profile.go:424 +msgid "Cannot provide container name to list" +msgstr "" + +#: lxc/remote.go:217 +#, c-format +msgid "Certificate fingerprint: %x" +msgstr "" + +#: lxc/action.go:34 +#, c-format +msgid "" +"Change state of one or more containers to %s.\n" +"\n" +"lxc %s [:] [[:]...]%s" +msgstr "" + +#: lxc/finger.go:15 +msgid "" +"Check if the LXD instance is up.\n" +"\n" +"lxc finger [:]" +msgstr "" + +#: lxc/remote.go:290 +msgid "Client certificate stored at server: " +msgstr "" + +#: lxc/list.go:122 lxc/list.go:123 +msgid "Columns" +msgstr "" + +#: lxc/copy.go:32 lxc/copy.go:33 lxc/init.go:136 lxc/init.go:137 +msgid "Config key/value to apply to the new container" +msgstr "" + +#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:737 lxc/network.go:346 +#: lxc/profile.go:218 +#, c-format +msgid "Config parsing error: %s" +msgstr "" + +#: lxc/main.go:29 +msgid "Connection refused; is LXD running?" +msgstr "" + +#: lxc/publish.go:62 +msgid "Container name is mandatory" +msgstr "" + +#: lxc/copy.go:141 lxc/copy.go:242 lxc/init.go:230 +#, c-format +msgid "Container name is: %s" +msgstr "" + +#: lxc/publish.go:152 lxc/publish.go:167 +#, c-format +msgid "Container published with fingerprint: %s" +msgstr "" + +#: lxc/image.go:166 +msgid "Copy aliases from source" +msgstr "" + +#: lxc/copy.go:25 +msgid "" +"Copy containers within or in between LXD instances.\n" +"\n" +"lxc copy [:][/] [[:]] [--" +"ephemeral|e] [--profile|-p ...] [--config|-c ...]" +msgstr "" + +#: lxc/image.go:279 +#, c-format +msgid "Copying the image: %s" +msgstr "" + +#: lxc/remote.go:232 +msgid "Could not create server cert dir" +msgstr "" + +#: lxc/file.go:83 +#, c-format +msgid "Could not sanitize path %s" +msgstr "" + +#: lxc/snapshot.go:21 +msgid "" +"Create a read-only snapshot of a container.\n" +"\n" +"lxc snapshot [:] [--stateful]\n" +"\n" +"Creates a snapshot of the container (optionally with the container's memory\n" +"state). When --stateful is used, LXD attempts to checkpoint the container's\n" +"running state, including process memory state, TCP connections, etc. so that " +"it\n" +"can be restored (via lxc restore) at a later time (although some things, e." +"g.\n" +"TCP connections after the TCP timeout window has expired, may not be " +"restored\n" +"successfully).\n" +"\n" +"Example:\n" +" lxc snapshot u1 snap0" +msgstr "" + +#: lxc/file.go:59 lxc/file.go:60 +msgid "Create any directories necessary" +msgstr "" + +#: lxc/image.go:351 lxc/info.go:95 +#, c-format +msgid "Created: %s" +msgstr "" + +#: lxc/init.go:181 lxc/launch.go:127 +#, c-format +msgid "Creating %s" +msgstr "" + +#: lxc/init.go:179 +msgid "Creating the container" +msgstr "" + +#: lxc/image.go:644 lxc/image.go:685 +msgid "DESCRIPTION" +msgstr "" + +#: lxc/publish.go:38 +msgid "Define a compression algorithm: for image or none" +msgstr "" + +#: lxc/delete.go:26 +msgid "" +"Delete containers or snapshots.\n" +"\n" +"lxc delete [:][/] [[:][/" +"]...]\n" +"\n" +"Destroy containers or snapshots with any attached data (configuration, " +"snapshots, ...)." +msgstr "" + +#: lxc/config.go:652 +#, c-format +msgid "Device %s added to %s" +msgstr "" + +#: lxc/config.go:839 +#, c-format +msgid "Device %s removed from %s" +msgstr "" + +#: lxc/list.go:574 +msgid "EPHEMERAL" +msgstr "" + +#: lxc/config.go:276 +msgid "EXPIRY DATE" +msgstr "" + +#: lxc/main.go:41 +msgid "Enable debug mode" +msgstr "" + +#: lxc/main.go:40 +msgid "Enable verbose mode" +msgstr "" + +#: lxc/exec.go:55 +msgid "Environment variable to set (e.g. HOME=/home/foo)" +msgstr "" + +#: lxc/help.go:69 +msgid "Environment:" +msgstr "" + +#: lxc/copy.go:36 lxc/copy.go:37 lxc/init.go:140 lxc/init.go:141 +msgid "Ephemeral container" +msgstr "" + +#: lxc/monitor.go:56 +msgid "Event type to listen for" +msgstr "" + +#: lxc/exec.go:46 +msgid "" +"Execute the specified command in a container.\n" +"\n" +"lxc exec [:] [--mode=auto|interactive|non-interactive] [--" +"env KEY=VALUE...] [--] \n" +"\n" +"Mode defaults to non-interactive, interactive mode is selected if both stdin " +"AND stdout are terminals (stderr is ignored)." +msgstr "" + +#: lxc/image.go:355 +#, c-format +msgid "Expires: %s" +msgstr "" + +#: lxc/image.go:357 +msgid "Expires: never" +msgstr "" + +#: lxc/config.go:273 lxc/image.go:642 lxc/image.go:684 +msgid "FINGERPRINT" +msgstr "" + +#: lxc/list.go:125 +msgid "Fast mode (same as --columns=nsacPt" +msgstr "" + +#: lxc/image.go:344 +#, c-format +msgid "Fingerprint: %s" +msgstr "" + +#: lxc/action.go:43 lxc/action.go:44 +msgid "Force the container to shutdown" +msgstr "" + +#: lxc/delete.go:35 lxc/delete.go:36 +msgid "Force the removal of stopped containers" +msgstr "" + +#: lxc/main.go:42 +msgid "Force using the local unix socket" +msgstr "" + +#: lxc/image.go:169 lxc/list.go:124 +msgid "Format" +msgstr "" + +#: lxc/remote.go:65 +msgid "Generating a client certificate. This may take a minute..." +msgstr "" + +#: lxc/help.go:25 +msgid "" +"Help page for the LXD client.\n" +"\n" +"lxc help [--all]" +msgstr "" + +#: lxc/list.go:425 +msgid "IPV4" +msgstr "" + +#: lxc/list.go:426 +msgid "IPV6" +msgstr "" + +#: lxc/config.go:275 +msgid "ISSUE DATE" +msgstr "" + +#: lxc/main.go:136 +msgid "" +"If this is your first time using LXD, you should also run: sudo lxd init" +msgstr "" + +#: lxc/main.go:43 +msgid "Ignore aliases when determining what command to run" +msgstr "" + +#: lxc/action.go:47 +msgid "Ignore the container state (only for start)" +msgstr "" + +#: lxc/image.go:282 +msgid "Image copied successfully!" +msgstr "" + +#: lxc/image.go:427 lxc/image.go:439 +#, c-format +msgid "Image imported with fingerprint: %s" +msgstr "" + +#: lxc/image.go:424 +#, c-format +msgid "Importing the image: %s" +msgstr "" + +#: lxc/init.go:74 +msgid "" +"Initialize a container from a particular image.\n" +"\n" +"lxc init [:] [:][] [--ephemeral|-e] [--profile|-" +"p ...] [--config|-c ...] [--network|-n ]\n" +"\n" +"Initializes a container using the specified image and name.\n" +"\n" +"Not specifying -p will result in the default profile.\n" +"Specifying \"-p\" with no argument will result in no profile.\n" +"\n" +"Example:\n" +" lxc init ubuntu:16.04 u1" +msgstr "" + +#: lxc/remote.go:135 +#, c-format +msgid "Invalid URL scheme \"%s\" in \"%s\"" +msgstr "" + +#: lxc/config.go:254 +msgid "Invalid certificate" +msgstr "" + +#: lxc/init.go:30 lxc/init.go:35 +msgid "Invalid configuration key" +msgstr "" + +#: lxc/file.go:285 +#, c-format +msgid "Invalid source %s" +msgstr "" + +#: lxc/file.go:72 +#, c-format +msgid "Invalid target %s" +msgstr "" + +#: lxc/info.go:124 +msgid "Ips:" +msgstr "" + +#: lxc/image.go:167 +msgid "Keep the image up to date after initial copy" +msgstr "" + +#: lxc/list.go:429 +msgid "LAST USED AT" +msgstr "" + +#: lxc/main.go:27 +msgid "LXD socket not found; is LXD installed and running?" +msgstr "" + +#: lxc/image.go:360 +#, c-format +msgid "Last used: %s" +msgstr "" + +#: lxc/image.go:362 +msgid "Last used: never" +msgstr "" + +#: lxc/launch.go:23 +msgid "" +"Launch a container from a particular image.\n" +"\n" +"lxc launch [:] [:][] [--ephemeral|-e] [--" +"profile|-p ...] [--config|-c ...] [--network|-n " +"]\n" +"\n" +"Launches a container using the specified image and name.\n" +"\n" +"Not specifying -p will result in the default profile.\n" +"Specifying \"-p\" with no argument will result in no profile.\n" +"\n" +"Example:\n" +" lxc launch ubuntu:16.04 u1" +msgstr "" + +#: lxc/info.go:25 +msgid "" +"List information on LXD servers and containers.\n" +"\n" +"For a container:\n" +" lxc info [] [--show-log]\n" +"\n" +"For a server:\n" +" lxc info []" +msgstr "" + +#: lxc/list.go:69 +msgid "" +"Lists the containers.\n" +"\n" +"lxc list [:] [filters] [--format table|json] [-c ] [--" +"fast]\n" +"\n" +"The filters are:\n" +"* A single keyword like \"web\" which will list any container with a name " +"starting by \"web\".\n" +"* A regular expression on the container name. (e.g. .*web.*01$)\n" +"* A key/value pair referring to a configuration item. For those, the " +"namespace can be abbreviated to the smallest unambiguous identifier:\n" +" * \"user.blah=abc\" will list all containers with the \"blah\" user " +"property set to \"abc\".\n" +" * \"u.blah=abc\" will do the same\n" +" * \"security.privileged=1\" will list all privileged containers\n" +" * \"s.privileged=1\" will do the same\n" +"* A regular expression matching a configuration item or its value. (e.g. " +"volatile.eth0.hwaddr=00:16:3e:.*)\n" +"\n" +"The -c option takes a comma separated list of arguments that control\n" +"which container attributes to output when displaying in table format.\n" +"Column arguments are either pre-defined shorthand chars (see below),\n" +"or (extended) config keys. Commas between consecutive shorthand chars\n" +"are optional.\n" +"\n" +"Pre-defined shorthand chars:\n" +"* 4 - IPv4 address\n" +"* 6 - IPv6 address\n" +"* a - architecture\n" +"* c - creation date\n" +"* l - last used date\n" +"* n - name\n" +"* p - pid of container init process\n" +"* P - profiles\n" +"* s - state\n" +"* S - number of snapshots\n" +"* t - type (persistent or ephemeral)\n" +"\n" +"Config key syntax: key[:name][:maxWidth]\n" +"* key - The (extended) config key to display\n" +"* name - Name to display in the column header, defaults to the key\n" +" if not specified or if empty (to allow defining maxWidth\n" +" without a custom name, e.g. user.key::0)\n" +"* maxWidth - Max width of the column (longer results are truncated).\n" +" -1 == unlimited\n" +" 0 == width of column header\n" +" >0 == max width in chars\n" +" Default is -1 (unlimited)\n" +"\n" +"Default column layout: ns46tS\n" +"Fast column layout: nsacPt\n" +"\n" +"Example:\n" +" lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0." +"hwaddr:MAC" +msgstr "" + +#: lxc/info.go:239 +msgid "Log:" +msgstr "" + +#: lxc/network.go:428 +msgid "MANAGED" +msgstr "" + +#: lxc/image.go:165 +msgid "Make image public" +msgstr "" + +#: lxc/publish.go:34 +msgid "Make the image public" +msgstr "" + +#: lxc/profile.go:49 +msgid "" +"Manage configuration profiles.\n" +"\n" +"lxc profile list [:] List " +"available profiles.\n" +"lxc profile show [:] Show details " +"of a profile.\n" +"lxc profile create [:] Create a " +"profile.\n" +"lxc profile copy [:] [:] Copy the " +"profile.\n" +"lxc profile get [:] Get profile " +"configuration.\n" +"lxc profile set [:] Set profile " +"configuration.\n" +"lxc profile unset [:] Unset " +"profile configuration.\n" +"lxc profile delete [:] Delete a " +"profile.\n" +"lxc profile edit [:]\n" +" Edit profile, either by launching external editor or reading STDIN.\n" +" Example: lxc profile edit # launch editor\n" +" cat profile.yaml | lxc profile edit # read from " +"profile.yaml\n" +"\n" +"lxc profile assign [:] \n" +" Assign a comma-separated list of profiles to a container, in order.\n" +" All profiles passed in this call (and only those) will be applied\n" +" to the specified container, i.e. it sets the list of profiles exactly " +"to\n" +" those specified in this command. To add/remove a particular profile from " +"a\n" +" container, use {add|remove} below.\n" +" Example: lxc profile assign foo default,bar # Apply default and bar\n" +" lxc profile assign foo default # Only default is active\n" +" lxc profile assign '' # no profiles are applied anymore\n" +" lxc profile assign bar,default # Apply default second now\n" +"lxc profile add [:] # add a profile to a " +"container\n" +"lxc profile remove [:] # remove the profile " +"from a container\n" +"\n" +"Devices:\n" +"lxc profile device list [:] " +"List devices in the given profile.\n" +"lxc profile device show [:] " +"Show full device details in the given profile.\n" +"lxc profile device remove [:] " +"Remove a device from a profile.\n" +"lxc profile device get [:] " +"Get a device property.\n" +"lxc profile device set [:] " +"Set a device property.\n" +"lxc profile device unset [:] " +"Unset a device property.\n" +"lxc profile device add [:] [key=value...]\n" +" Add a profile device, such as a disk or a nic, to the containers using " +"the specified profile." +msgstr "" + +#: lxc/config.go:58 +msgid "" +"Manage configuration.\n" +"\n" +"lxc config device add [:] " +"[key=value...] Add a device to a container.\n" +"lxc config device get [:] " +" Get a device property.\n" +"lxc config device set [:] " +" Set a device property.\n" +"lxc config device unset [:] " +" Unset a device property.\n" +"lxc config device list " +"[:] List devices for " +"container.\n" +"lxc config device show " +"[:] Show full device " +"details for container.\n" +"lxc config device remove [:] " +" Remove device from container.\n" +"\n" +"lxc config get [:][container] " +" Get container or server " +"configuration key.\n" +"lxc config set [:][container] " +" Set container or server configuration " +"key.\n" +"lxc config unset [:][container] " +" Unset container or server " +"configuration key.\n" +"lxc config show [:][container] [--" +"expanded] Show container or server configuration.\n" +"lxc config edit [:]" +"[container] Edit container or server " +"configuration in external editor.\n" +" Edit configuration, either by launching external editor or reading " +"STDIN.\n" +" Example: lxc config edit # launch editor\n" +" cat config.yaml | lxc config edit # read from " +"config.yaml\n" +"\n" +"lxc config trust list " +"[:] List all trusted " +"certs.\n" +"lxc config trust add [:] Add certfile.crt to trusted hosts.\n" +"lxc config trust remove [:] [hostname|" +"fingerprint] Remove the cert from trusted hosts.\n" +"\n" +"Examples:\n" +"To mount host's /share/c1 onto /opt in the container:\n" +" lxc config device add [:]container1 disk source=/" +"share/c1 path=opt\n" +"\n" +"To set an lxc config value:\n" +" lxc config set [:] raw.lxc 'lxc.aa_allow_incomplete = " +"1'\n" +"\n" +"To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the " +"default):\n" +" lxc config set core.https_address [::]:8443\n" +"\n" +"To set the server trust password:\n" +" lxc config set core.trust_password blah" +msgstr "" + +#: lxc/file.go:36 +msgid "" +"Manage files in a container.\n" +"\n" +"lxc file pull [-r|--recursive] [:] " +"[[:]...] \n" +"lxc file push [-r|--recursive] [-p|--create-dirs] [--uid=UID] [--gid=GID] [--" +"mode=MODE] [...] [:]\n" +"lxc file edit [:]/\n" +"\n" +" in the case of pull, in the case of push and in the " +"case of edit are /\n" +"\n" +"Examples:\n" +"To push /etc/hosts into the container foo:\n" +" lxc file push /etc/hosts foo/etc/hosts\n" +"\n" +"To pull /etc/hosts from the container:\n" +" lxc file pull foo/etc/hosts ." +msgstr "" + +#: lxc/network.go:49 +msgid "" +"Manage networks.\n" +"\n" +"lxc network list [:] List available " +"networks.\n" +"lxc network show [:] Show details of a " +"network.\n" +"lxc network create [:] [key=value...] Create a network.\n" +"lxc network get [:] Get network " +"configuration.\n" +"lxc network set [:] Set network " +"configuration.\n" +"lxc network unset [:] Unset network " +"configuration.\n" +"lxc network delete [:] Delete a network.\n" +"lxc network edit [:]\n" +" Edit network, either by launching external editor or reading STDIN.\n" +" Example: lxc network edit # launch editor\n" +" cat network.yaml | lxc network edit # read from " +"network.yaml\n" +"\n" +"lxc network attach [:] [device name]\n" +"lxc network attach-profile [:] [device name]\n" +"\n" +"lxc network detach [:] [device name]\n" +"lxc network detach-profile [:] [device name]" +msgstr "" + +#: lxc/remote.go:37 +msgid "" +"Manage remote LXD servers.\n" +"\n" +"lxc remote add [] [--accept-certificate] [--" +"password=PASSWORD]\n" +" [--public] [--protocol=PROTOCOL] " +"Add the remote at .\n" +"lxc remote remove " +"Remove the remote .\n" +"lxc remote list " +"List all remotes.\n" +"lxc remote rename " +"Rename remote to .\n" +"lxc remote set-url " +"Update 's url to .\n" +"lxc remote set-default " +"Set the default remote.\n" +"lxc remote get-default " +"Print the default remote." +msgstr "" + +#: lxc/image.go:96 +msgid "" +"Manipulate container images.\n" +"\n" +"In LXD containers are created from images. Those images were themselves\n" +"either generated from an existing container or downloaded from an image\n" +"server.\n" +"\n" +"When using remote images, LXD will automatically cache images for you\n" +"and remove them upon expiration.\n" +"\n" +"The image unique identifier is the hash (sha-256) of its representation\n" +"as a compressed tarball (or for split images, the concatenation of the\n" +"metadata and rootfs tarballs).\n" +"\n" +"Images can be referenced by their full hash, shortest unique partial\n" +"hash or alias name (if one is set).\n" +"\n" +"\n" +"lxc image import [|] [:] [--public] " +"[--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] " +"[--alias=ALIAS...] [prop=value]\n" +" Import an image tarball (or tarballs) into the LXD image store.\n" +"\n" +"lxc image copy [:] : [--alias=ALIAS...] [--copy-" +"aliases] [--public] [--auto-update]\n" +" Copy an image from one LXD daemon to another over the network.\n" +"\n" +" The auto-update flag instructs the server to keep this image up to\n" +" date. It requires the source to be an alias and for it to be public.\n" +"\n" +"lxc image delete [:] [[:]...]\n" +" Delete one or more images from the LXD image store.\n" +"\n" +"lxc image export [:] [target]\n" +" Export an image from the LXD image store into a distributable tarball.\n" +"\n" +" The output target is optional and defaults to the working directory.\n" +" The target may be an existing directory, file name, or \"-\" to specify\n" +" stdout. The target MUST be a directory when exporting a split image.\n" +" If the target is a directory, the image's name (each part's name for\n" +" split images) as found in the database will be used for the exported\n" +" image. If the target is a file (not a directory and not stdout), then\n" +" the appropriate extension will be appended to the provided file name\n" +" based on the algorithm used to compress the image. \n" +"\n" +"lxc image info [:]\n" +" Print everything LXD knows about a given image.\n" +"\n" +"lxc image list [:] [filter] [--format table|json]\n" +" List images in the LXD image store. Filters may be of the\n" +" = form for property based filtering, or part of the image\n" +" hash or part of the image alias name.\n" +"\n" +"lxc image show [:]\n" +" Yaml output of the user modifiable properties of an image.\n" +"\n" +"lxc image edit [:]\n" +" Edit image, either by launching external editor or reading STDIN.\n" +" Example: lxc image edit # launch editor\n" +" cat image.yaml | lxc image edit # read from image.yaml\n" +"\n" +"lxc image alias create [:] \n" +" Create a new alias for an existing image.\n" +"\n" +"lxc image alias delete [:]\n" +" Delete an alias.\n" +"\n" +"lxc image alias list [:] [filter]\n" +" List the aliases. Filters may be part of the image hash or part of the " +"image alias name." +msgstr "" + +#: lxc/info.go:161 +msgid "Memory (current)" +msgstr "" + +#: lxc/info.go:165 +msgid "Memory (peak)" +msgstr "" + +#: lxc/help.go:87 +msgid "Missing summary." +msgstr "" + +#: lxc/monitor.go:41 +msgid "" +"Monitor activity on the LXD server.\n" +"\n" +"lxc monitor [:] [--type=TYPE...]\n" +"\n" +"Connects to the monitoring interface of the specified LXD server.\n" +"\n" +"By default will listen to all message types.\n" +"Specific types to listen to can be specified with --type.\n" +"\n" +"Example:\n" +" lxc monitor --type=logging" +msgstr "" + +#: lxc/network.go:216 lxc/network.go:265 +msgid "More than one device matches, specify the device name." +msgstr "" + +#: lxc/file.go:273 +msgid "More than one file to download, but target is not a directory" +msgstr "" + +#: lxc/move.go:16 +msgid "" +"Move containers within or in between lxd instances.\n" +"\n" +"lxc move [:] [:][]\n" +" Move a container between two hosts, renaming it if destination name " +"differs.\n" +"\n" +"lxc move \n" +" Rename a local container.\n" +"\n" +"lxc move / /\n" +" Rename a snapshot." +msgstr "" + +#: lxc/action.go:70 +msgid "Must supply container name for: " +msgstr "" + +#: lxc/list.go:430 lxc/network.go:426 lxc/profile.go:451 lxc/remote.go:380 +msgid "NAME" +msgstr "" + +#: lxc/network.go:412 lxc/remote.go:354 lxc/remote.go:359 +msgid "NO" +msgstr "" + +#: lxc/info.go:89 +#, c-format +msgid "Name: %s" +msgstr "" + +#: lxc/network.go:190 +#, c-format +msgid "Network %s created" +msgstr "" + +#: lxc/network.go:293 +#, c-format +msgid "Network %s deleted" +msgstr "" + +#: lxc/init.go:142 lxc/init.go:143 +msgid "Network name" +msgstr "" + +#: lxc/image.go:168 lxc/publish.go:35 +msgid "New alias to define at target" +msgstr "" + +#: lxc/config.go:285 +msgid "No certificate provided to add" +msgstr "" + +#: lxc/network.go:225 lxc/network.go:274 +msgid "No device found for this network" +msgstr "" + +#: lxc/config.go:308 +msgid "No fingerprint specified." +msgstr "" + +#: lxc/remote.go:120 +msgid "Only https URLs are supported for simplestreams" +msgstr "" + +#: lxc/image.go:430 +msgid "Only https:// is supported for remote image import." +msgstr "" + +#: lxc/network.go:322 lxc/network.go:449 +msgid "Only managed networks can be modified." +msgstr "" + +#: lxc/help.go:63 lxc/main.go:112 +msgid "Options:" +msgstr "" + +#: lxc/image.go:541 +#, c-format +msgid "Output is in %s" +msgstr "" + +#: lxc/exec.go:56 +msgid "Override the terminal mode (auto, interactive or non-interactive)" +msgstr "" + +#: lxc/list.go:576 +msgid "PERSISTENT" +msgstr "" + +#: lxc/list.go:431 +msgid "PID" +msgstr "" + +#: lxc/list.go:432 +msgid "PROFILES" +msgstr "" + +#: lxc/remote.go:382 +msgid "PROTOCOL" +msgstr "" + +#: lxc/image.go:643 lxc/remote.go:383 +msgid "PUBLIC" +msgstr "" + +#: lxc/info.go:188 +msgid "Packets received" +msgstr "" + +#: lxc/info.go:189 +msgid "Packets sent" +msgstr "" + +#: lxc/help.go:70 +msgid "Path to an alternate client configuration directory" +msgstr "" + +#: lxc/help.go:71 +msgid "Path to an alternate server directory" +msgstr "" + +#: lxc/main.go:31 +msgid "Permission denied, are you in the lxd group?" +msgstr "" + +#: lxc/info.go:106 +#, c-format +msgid "Pid: %d" +msgstr "" + +#: lxc/network.go:347 lxc/profile.go:219 +msgid "Press enter to open the editor again" +msgstr "" + +#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:738 +msgid "Press enter to start the editor again" +msgstr "" + +#: lxc/manpage.go:18 +msgid "Print all the subcommands help." +msgstr "" + +#: lxc/help.go:65 +msgid "Print debug information" +msgstr "" + +#: lxc/help.go:64 +msgid "Print less common commands" +msgstr "" + +#: lxc/help.go:66 +msgid "Print verbose information" +msgstr "" + +#: lxc/version.go:18 +msgid "" +"Prints the version number of this client tool.\n" +"\n" +"lxc version" +msgstr "" + +#: lxc/info.go:130 +#, c-format +msgid "Processes: %d" +msgstr "" + +#: lxc/profile.go:275 +#, c-format +msgid "Profile %s added to %s" +msgstr "" + +#: lxc/profile.go:170 +#, c-format +msgid "Profile %s created" +msgstr "" + +#: lxc/profile.go:240 +#, c-format +msgid "Profile %s deleted" +msgstr "" + +#: lxc/profile.go:306 +#, c-format +msgid "Profile %s removed from %s" +msgstr "" + +#: lxc/copy.go:34 lxc/copy.go:35 lxc/init.go:138 lxc/init.go:139 +msgid "Profile to apply to the new container" +msgstr "" + +#: lxc/profile.go:256 +#, c-format +msgid "Profiles %s applied to %s" +msgstr "" + +#: lxc/info.go:104 +#, c-format +msgid "Profiles: %s" +msgstr "" + +#: lxc/image.go:364 +msgid "Properties:" +msgstr "" + +#: lxc/remote.go:54 +msgid "Public image server" +msgstr "" + +#: lxc/image.go:347 +#, c-format +msgid "Public: %s" +msgstr "" + +#: lxc/publish.go:27 +msgid "" +"Publish containers as images.\n" +"\n" +"lxc publish [:][/] [:] [--" +"alias=ALIAS...] [prop-key=prop-value...]" +msgstr "" + +#: lxc/file.go:57 lxc/file.go:58 +msgid "Recursively push or pull files" +msgstr "" + +#: lxc/remote.go:52 +msgid "Remote admin password" +msgstr "" + +#: lxc/info.go:91 +#, c-format +msgid "Remote: %s" +msgstr "" + +#: lxc/delete.go:43 +#, c-format +msgid "Remove %s (yes/no): " +msgstr "" + +#: lxc/delete.go:37 lxc/delete.go:38 +msgid "Require user confirmation" +msgstr "" + +#: lxc/info.go:127 +msgid "Resources:" +msgstr "" + +#: lxc/restore.go:21 +msgid "" +"Restore a container's state to a previous snapshot.\n" +"\n" +"lxc restore [:] [--stateful]\n" +"\n" +"Restores a container from a snapshot (optionally with running state, see\n" +"snapshot help for details).\n" +"\n" +"Examples:\n" +"Create the snapshot:\n" +" lxc snapshot u1 snap0\n" +"\n" +"Restore the snapshot:\n" +" lxc restore u1 snap0" +msgstr "" + +#: lxc/init.go:240 +#, c-format +msgid "Retrieving image: %s" +msgstr "" + +#: lxc/image.go:646 +msgid "SIZE" +msgstr "" + +#: lxc/list.go:433 +msgid "SNAPSHOTS" +msgstr "" + +#: lxc/list.go:434 +msgid "STATE" +msgstr "" + +#: lxc/remote.go:384 +msgid "STATIC" +msgstr "" + +#: lxc/remote.go:225 +msgid "Server certificate NACKed by user" +msgstr "" + +#: lxc/remote.go:287 +msgid "Server doesn't trust us after adding our cert" +msgstr "" + +#: lxc/remote.go:53 +msgid "Server protocol (lxd or simplestreams)" +msgstr "" + +#: lxc/file.go:55 +msgid "Set the file's gid on push" +msgstr "" + +#: lxc/file.go:56 +msgid "Set the file's perms on push" +msgstr "" + +#: lxc/file.go:54 +msgid "Set the file's uid on push" +msgstr "" + +#: lxc/help.go:32 +msgid "Show all commands (not just interesting ones)" +msgstr "" + +#: lxc/help.go:67 +msgid "Show client version" +msgstr "" + +#: lxc/info.go:36 +msgid "Show the container's last 100 log lines?" +msgstr "" + +#: lxc/config.go:33 +msgid "Show the expanded configuration" +msgstr "" + +#: lxc/image.go:345 +#, c-format +msgid "Size: %.2fMB" +msgstr "" + +#: lxc/info.go:208 +msgid "Snapshots:" +msgstr "" + +#: lxc/image.go:374 +msgid "Source:" +msgstr "" + +#: lxc/launch.go:136 +#, c-format +msgid "Starting %s" +msgstr "" + +#: lxc/info.go:98 +#, c-format +msgid "Status: %s" +msgstr "" + +#: lxc/publish.go:36 lxc/publish.go:37 +msgid "Stop the container if currently running" +msgstr "" + +#: lxc/delete.go:107 lxc/publish.go:114 +msgid "Stopping container failed!" +msgstr "" + +#: lxc/action.go:46 +msgid "Store the container state (only for stop)" +msgstr "" + +#: lxc/info.go:169 +msgid "Swap (current)" +msgstr "" + +#: lxc/info.go:173 +msgid "Swap (peak)" +msgstr "" + +#: lxc/list.go:435 lxc/network.go:427 +msgid "TYPE" +msgstr "" + +#: lxc/delete.go:93 +msgid "The container is currently running, stop it first or pass --force." +msgstr "" + +#: lxc/publish.go:92 +msgid "" +"The container is currently running. Use --force to have it stopped and " +"restarted." +msgstr "" + +#: lxc/init.go:313 +msgid "The container you are starting doesn't have any network attached to it." +msgstr "" + +#: lxc/config.go:680 lxc/config.go:692 lxc/config.go:725 lxc/config.go:743 +#: lxc/config.go:781 lxc/config.go:799 +msgid "The device doesn't exist" +msgstr "" + +#: lxc/init.go:297 +#, c-format +msgid "The local image '%s' couldn't be found, trying '%s:' instead." +msgstr "" + +#: lxc/main.go:181 +msgid "The opposite of `lxc pause` is `lxc start`." +msgstr "" + +#: lxc/network.go:230 lxc/network.go:279 +msgid "The specified device doesn't exist" +msgstr "" + +#: lxc/network.go:234 lxc/network.go:283 +msgid "The specified device doesn't match the network" +msgstr "" + +#: lxc/publish.go:65 +msgid "There is no \"image name\". Did you want an alias?" +msgstr "" + +#: lxc/action.go:42 +msgid "Time to wait for the container before killing it" +msgstr "" + +#: lxc/image.go:348 +msgid "Timestamps:" +msgstr "" + +#: lxc/init.go:315 +msgid "To attach a network to a container, use: lxc network attach" +msgstr "" + +#: lxc/init.go:314 +msgid "To create a new network, use: lxc network create" +msgstr "" + +#: lxc/main.go:137 +msgid "To start your first container, try: lxc launch ubuntu:16.04" +msgstr "" + +#: lxc/image.go:432 +#, c-format +msgid "Transferring image: %s" +msgstr "" + +#: lxc/action.go:100 lxc/launch.go:149 +#, c-format +msgid "Try `lxc info --show-log %s` for more info" +msgstr "" + +#: lxc/info.go:100 +msgid "Type: ephemeral" +msgstr "" + +#: lxc/info.go:102 +msgid "Type: persistent" +msgstr "" + +#: lxc/image.go:647 +msgid "UPLOAD DATE" +msgstr "" + +#: lxc/remote.go:381 +msgid "URL" +msgstr "" + +#: lxc/network.go:429 lxc/profile.go:452 +msgid "USED BY" +msgstr "" + +#: lxc/remote.go:95 +msgid "Unable to read remote TLS certificate" +msgstr "" + +#: lxc/image.go:353 +#, c-format +msgid "Uploaded: %s" +msgstr "" + +#: lxc/main.go:112 +#, c-format +msgid "Usage: %s" +msgstr "" + +#: lxc/help.go:48 +msgid "Usage: lxc [options]" +msgstr "" + +#: lxc/delete.go:47 +msgid "User aborted delete operation." +msgstr "" + +#: lxc/restore.go:38 +msgid "" +"Whether or not to restore the container's running state from snapshot (if " +"available)" +msgstr "" + +#: lxc/snapshot.go:38 +msgid "Whether or not to snapshot the container's running state" +msgstr "" + +#: lxc/network.go:414 lxc/remote.go:356 lxc/remote.go:361 +msgid "YES" +msgstr "" + +#: lxc/main.go:52 +msgid "`lxc config profile` is deprecated, please use `lxc profile`" +msgstr "" + +#: lxc/launch.go:120 +msgid "bad number of things scanned from image, container or snapshot" +msgstr "" + +#: lxc/action.go:96 +msgid "bad result type from action" +msgstr "" + +#: lxc/copy.go:116 +msgid "can't copy to the same container name" +msgstr "" + +#: lxc/file.go:308 +msgid "can't pull a directory without --recursive" +msgstr "" + +#: lxc/remote.go:344 +msgid "can't remove the default remote" +msgstr "" + +#: lxc/file.go:119 +msgid "can't supply uid/gid/mode in recursive mode" +msgstr "" + +#: lxc/remote.go:370 +msgid "default" +msgstr "" + +#: lxc/copy.go:132 lxc/copy.go:137 lxc/copy.go:233 lxc/copy.go:238 +#: lxc/init.go:220 lxc/init.go:225 lxc/launch.go:104 lxc/launch.go:109 +msgid "didn't get any affected image, container or snapshot from server" +msgstr "" + +#: lxc/image.go:339 +msgid "disabled" +msgstr "" + +#: lxc/image.go:341 +msgid "enabled" +msgstr "" + +#: lxc/main.go:22 lxc/main.go:148 +#, c-format +msgid "error: %v" +msgstr "" + +#: lxc/help.go:40 lxc/main.go:107 +#, c-format +msgid "error: unknown command: %s" +msgstr "" + +#: lxc/launch.go:124 +msgid "got bad version" +msgstr "" + +#: lxc/image.go:334 lxc/image.go:623 +msgid "no" +msgstr "" + +#: lxc/copy.go:165 +msgid "not all the profiles from the source exist on the target" +msgstr "" + +#: lxc/remote.go:218 +msgid "ok (y/n)?" +msgstr "" + +#: lxc/main.go:304 lxc/main.go:308 +#, c-format +msgid "processing aliases failed %s\n" +msgstr "" + +#: lxc/file.go:349 +msgid "recursive edit doesn't make sense :(" +msgstr "" + +#: lxc/remote.go:406 +#, c-format +msgid "remote %s already exists" +msgstr "" + +#: lxc/remote.go:336 lxc/remote.go:398 lxc/remote.go:433 lxc/remote.go:449 +#, c-format +msgid "remote %s doesn't exist" +msgstr "" + +#: lxc/remote.go:319 +#, c-format +msgid "remote %s exists as <%s>" +msgstr "" + +#: lxc/remote.go:340 lxc/remote.go:402 lxc/remote.go:437 +#, c-format +msgid "remote %s is static and cannot be modified" +msgstr "" + +#: lxc/info.go:219 +msgid "stateful" +msgstr "" + +#: lxc/info.go:221 +msgid "stateless" +msgstr "" + +#: lxc/info.go:215 +#, c-format +msgid "taken at %s" +msgstr "" + +#: lxc/exec.go:186 +msgid "unreachable return reached" +msgstr "" + +#: lxc/main.go:236 +msgid "wrong number of subcommand arguments" +msgstr "" + +#: lxc/delete.go:46 lxc/image.go:336 lxc/image.go:627 +msgid "yes" +msgstr "" + +#: lxc/copy.go:45 +msgid "you must specify a source container name" +msgstr "" diff -Nru lxd-2.7/po/fr.po lxd-2.8/po/fr.po --- lxd-2.7/po/fr.po 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/po/fr.po 2017-01-25 02:50:49.000000000 +0000 @@ -7,14 +7,17 @@ msgstr "" "Project-Id-Version: LXD\n" "Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n" -"POT-Creation-Date: 2016-12-15 13:20-0500\n" -"PO-Revision-Date: 2016-12-16 14:08+0100\n" -"Last-Translator: Stéphane Graber \n" -"Language: \n" +"POT-Creation-Date: 2017-01-24 18:22-0500\n" +"PO-Revision-Date: 2017-01-12 17:32+0000\n" +"Last-Translator: Stéphane Graber \n" +"Language-Team: French \n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 2.11-dev\n" #: lxc/info.go:154 msgid " CPU usage:" @@ -32,7 +35,7 @@ msgid " Network usage:" msgstr " Réseau utilisé :" -#: lxc/config.go:36 +#: lxc/config.go:37 msgid "" "### This is a yaml representation of the configuration.\n" "### Any line starting with a '# will be ignored.\n" @@ -70,7 +73,7 @@ "###\n" "### Notez que le nom est affiché mais ne peut être modifié" -#: lxc/image.go:85 +#: lxc/image.go:86 msgid "" "### This is a yaml representation of the image properties.\n" "### Any line starting with a '# will be ignored.\n" @@ -86,7 +89,7 @@ "### Un exemple serait :\n" "### description: Mon image personnalisée" -#: lxc/network.go:28 +#: lxc/network.go:29 msgid "" "### This is a yaml representation of the network.\n" "### Any line starting with a '# will be ignored.\n" @@ -123,7 +126,7 @@ "###\n" "### Notez que seule la configuration peut être modifiée." -#: lxc/profile.go:27 +#: lxc/profile.go:28 msgid "" "### This is a yaml representation of the profile.\n" "### Any line starting with a '# will be ignored.\n" @@ -160,7 +163,7 @@ "###\n" "### Notez que le nom est affiché mais ne peut être modifié" -#: lxc/image.go:614 +#: lxc/image.go:620 #, c-format msgid "%s (%d more)" msgstr "%s (%d de plus)" @@ -169,21 +172,21 @@ msgid "'/' not allowed in snapshot name" msgstr "'/' n'est pas autorisé dans le nom d'un instantané" -#: lxc/profile.go:253 +#: lxc/profile.go:254 msgid "(none)" msgstr "(aucun)" -#: lxc/image.go:635 lxc/image.go:677 +#: lxc/image.go:641 lxc/image.go:683 msgid "ALIAS" -msgstr "" +msgstr "ALIAS" -#: lxc/image.go:639 +#: lxc/image.go:645 msgid "ARCH" -msgstr "" +msgstr "ARCH" -#: lxc/list.go:425 +#: lxc/list.go:427 msgid "ARCHITECTURE" -msgstr "" +msgstr "ARCHITECTURE" #: lxc/remote.go:51 msgid "Accept certificate" @@ -194,16 +197,16 @@ msgid "Admin password for %s: " msgstr "Mot de passe administrateur pour %s : " -#: lxc/image.go:362 +#: lxc/image.go:368 msgid "Aliases:" msgstr "Alias :" -#: lxc/image.go:345 lxc/info.go:93 +#: lxc/image.go:346 lxc/info.go:93 #, c-format msgid "Architecture: %s" msgstr "Architecture : %s" -#: lxc/image.go:366 +#: lxc/image.go:372 #, c-format msgid "Auto update: %s" msgstr "Mise à jour auto. : %s" @@ -220,30 +223,30 @@ msgid "Bytes sent" msgstr "Octets émis" -#: lxc/config.go:273 +#: lxc/config.go:274 msgid "COMMON NAME" -msgstr "" +msgstr "COMMON NAME" #: lxc/info.go:150 msgid "CPU usage (in seconds)" msgstr "CPU utilisé (en secondes)" -#: lxc/list.go:426 +#: lxc/list.go:428 msgid "CREATED AT" msgstr "CRÉÉ À" -#: lxc/config.go:113 +#: lxc/config.go:114 lxc/network.go:463 #, c-format msgid "Can't read from stdin: %s" msgstr "Impossible de lire depuis stdin : %s" -#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181 +#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182 #, c-format msgid "Can't unset key '%s', it's not currently set." msgstr "" "Impossible de désaffecter la clé '%s', elle n'est pas définie actuellement." -#: lxc/network.go:385 lxc/profile.go:419 +#: lxc/network.go:390 lxc/profile.go:424 msgid "Cannot provide container name to list" msgstr "Impossible de fournir le nom du conteneur à lister" @@ -252,7 +255,7 @@ msgid "Certificate fingerprint: %x" msgstr "Empreinte du certificat : %x" -#: lxc/action.go:33 +#: lxc/action.go:34 #, c-format msgid "" "Change state of one or more containers to %s.\n" @@ -269,7 +272,7 @@ "\n" "lxc finger [:]" msgstr "" -"Contacte LXD pour vérifier qu'il est actif et fonctionel.\n" +"Vérifier si l'instance LXD est active.\n" "\n" "lxc finger [:]" @@ -277,17 +280,16 @@ msgid "Client certificate stored at server: " msgstr "Certificat client enregistré sur le serveur : " -#: lxc/list.go:121 lxc/list.go:122 +#: lxc/list.go:122 lxc/list.go:123 msgid "Columns" msgstr "Colonnes" -#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:135 lxc/init.go:136 -#: lxc/launch.go:40 lxc/launch.go:41 +#: lxc/copy.go:32 lxc/copy.go:33 lxc/init.go:136 lxc/init.go:137 msgid "Config key/value to apply to the new container" msgstr "Clé/valeur de configuration à appliquer au nouveau conteneur" -#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:731 lxc/network.go:341 -#: lxc/profile.go:217 +#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:737 lxc/network.go:346 +#: lxc/profile.go:218 #, c-format msgid "Config parsing error: %s" msgstr "Erreur lors de la lecture de la configuration : %s" @@ -296,25 +298,25 @@ msgid "Connection refused; is LXD running?" msgstr "Connexion refusée ; LXD est-il actif ?" -#: lxc/publish.go:61 +#: lxc/publish.go:62 msgid "Container name is mandatory" msgstr "Le nom du conteneur est obligatoire" -#: lxc/copy.go:140 lxc/copy.go:241 lxc/init.go:229 +#: lxc/copy.go:141 lxc/copy.go:242 lxc/init.go:230 #, c-format msgid "Container name is: %s" msgstr "Le nom du conteneur est : %s" -#: lxc/publish.go:151 lxc/publish.go:166 +#: lxc/publish.go:152 lxc/publish.go:167 #, c-format msgid "Container published with fingerprint: %s" msgstr "Conteneur publié avec l'empreinte : %s" -#: lxc/image.go:165 +#: lxc/image.go:166 msgid "Copy aliases from source" msgstr "Copier les alias depuis la source" -#: lxc/copy.go:24 +#: lxc/copy.go:25 msgid "" "Copy containers within or in between LXD instances.\n" "\n" @@ -326,10 +328,10 @@ "lxc copy [:][/] [[:]] [--" "ephemeral|e] [--profile|-p ...] [--config|-c ...]" -#: lxc/image.go:278 +#: lxc/image.go:279 #, c-format msgid "Copying the image: %s" -msgstr "Copier l'image : %s" +msgstr "Copie de l'image : %s" #: lxc/remote.go:232 msgid "Could not create server cert dir" @@ -338,7 +340,7 @@ #: lxc/file.go:83 #, c-format msgid "Could not sanitize path %s" -msgstr "" +msgstr "Impossible d'assainir le chemin %s" #: lxc/snapshot.go:21 msgid "" @@ -377,31 +379,31 @@ #: lxc/file.go:59 lxc/file.go:60 msgid "Create any directories necessary" -msgstr "Créer tout répertoire nécessaire" +msgstr "Créer tous répertoires nécessaires" -#: lxc/image.go:350 lxc/info.go:95 +#: lxc/image.go:351 lxc/info.go:95 #, c-format msgid "Created: %s" msgstr "Créé : %s" -#: lxc/init.go:180 lxc/launch.go:135 +#: lxc/init.go:181 lxc/launch.go:127 #, c-format msgid "Creating %s" msgstr "Création de %s" -#: lxc/init.go:178 +#: lxc/init.go:179 msgid "Creating the container" msgstr "Création du conteneur" -#: lxc/image.go:638 lxc/image.go:679 +#: lxc/image.go:644 lxc/image.go:685 msgid "DESCRIPTION" -msgstr "" +msgstr "DESCRIPTION" -#: lxc/publish.go:37 +#: lxc/publish.go:38 msgid "Define a compression algorithm: for image or none" msgstr "Définir un algorithme de compression : pour image ou aucun" -#: lxc/delete.go:25 +#: lxc/delete.go:26 msgid "" "Delete containers or snapshots.\n" "\n" @@ -411,50 +413,49 @@ "Destroy containers or snapshots with any attached data (configuration, " "snapshots, ...)." msgstr "" -"Supprimer des conteneurs ou des instantanés de conteneur.\n" +"Supprimer des conteneurs ou des instantanés.\n" "\n" "lxc delete [:][/] [:][[/" "]...]\n" "\n" -"Détruit les conteneurs ou les instantanés ainsi que toute donnée\n" -"associée (configuration, instantanés, …)." +"Détruit les conteneurs ou les instantanés ainsi que toute donnée associée " +"(configuration, instantanés, …)." -#: lxc/config.go:647 +#: lxc/config.go:652 #, c-format msgid "Device %s added to %s" msgstr "Périphérique %s ajouté à %s" -#: lxc/config.go:834 +#: lxc/config.go:839 #, c-format msgid "Device %s removed from %s" msgstr "Périphérique %s retiré de %s" -#: lxc/list.go:570 +#: lxc/list.go:574 msgid "EPHEMERAL" msgstr "ÉPHÉMÈRE" -#: lxc/config.go:275 +#: lxc/config.go:276 msgid "EXPIRY DATE" msgstr "DATE D'EXPIRATION" #: lxc/main.go:41 msgid "Enable debug mode" -msgstr "Active le mode de débogage" +msgstr "Activer le mode de débogage" #: lxc/main.go:40 msgid "Enable verbose mode" -msgstr "Active le mode verbeux" +msgstr "Activer le mode verbeux" -#: lxc/exec.go:54 +#: lxc/exec.go:55 msgid "Environment variable to set (e.g. HOME=/home/foo)" -msgstr "Une variable d'environnement (de la forme HOME=/home/foo)" +msgstr "Variable d'environnement (de la forme HOME=/home/foo) à positionner" #: lxc/help.go:69 msgid "Environment:" msgstr "Environnement :" -#: lxc/copy.go:35 lxc/copy.go:36 lxc/init.go:139 lxc/init.go:140 -#: lxc/launch.go:44 lxc/launch.go:45 +#: lxc/copy.go:36 lxc/copy.go:37 lxc/init.go:140 lxc/init.go:141 msgid "Ephemeral container" msgstr "Conteneur éphémère" @@ -462,7 +463,7 @@ msgid "Event type to listen for" msgstr "Type d'évènements à surveiller" -#: lxc/exec.go:45 +#: lxc/exec.go:46 msgid "" "Execute the specified command in a container.\n" "\n" @@ -472,7 +473,7 @@ "Mode defaults to non-interactive, interactive mode is selected if both stdin " "AND stdout are terminals (stderr is ignored)." msgstr "" -"Exécute la commande spécifiée dans un conteneur.\n" +"Exécuter la commande indiquée dans un conteneur.\n" "\n" "lxc exec [:] [--mode=auto|interactive|non-interactive] [--" "env KEY=VALUE...] [--] \n" @@ -481,43 +482,43 @@ "sélectionné si à la fois stdin et stdout sont des terminaux (stderr\n" "est ignoré)." -#: lxc/image.go:354 +#: lxc/image.go:355 #, c-format msgid "Expires: %s" msgstr "Expire : %s" -#: lxc/image.go:356 +#: lxc/image.go:357 msgid "Expires: never" msgstr "N'expire jamais" -#: lxc/config.go:272 lxc/image.go:636 lxc/image.go:678 +#: lxc/config.go:273 lxc/image.go:642 lxc/image.go:684 msgid "FINGERPRINT" msgstr "EMPREINTE" -#: lxc/list.go:124 +#: lxc/list.go:125 msgid "Fast mode (same as --columns=nsacPt" msgstr "Mode rapide (identique à --columns=nsacPt" -#: lxc/image.go:343 +#: lxc/image.go:344 #, c-format msgid "Fingerprint: %s" msgstr "Empreinte : %s" -#: lxc/action.go:42 lxc/action.go:43 +#: lxc/action.go:43 lxc/action.go:44 msgid "Force the container to shutdown" -msgstr "Force le conteneur à s'arrêter" +msgstr "Forcer le conteneur à s'arrêter" -#: lxc/delete.go:34 lxc/delete.go:35 +#: lxc/delete.go:35 lxc/delete.go:36 msgid "Force the removal of stopped containers" -msgstr "Force la suppression des conteneurs arrêtés" +msgstr "Forcer la suppression des conteneurs arrêtés" #: lxc/main.go:42 msgid "Force using the local unix socket" -msgstr "Force l'utilisation de la socket unix locale" +msgstr "Forcer l'utilisation de la socket unix locale" -#: lxc/image.go:168 lxc/list.go:123 +#: lxc/image.go:169 lxc/list.go:124 msgid "Format" -msgstr "" +msgstr "Format" #: lxc/remote.go:65 msgid "Generating a client certificate. This may take a minute..." @@ -529,19 +530,19 @@ "\n" "lxc help [--all]" msgstr "" -"Explique comment utiliser LXD.\n" +"Page d'aide du client LXD.\n" "\n" "lxc help [--all]" -#: lxc/list.go:423 +#: lxc/list.go:425 msgid "IPV4" -msgstr "" +msgstr "IPv4" -#: lxc/list.go:424 +#: lxc/list.go:426 msgid "IPV6" -msgstr "" +msgstr "IPv6" -#: lxc/config.go:274 +#: lxc/config.go:275 msgid "ISSUE DATE" msgstr "DATE D'ÉMISSION" @@ -556,23 +557,23 @@ msgid "Ignore aliases when determining what command to run" msgstr "Ignorer les alias pour déterminer la commande à exécuter" -#: lxc/action.go:46 +#: lxc/action.go:47 msgid "Ignore the container state (only for start)" msgstr "Ignorer l'état du conteneur (seulement pour start)" -#: lxc/image.go:281 +#: lxc/image.go:282 msgid "Image copied successfully!" msgstr "Image copiée avec succès !" -#: lxc/image.go:421 lxc/image.go:433 +#: lxc/image.go:427 lxc/image.go:439 #, c-format msgid "Image imported with fingerprint: %s" msgstr "Image importée avec l'empreinte : %s" -#: lxc/image.go:418 +#: lxc/image.go:424 #, c-format msgid "Importing the image: %s" -msgstr "Importation de l'image : %s" +msgstr "Import de l'image : %s" #: lxc/init.go:74 msgid "" @@ -587,27 +588,27 @@ "Specifying \"-p\" with no argument will result in no profile.\n" "\n" "Example:\n" -" lxc init ubuntu u1" +" lxc init ubuntu:16.04 u1" msgstr "" "Initialiser un conteneur avec une image particulière.\n" "\n" "lxc init [:] [:][] [--ephemeral|-e] [--profile|-" "p ...] [--config|-c ...] [--network|-n ]\n" "\n" -"Initialise un conteneur en utilisant l'image et le nom spécifiés.\n" +"Initialise un conteneur en utilisant l'image et le nom indiqués.\n" "\n" -"Ne pas spécifier -p résultera en l'utilisation du profil par défaut.\n" -"Spécifier \\-p\\ sans argument résultera en aucun profil.\n" +"Ne pas utiliser -p résultera en l'utilisation du profil par défaut.\n" +"Utiliser \"-p\" sans argument résultera en aucun profil.\n" "\n" "Exemple :\n" -" lxc init ubuntu u1" +" lxc init ubuntu:16.04 u1" #: lxc/remote.go:135 #, c-format msgid "Invalid URL scheme \"%s\" in \"%s\"" msgstr "Schème d'URL invalide \"%s\" in \"%s\"" -#: lxc/config.go:253 +#: lxc/config.go:254 msgid "Invalid certificate" msgstr "Certificat invalide" @@ -615,7 +616,7 @@ msgid "Invalid configuration key" msgstr "Clé de configuration invalide" -#: lxc/file.go:249 +#: lxc/file.go:285 #, c-format msgid "Invalid source %s" msgstr "Source invalide %s" @@ -627,13 +628,13 @@ #: lxc/info.go:124 msgid "Ips:" -msgstr "Ips :" +msgstr "IPs :" -#: lxc/image.go:166 +#: lxc/image.go:167 msgid "Keep the image up to date after initial copy" msgstr "Garder l'image à jour après la copie initiale" -#: lxc/list.go:427 +#: lxc/list.go:429 msgid "LAST USED AT" msgstr "DERNIÈRE UTILISATION À" @@ -641,7 +642,16 @@ msgid "LXD socket not found; is LXD installed and running?" msgstr "Socket LXD introuvable ; LXD est-il installé et actif ?" -#: lxc/launch.go:22 +#: lxc/image.go:360 +#, c-format +msgid "Last used: %s" +msgstr "Dernière utilisation : %s" + +#: lxc/image.go:362 +msgid "Last used: never" +msgstr "Dernière utilisation : jamais" + +#: lxc/launch.go:23 msgid "" "Launch a container from a particular image.\n" "\n" @@ -663,10 +673,10 @@ "profile|-p ...] [--config|-c ...] [--network|-n " "]\n" "\n" -"Lance un conteneur en utilisant l'image et le nom spécifiés.\n" +"Lance un conteneur en utilisant l'image et le nom indiqués.\n" "\n" -"Ne pas spécifier -p résultera en l'utilisation du profil par défaut.\n" -"Spécifier \\\\-p\\\\ sans argument résultera en aucun profil.\n" +"Ne pas utiliser -p résultera en l'utilisation du profil par défaut.\n" +"Utiliser \"-p\" sans argument résultera en aucun profil.\n" "\n" "Exemple :\n" " lxc launch ubuntu:16.04 u1" @@ -689,7 +699,7 @@ "Pour un serveur :\n" " lxc info [:]" -#: lxc/list.go:68 +#: lxc/list.go:69 msgid "" "Lists the containers.\n" "\n" @@ -747,25 +757,25 @@ " lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0." "hwaddr:MAC" msgstr "" -"Lister les ressources disponibles.\n" +"Lister les conteneurs.\n" "\n" "lxc list [:] [filters] [--format table|json] [-c ] [--" "fast]\n" "\n" "Les filtres sont :\n" -"* Un mot-clé unique comme \\web\\ qui listera tous les conteneurs dont\n" -"le nom commence par \\web\\.\n" +"* Un mot-clé unique comme \\web\\ qui listera tous les conteneurs dont le " +"nom commence par \\web\\.\n" "* Une expression rationnelle sur le nom du conteneur (ex : *web.*01$)\n" -"* Une paire de clé/valeur faisant référence à un élément de\n" -"configuration. Pour ceux-ci, l'espace de nom peut être abrégé au plus\n" -"petit identifiant non ambigu :\n" -" * \\user.blah=abc\\ listera tous les conteneurs dont la propriété\n" -" utilisateur \\blah\\ est positionnée à \\abc\\.\n" +"* Une paire de clé/valeur faisant référence à un élément de configuration. " +"Pour ceux-ci, l'espace de nom peut être abrégé au plus petit identifiant non " +"ambigu :\n" +" * \\user.blah=abc\\ listera tous les conteneurs dont la propriété " +"utilisateur \\blah\\ est positionnée à \\abc\\.\n" " * \\u.blah=abc\\ fera la même chose\n" " * \\security.privileged=1\\ listera tous les conteneurs privilégiés\n" " * \\s.privileged=1\\ fera la même chose\n" -"* Une expression rationelle correspondant à un élément de\n" -"configuration ou à sa valeur (ex : volatile.eth0.hwaddr=00:16:3e:.*)\n" +"* Une expression rationelle correspondant à un élément de configuration ou à " +"sa valeur (ex : volatile.eth0.hwaddr=00:16:3e:.*)\n" "\n" "L'option -c prend une liste d'arguments séparés par des virgules\n" "contrôlant les attributs des conteneurs à afficher au format tableau.\n" @@ -811,19 +821,19 @@ msgid "Log:" msgstr "Journal : " -#: lxc/network.go:423 +#: lxc/network.go:428 msgid "MANAGED" msgstr "GÉRÉ" -#: lxc/image.go:164 +#: lxc/image.go:165 msgid "Make image public" msgstr "Rendre l'image publique" -#: lxc/publish.go:33 +#: lxc/publish.go:34 msgid "Make the image public" msgstr "Rendre l'image publique" -#: lxc/profile.go:48 +#: lxc/profile.go:49 msgid "" "Manage configuration profiles.\n" "\n" @@ -910,7 +920,7 @@ "\n" "lxc profile assign [:] \n" " Assigner une liste de profils séparés par des virgules à un\n" -" conteneur, dans l'ordre spécifié. Tous les profils fournis dans\n" +" conteneur, dans l'ordre indiqué. Tous les profils fournis dans\n" " cet appel (et seulement ceux-ci) seront appliqués au conteneur\n" " indiqué, i.e. cela positionne exactement la liste de profils à\n" " ceux spécifiés dans la commande. Pour ajouter ou supprimer un\n" @@ -940,9 +950,9 @@ "Désaffecter la propriété d'un périphérique.\n" "lxc profile device add [:] [key=value]...\n" " Ajouter un périphérique de profil, comme un disque ou une\n" -" interface réseau, aux conteneurs utilisant le profil spécifié." +" interface réseau, aux conteneurs utilisant le profil indiqué." -#: lxc/config.go:57 +#: lxc/config.go:58 msgid "" "Manage configuration.\n" "\n" @@ -1084,7 +1094,7 @@ "To pull /etc/hosts from the container:\n" " lxc file pull foo/etc/hosts ." msgstr "" -"Gérer les fichiers dans un conteneur.\n" +"Gérer les fichiers d'un conteneur.\n" "\n" "lxc file pull [-r|--recursive] [] " "[[:]...] \n" @@ -1103,7 +1113,7 @@ "Pour récupérer /etc/hosts du conteneur :\n" " lxc file pull foo/etc/hosts ." -#: lxc/network.go:48 +#: lxc/network.go:49 msgid "" "Manage networks.\n" "\n" @@ -1166,15 +1176,15 @@ "lxc remote add [] [--accept-certificate] [--" "password=PASSWORD]\n" " [--public] [--protocol=PROTOCOL] " -"Add the remote at .\n" +"Add the remote at .\n" "lxc remote remove " -"Remove the remote .\n" +"Remove the remote .\n" "lxc remote list " "List all remotes.\n" "lxc remote rename " -"Rename remote to .\n" +"Rename remote to .\n" "lxc remote set-url " -"Update 's url to .\n" +"Update 's url to .\n" "lxc remote set-default " "Set the default remote.\n" "lxc remote get-default " @@ -1199,7 +1209,7 @@ "lxc remote get-default " "Afficher le serveur distant par défaut." -#: lxc/image.go:95 +#: lxc/image.go:96 msgid "" "Manipulate container images.\n" "\n" @@ -1378,7 +1388,7 @@ "\n" "lxc monitor [:] [--type=TYPE...]\n" "\n" -"Se connecte à l'interface de supervision du serveur LXD spécifié.\n" +"Se connecte à l'interface de supervision du serveur LXD indiqué.\n" "\n" "Surveillera tous types de message ar défaut.\n" "Les types à surveiller peuvent être spécifiés avec --type.\n" @@ -1386,11 +1396,11 @@ "Exemple :\n" "lxc monitor --type=logging" -#: lxc/network.go:215 lxc/network.go:264 +#: lxc/network.go:216 lxc/network.go:265 msgid "More than one device matches, specify the device name." -msgstr "Plus d'un périphérique correspond, spécifiez le nom du périphérique." +msgstr "Plus d'un périphérique correspond, spécifier le nom du périphérique." -#: lxc/file.go:237 +#: lxc/file.go:273 msgid "More than one file to download, but target is not a directory" msgstr "" "Plusieurs fichiers à télécharger, mais la destination n'est pas un dossier" @@ -1409,7 +1419,7 @@ "lxc move / /\n" " Rename a snapshot." msgstr "" -"Déplacer les conteneurs vers ou entre des instances lxd.\n" +"Déplacer les conteneurs dans ou entre des instances lxd.\n" "\n" "lxc move [:] [:][]\n" " Déplace un conteneur entre deux hôtes, en le renommant si le nom\n" @@ -1421,15 +1431,15 @@ "lxc move / /\n" " Renomme un instantané." -#: lxc/action.go:69 +#: lxc/action.go:70 msgid "Must supply container name for: " -msgstr "Vous devez spécifier le nom d'un conteneur pour : " +msgstr "Vous devez fournir le nom d'un conteneur pour : " -#: lxc/list.go:428 lxc/network.go:421 lxc/profile.go:446 lxc/remote.go:380 +#: lxc/list.go:430 lxc/network.go:426 lxc/profile.go:451 lxc/remote.go:380 msgid "NAME" msgstr "NOM" -#: lxc/network.go:407 lxc/remote.go:354 lxc/remote.go:359 +#: lxc/network.go:412 lxc/remote.go:354 lxc/remote.go:359 msgid "NO" msgstr "NON" @@ -1438,66 +1448,70 @@ msgid "Name: %s" msgstr "Nom : %s" -#: lxc/network.go:189 +#: lxc/network.go:190 #, c-format msgid "Network %s created" msgstr "Le réseau %s a été créé" -#: lxc/network.go:292 +#: lxc/network.go:293 #, c-format msgid "Network %s deleted" -msgstr "Le réseau % a été supprimé" +msgstr "Le réseau %s a été supprimé" -#: lxc/init.go:141 lxc/init.go:142 lxc/launch.go:46 lxc/launch.go:47 +#: lxc/init.go:142 lxc/init.go:143 msgid "Network name" msgstr "Nom du réseau" -#: lxc/image.go:167 lxc/publish.go:34 +#: lxc/image.go:168 lxc/publish.go:35 msgid "New alias to define at target" msgstr "Nouvel alias à définir sur la cible" -#: lxc/config.go:284 +#: lxc/config.go:285 msgid "No certificate provided to add" msgstr "Un certificat à ajouter n'a pas été fourni" -#: lxc/network.go:224 lxc/network.go:273 +#: lxc/network.go:225 lxc/network.go:274 msgid "No device found for this network" msgstr "Aucun périphérique existant pour ce réseau" -#: lxc/config.go:307 +#: lxc/config.go:308 msgid "No fingerprint specified." -msgstr "Aucune empreinte n'a été spécifiée." +msgstr "Aucune empreinte n'a été indiquée." #: lxc/remote.go:120 msgid "Only https URLs are supported for simplestreams" msgstr "Seules les URLs https sont supportées par simplestreams" -#: lxc/image.go:424 +#: lxc/image.go:430 msgid "Only https:// is supported for remote image import." msgstr "Seul https:// est supporté par l'import d'images distantes." +#: lxc/network.go:322 lxc/network.go:449 +msgid "Only managed networks can be modified." +msgstr "Seuls les réseaux gérés par LXD peuvent être modifiés." + #: lxc/help.go:63 lxc/main.go:112 msgid "Options:" msgstr "Options :" -#: lxc/image.go:535 +#: lxc/image.go:541 #, c-format msgid "Output is in %s" msgstr "Le résultat est dans %s" -#: lxc/exec.go:55 +#: lxc/exec.go:56 msgid "Override the terminal mode (auto, interactive or non-interactive)" msgstr "Surcharger le mode terminal (auto, interactif ou non-interactif)" -#: lxc/list.go:572 +#: lxc/list.go:576 msgid "PERSISTENT" msgstr "PERSISTANT" -#: lxc/list.go:429 +#: lxc/list.go:431 msgid "PID" -msgstr "" +msgstr "PID" -#: lxc/list.go:430 +#: lxc/list.go:432 msgid "PROFILES" msgstr "PROFILS" @@ -1505,9 +1519,9 @@ msgid "PROTOCOL" msgstr "PROTOCOLE" -#: lxc/image.go:637 lxc/remote.go:383 +#: lxc/image.go:643 lxc/remote.go:383 msgid "PUBLIC" -msgstr "" +msgstr "PUBLIC" #: lxc/info.go:188 msgid "Packets received" @@ -1534,11 +1548,11 @@ msgid "Pid: %d" msgstr "Pid : %d" -#: lxc/network.go:342 lxc/profile.go:218 +#: lxc/network.go:347 lxc/profile.go:219 msgid "Press enter to open the editor again" msgstr "Appuyer sur Entrée pour ouvrir à nouveau l'éditeur" -#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:732 +#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:738 msgid "Press enter to start the editor again" msgstr "Appuyer sur Entrée pour lancer à nouveau l'éditeur" @@ -1556,7 +1570,7 @@ #: lxc/help.go:66 msgid "Print verbose information" -msgstr "Afficher ds informations supplémentaires" +msgstr "Afficher des informations supplémentaires" #: lxc/version.go:18 msgid "" @@ -1573,32 +1587,31 @@ msgid "Processes: %d" msgstr "Processus : %d" -#: lxc/profile.go:274 +#: lxc/profile.go:275 #, c-format msgid "Profile %s added to %s" msgstr "Profil %s ajouté à %s" -#: lxc/profile.go:169 +#: lxc/profile.go:170 #, c-format msgid "Profile %s created" msgstr "Profil %s créé" -#: lxc/profile.go:239 +#: lxc/profile.go:240 #, c-format msgid "Profile %s deleted" msgstr "Profil %s supprimé" -#: lxc/profile.go:305 +#: lxc/profile.go:306 #, c-format msgid "Profile %s removed from %s" msgstr "Profil %s supprimé de %s" -#: lxc/copy.go:33 lxc/copy.go:34 lxc/init.go:137 lxc/init.go:138 -#: lxc/launch.go:42 lxc/launch.go:43 +#: lxc/copy.go:34 lxc/copy.go:35 lxc/init.go:138 lxc/init.go:139 msgid "Profile to apply to the new container" msgstr "Profil à appliquer au nouveau conteneur" -#: lxc/profile.go:255 +#: lxc/profile.go:256 #, c-format msgid "Profiles %s applied to %s" msgstr "Profils %s appliqués à %s" @@ -1608,7 +1621,7 @@ msgid "Profiles: %s" msgstr "Profils : %s" -#: lxc/image.go:358 +#: lxc/image.go:364 msgid "Properties:" msgstr "Propriétés :" @@ -1616,19 +1629,19 @@ msgid "Public image server" msgstr "Serveur d'images public" -#: lxc/image.go:346 +#: lxc/image.go:347 #, c-format msgid "Public: %s" -msgstr "Public :" +msgstr "Public : %s" -#: lxc/publish.go:26 +#: lxc/publish.go:27 msgid "" "Publish containers as images.\n" "\n" "lxc publish [:][/] [:] [--" "alias=ALIAS...] [prop-key=prop-value...]" msgstr "" -"Publier des conteneurs en tant qu'image.\n" +"Publier des conteneurs en tant qu'images.\n" "\n" "lxc publish [:][/] [:] [--" "alias=ALIAS...] [prop-key=prop-value...]" @@ -1646,12 +1659,12 @@ msgid "Remote: %s" msgstr "Serveur distant : %s" -#: lxc/delete.go:42 +#: lxc/delete.go:43 #, c-format msgid "Remove %s (yes/no): " -msgstr "Supprimer %s (yes/no) : " +msgstr "Supprimer %s (oui/non) : " -#: lxc/delete.go:36 lxc/delete.go:37 +#: lxc/delete.go:37 lxc/delete.go:38 msgid "Require user confirmation" msgstr "Requérir une confirmation de l'utilisateur" @@ -1675,7 +1688,7 @@ "Restore the snapshot:\n" " lxc restore u1 snap0" msgstr "" -"Restaurer l'état d'une ressource depuis un instantané.\n" +"Restaurer l'état d'un conteneur depuis un instantané.\n" "\n" "lxc restore [:] [--stateful]\n" "\n" @@ -1689,20 +1702,20 @@ "Restaurer un instantané :\n" " lxc restore u1 snap0" -#: lxc/init.go:239 +#: lxc/init.go:240 #, c-format msgid "Retrieving image: %s" -msgstr "Récupérer l'image : %s" +msgstr "Récupération de l'image : %s" -#: lxc/image.go:640 +#: lxc/image.go:646 msgid "SIZE" msgstr "TAILLE" -#: lxc/list.go:431 +#: lxc/list.go:433 msgid "SNAPSHOTS" msgstr "INSTANTANÉS" -#: lxc/list.go:432 +#: lxc/list.go:434 msgid "STATE" msgstr "ÉTAT" @@ -1712,7 +1725,7 @@ #: lxc/remote.go:225 msgid "Server certificate NACKed by user" -msgstr "Le certificat serveur a été rejeté par l'utilisateur" +msgstr "Certificat serveur rejeté par l'utilisateur" #: lxc/remote.go:287 msgid "Server doesn't trust us after adding our cert" @@ -1747,11 +1760,11 @@ msgid "Show the container's last 100 log lines?" msgstr "Afficher les 100 dernières lignes du journal du conteneur ?" -#: lxc/config.go:32 +#: lxc/config.go:33 msgid "Show the expanded configuration" -msgstr "Afficher ou pas la configuration étendue" +msgstr "Afficher la configuration étendue" -#: lxc/image.go:344 +#: lxc/image.go:345 #, c-format msgid "Size: %.2fMB" msgstr "Taille : %.2f Mo" @@ -1760,11 +1773,11 @@ msgid "Snapshots:" msgstr "Instantanés :" -#: lxc/image.go:368 +#: lxc/image.go:374 msgid "Source:" msgstr "Source :" -#: lxc/launch.go:144 +#: lxc/launch.go:136 #, c-format msgid "Starting %s" msgstr "Démarrage de %s" @@ -1774,15 +1787,15 @@ msgid "Status: %s" msgstr "État : %s" -#: lxc/publish.go:35 lxc/publish.go:36 +#: lxc/publish.go:36 lxc/publish.go:37 msgid "Stop the container if currently running" msgstr "Arrêter le conteneur s'il est en cours d'exécution" -#: lxc/delete.go:106 lxc/publish.go:113 +#: lxc/delete.go:107 lxc/publish.go:114 msgid "Stopping container failed!" msgstr "L'arrêt du conteneur a échoué !" -#: lxc/action.go:45 +#: lxc/action.go:46 msgid "Store the container state (only for stop)" msgstr "Forcer l'arrêt du conteneur (seulement pour stop)" @@ -1794,84 +1807,84 @@ msgid "Swap (peak)" msgstr "Swap (pointe)" -#: lxc/list.go:433 lxc/network.go:422 +#: lxc/list.go:435 lxc/network.go:427 msgid "TYPE" -msgstr "" +msgstr "TYPE" -#: lxc/delete.go:92 +#: lxc/delete.go:93 msgid "The container is currently running, stop it first or pass --force." msgstr "" -"Le conteneur est en cours d'exécution, arrêtez-le d'abord ou ajoutez --force." +"Le conteneur est en cours d'exécution, l'arrêter d'abord ou ajouter --force." -#: lxc/publish.go:91 +#: lxc/publish.go:92 msgid "" "The container is currently running. Use --force to have it stopped and " "restarted." msgstr "" -"Le conteneur est en cours d'exécution. Utilisez --force pour qu'il soit " +"Le conteneur est en cours d'exécution. Utiliser --force pour qu'il soit " "arrêté et redémarré." -#: lxc/init.go:312 +#: lxc/init.go:313 msgid "The container you are starting doesn't have any network attached to it." msgstr "" "Le conteneur que vous démarrez n'est attaché à aucune interface réseau." -#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738 -#: lxc/config.go:776 lxc/config.go:794 +#: lxc/config.go:680 lxc/config.go:692 lxc/config.go:725 lxc/config.go:743 +#: lxc/config.go:781 lxc/config.go:799 msgid "The device doesn't exist" msgstr "Le périphérique n'existe pas" -#: lxc/init.go:296 +#: lxc/init.go:297 #, c-format msgid "The local image '%s' couldn't be found, trying '%s:' instead." -msgstr "L'image locale '%s' n'a pas été trouvée, essayez '%s:' à la place." +msgstr "L'image locale '%s' n'a pas été trouvée, essayer '%s:' à la place." #: lxc/main.go:181 msgid "The opposite of `lxc pause` is `lxc start`." msgstr "Le pendant de `lxc pause` est `lxc start`." -#: lxc/network.go:229 lxc/network.go:278 +#: lxc/network.go:230 lxc/network.go:279 msgid "The specified device doesn't exist" msgstr "Le périphérique indiqué n'existe pas" -#: lxc/network.go:233 lxc/network.go:282 +#: lxc/network.go:234 lxc/network.go:283 msgid "The specified device doesn't match the network" msgstr "le périphérique indiqué ne correspond pas au réseau" -#: lxc/publish.go:64 +#: lxc/publish.go:65 msgid "There is no \"image name\". Did you want an alias?" msgstr "Il n'existe pas d'\"image\". Vouliez-vous un alias ?" -#: lxc/action.go:41 +#: lxc/action.go:42 msgid "Time to wait for the container before killing it" msgstr "Temps d'attente du conteneur avant de le tuer" -#: lxc/image.go:347 +#: lxc/image.go:348 msgid "Timestamps:" msgstr "Horodatage :" -#: lxc/init.go:314 +#: lxc/init.go:315 msgid "To attach a network to a container, use: lxc network attach" -msgstr "Pour attacher un réseau à un conteneur, utilisez : lxc network attach" +msgstr "Pour attacher un réseau à un conteneur, utiliser : lxc network attach" -#: lxc/init.go:313 +#: lxc/init.go:314 msgid "To create a new network, use: lxc network create" -msgstr "Pour créer un réseau, utilisez : lxc network create" +msgstr "Pour créer un réseau, utiliser : lxc network create" #: lxc/main.go:137 msgid "To start your first container, try: lxc launch ubuntu:16.04" msgstr "" -"Pour démarrer votre premier conteneur, essayez : lxc launch ubuntu:16.04" +"Pour démarrer votre premier conteneur, essayer : lxc launch ubuntu:16.04" -#: lxc/image.go:426 +#: lxc/image.go:432 #, c-format msgid "Transferring image: %s" msgstr "Transfert de l'image : %s" -#: lxc/action.go:99 lxc/launch.go:157 +#: lxc/action.go:100 lxc/launch.go:149 #, c-format msgid "Try `lxc info --show-log %s` for more info" -msgstr "Essayez `lxc info --show-log %s` pour plus d'informations" +msgstr "Essayer `lxc info --show-log %s` pour plus d'informations" #: lxc/info.go:100 msgid "Type: ephemeral" @@ -1881,15 +1894,15 @@ msgid "Type: persistent" msgstr "Type : persistant" -#: lxc/image.go:641 +#: lxc/image.go:647 msgid "UPLOAD DATE" msgstr "DATE DE PUBLICATION" #: lxc/remote.go:381 msgid "URL" -msgstr "" +msgstr "URL" -#: lxc/network.go:424 lxc/profile.go:447 +#: lxc/network.go:429 lxc/profile.go:452 msgid "USED BY" msgstr "UTILISÉ PAR" @@ -1897,7 +1910,7 @@ msgid "Unable to read remote TLS certificate" msgstr "Impossible de lire le certificat TLS distant" -#: lxc/image.go:352 +#: lxc/image.go:353 #, c-format msgid "Uploaded: %s" msgstr "Publié : %s" @@ -1911,7 +1924,7 @@ msgid "Usage: lxc [options]" msgstr "Utilisation : lxc [options]" -#: lxc/delete.go:46 +#: lxc/delete.go:47 msgid "User aborted delete operation." msgstr "L'utilisateur a annulé l'opération de suppression." @@ -1927,7 +1940,7 @@ msgid "Whether or not to snapshot the container's running state" msgstr "Réaliser ou pas l'instantané de l'état de fonctionnement du conteneur" -#: lxc/network.go:409 lxc/remote.go:356 lxc/remote.go:361 +#: lxc/network.go:414 lxc/remote.go:356 lxc/remote.go:361 msgid "YES" msgstr "OUI" @@ -1937,19 +1950,19 @@ "La commande `lxc config profile` est dépréciée, merci d'utiliser `lxc " "profile`" -#: lxc/launch.go:128 +#: lxc/launch.go:120 msgid "bad number of things scanned from image, container or snapshot" -msgstr "nombre d'éléments invalide dans l'image, conteneur ou instantané" +msgstr "mauvais nombre d'éléments dans l'image, conteneur ou instantané" -#: lxc/action.go:95 +#: lxc/action.go:96 msgid "bad result type from action" -msgstr "type de réponse renvoyé par l'action invalide !" +msgstr "mauvais type de réponse renvoyé par l'action !" -#: lxc/copy.go:115 +#: lxc/copy.go:116 msgid "can't copy to the same container name" msgstr "impossible de copier vers le même nom de conteneur" -#: lxc/file.go:272 +#: lxc/file.go:308 msgid "can't pull a directory without --recursive" msgstr "impossible de récupérer un répertoire sans --recursive" @@ -1965,16 +1978,16 @@ msgid "default" msgstr "par défaut" -#: lxc/copy.go:131 lxc/copy.go:136 lxc/copy.go:232 lxc/copy.go:237 -#: lxc/init.go:219 lxc/init.go:224 lxc/launch.go:112 lxc/launch.go:117 +#: lxc/copy.go:132 lxc/copy.go:137 lxc/copy.go:233 lxc/copy.go:238 +#: lxc/init.go:220 lxc/init.go:225 lxc/launch.go:104 lxc/launch.go:109 msgid "didn't get any affected image, container or snapshot from server" -msgstr "pas de réponse du serveur pour cette image, conteneur ou instantané" +msgstr "pas d'image, conteneur ou instantané affecté sur ce serveur" -#: lxc/image.go:338 +#: lxc/image.go:339 msgid "disabled" msgstr "désactivé" -#: lxc/image.go:340 +#: lxc/image.go:341 msgid "enabled" msgstr "activé" @@ -1988,15 +2001,15 @@ msgid "error: unknown command: %s" msgstr "erreur : commande inconnue: %s" -#: lxc/launch.go:132 +#: lxc/launch.go:124 msgid "got bad version" -msgstr "version reçue invalide" +msgstr "reçu une mauvaise version" -#: lxc/image.go:333 lxc/image.go:617 +#: lxc/image.go:334 lxc/image.go:623 msgid "no" msgstr "non" -#: lxc/copy.go:164 +#: lxc/copy.go:165 msgid "not all the profiles from the source exist on the target" msgstr "tous les profils de la source n'existent pas sur la cible" @@ -2009,7 +2022,7 @@ msgid "processing aliases failed %s\n" msgstr "l'analyse des alias a échoué %s\n" -#: lxc/file.go:313 +#: lxc/file.go:349 msgid "recursive edit doesn't make sense :(" msgstr "l'édition récursive ne fait aucun sens :(" @@ -2046,7 +2059,7 @@ msgid "taken at %s" msgstr "pris à %s" -#: lxc/exec.go:185 +#: lxc/exec.go:186 msgid "unreachable return reached" msgstr "Un retour impossible à été renvoyé" @@ -2054,10 +2067,10 @@ msgid "wrong number of subcommand arguments" msgstr "nombre d'arguments incorrect pour la sous-comande" -#: lxc/delete.go:45 lxc/image.go:335 lxc/image.go:621 +#: lxc/delete.go:46 lxc/image.go:336 lxc/image.go:627 msgid "yes" msgstr "oui" -#: lxc/copy.go:44 +#: lxc/copy.go:45 msgid "you must specify a source container name" msgstr "vous devez spécifier un nom de conteneur source" diff -Nru lxd-2.7/po/ja.po lxd-2.8/po/ja.po --- lxd-2.7/po/ja.po 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/po/ja.po 2017-01-25 02:50:49.000000000 +0000 @@ -7,14 +7,17 @@ msgstr "" "Project-Id-Version: LXD\n" "Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n" -"POT-Creation-Date: 2016-12-15 13:20-0500\n" -"PO-Revision-Date: 2016-11-09 19:45+0900\n" +"POT-Creation-Date: 2017-01-24 18:22-0500\n" +"PO-Revision-Date: 2017-01-16 11:29+0000\n" "Last-Translator: KATOH Yasufumi \n" -"Language-Team: Japanese \n" -"Language: Japanese\n" +"Language-Team: Japanese \n" +"Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 2.11-dev\n" #: lxc/info.go:154 msgid " CPU usage:" @@ -32,7 +35,7 @@ msgid " Network usage:" msgstr " ネットワーク使用状況:" -#: lxc/config.go:36 +#: lxc/config.go:37 msgid "" "### This is a yaml representation of the configuration.\n" "### Any line starting with a '# will be ignored.\n" @@ -53,7 +56,7 @@ "### Note that the name is shown but cannot be changed" msgstr "" -#: lxc/image.go:85 +#: lxc/image.go:86 msgid "" "### This is a yaml representation of the image properties.\n" "### Any line starting with a '# will be ignored.\n" @@ -63,7 +66,7 @@ "### description: My custom image" msgstr "" -#: lxc/network.go:28 +#: lxc/network.go:29 msgid "" "### This is a yaml representation of the network.\n" "### Any line starting with a '# will be ignored.\n" @@ -83,7 +86,7 @@ "### Note that only the configuration can be changed." msgstr "" -#: lxc/profile.go:27 +#: lxc/profile.go:28 msgid "" "### This is a yaml representation of the profile.\n" "### Any line starting with a '# will be ignored.\n" @@ -104,28 +107,28 @@ "### Note that the name is shown but cannot be changed" msgstr "" -#: lxc/image.go:614 +#: lxc/image.go:620 #, c-format msgid "%s (%d more)" msgstr "" #: lxc/snapshot.go:61 msgid "'/' not allowed in snapshot name" -msgstr "'/' はスナップショットの名前には使用できません。" +msgstr "'/' はスナップショットの名前には使用できません" -#: lxc/profile.go:253 +#: lxc/profile.go:254 msgid "(none)" msgstr "" -#: lxc/image.go:635 lxc/image.go:677 +#: lxc/image.go:641 lxc/image.go:683 msgid "ALIAS" msgstr "" -#: lxc/image.go:639 +#: lxc/image.go:645 msgid "ARCH" msgstr "" -#: lxc/list.go:425 +#: lxc/list.go:427 msgid "ARCHITECTURE" msgstr "" @@ -138,16 +141,16 @@ msgid "Admin password for %s: " msgstr "%s の管理者パスワード: " -#: lxc/image.go:362 +#: lxc/image.go:368 msgid "Aliases:" msgstr "エイリアス:" -#: lxc/image.go:345 lxc/info.go:93 +#: lxc/image.go:346 lxc/info.go:93 #, c-format msgid "Architecture: %s" msgstr "アーキテクチャ: %s" -#: lxc/image.go:366 +#: lxc/image.go:372 #, c-format msgid "Auto update: %s" msgstr "自動更新: %s" @@ -164,29 +167,29 @@ msgid "Bytes sent" msgstr "送信バイト数" -#: lxc/config.go:273 +#: lxc/config.go:274 msgid "COMMON NAME" msgstr "" #: lxc/info.go:150 msgid "CPU usage (in seconds)" -msgstr "" +msgstr "CPU使用量(秒)" -#: lxc/list.go:426 +#: lxc/list.go:428 msgid "CREATED AT" msgstr "" -#: lxc/config.go:113 +#: lxc/config.go:114 lxc/network.go:463 #, c-format msgid "Can't read from stdin: %s" msgstr "標準入力から読み込めません: %s" -#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181 +#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182 #, c-format msgid "Can't unset key '%s', it's not currently set." msgstr "キー '%s' が指定されていないので削除できません。" -#: lxc/network.go:385 lxc/profile.go:419 +#: lxc/network.go:390 lxc/profile.go:424 msgid "Cannot provide container name to list" msgstr "コンテナ名を取得できません" @@ -195,8 +198,8 @@ msgid "Certificate fingerprint: %x" msgstr "証明書のフィンガープリント: %x" -#: lxc/action.go:33 -#, fuzzy, c-format +#: lxc/action.go:34 +#, c-format msgid "" "Change state of one or more containers to %s.\n" "\n" @@ -204,10 +207,9 @@ msgstr "" "1つまたは複数のコンテナの状態を %s に変更します。\n" "\n" -"lxc %s [...]%s" +"lxc %s [:] [[:]...]%s" #: lxc/finger.go:15 -#, fuzzy msgid "" "Check if the LXD instance is up.\n" "\n" @@ -215,23 +217,22 @@ msgstr "" "LXDインスタンスが稼働中かを確認します。\n" "\n" -"lxc finger " +"lxc finger [:]" #: lxc/remote.go:290 msgid "Client certificate stored at server: " msgstr "クライアント証明書がサーバに格納されました: " -#: lxc/list.go:121 lxc/list.go:122 +#: lxc/list.go:122 lxc/list.go:123 msgid "Columns" msgstr "カラムレイアウト" -#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:135 lxc/init.go:136 -#: lxc/launch.go:40 lxc/launch.go:41 +#: lxc/copy.go:32 lxc/copy.go:33 lxc/init.go:136 lxc/init.go:137 msgid "Config key/value to apply to the new container" msgstr "新しいコンテナに適用するキー/値の設定" -#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:731 lxc/network.go:341 -#: lxc/profile.go:217 +#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:737 lxc/network.go:346 +#: lxc/profile.go:218 #, c-format msgid "Config parsing error: %s" msgstr "設定の構文エラー: %s" @@ -240,26 +241,25 @@ msgid "Connection refused; is LXD running?" msgstr "接続が拒否されました。LXDが実行されていますか?" -#: lxc/publish.go:61 +#: lxc/publish.go:62 msgid "Container name is mandatory" msgstr "コンテナ名を指定する必要があります" -#: lxc/copy.go:140 lxc/copy.go:241 lxc/init.go:229 +#: lxc/copy.go:141 lxc/copy.go:242 lxc/init.go:230 #, c-format msgid "Container name is: %s" msgstr "コンテナ名: %s" -#: lxc/publish.go:151 lxc/publish.go:166 +#: lxc/publish.go:152 lxc/publish.go:167 #, c-format msgid "Container published with fingerprint: %s" msgstr "コンテナは以下のフィンガープリントで publish されます: %s" -#: lxc/image.go:165 +#: lxc/image.go:166 msgid "Copy aliases from source" msgstr "ソースからエイリアスをコピーしました" -#: lxc/copy.go:24 -#, fuzzy +#: lxc/copy.go:25 msgid "" "Copy containers within or in between LXD instances.\n" "\n" @@ -268,25 +268,24 @@ msgstr "" "LXDインスタンス内もしくはLXDインスタンス間でコンテナをコピーします。\n" "\n" -"lxc copy [remote:] [remote:]\n" +"lxc copy [:][/] [[:]]\n" "[--ephemeral|e] [--profile|-p ...] [--config|-c ...]" -#: lxc/image.go:278 +#: lxc/image.go:279 #, c-format msgid "Copying the image: %s" msgstr "イメージのコピー中: %s" #: lxc/remote.go:232 msgid "Could not create server cert dir" -msgstr "サーバ証明書格納用のディレクトリを作成できません。" +msgstr "サーバ証明書格納用のディレクトリを作成できません" #: lxc/file.go:83 #, c-format msgid "Could not sanitize path %s" -msgstr "" +msgstr "パス %s をサニタイズできません" #: lxc/snapshot.go:21 -#, fuzzy msgid "" "Create a read-only snapshot of a container.\n" "\n" @@ -307,7 +306,7 @@ msgstr "" "コンテナの読み取り専用のスナップショットを作成します。\n" "\n" -"lxc snapshot [remote:] [--stateful]\n" +"lxc snapshot [:] [--stateful]\n" "\n" "コンテナのスナップショットを作成します (オプションでコンテナのメモリ状態を\n" "含めて)。--statefulが指定された場合、LXDはコンテナプロセスのメモリ状態、TCP\n" @@ -322,30 +321,29 @@ msgid "Create any directories necessary" msgstr "必要なディレクトリをすべて作成します" -#: lxc/image.go:350 lxc/info.go:95 +#: lxc/image.go:351 lxc/info.go:95 #, c-format msgid "Created: %s" msgstr "作成日時: %s" -#: lxc/init.go:180 lxc/launch.go:135 +#: lxc/init.go:181 lxc/launch.go:127 #, c-format msgid "Creating %s" msgstr "%s を作成中" -#: lxc/init.go:178 +#: lxc/init.go:179 msgid "Creating the container" msgstr "コンテナを作成中" -#: lxc/image.go:638 lxc/image.go:679 +#: lxc/image.go:644 lxc/image.go:685 msgid "DESCRIPTION" msgstr "" -#: lxc/publish.go:37 +#: lxc/publish.go:38 msgid "Define a compression algorithm: for image or none" msgstr "圧縮アルゴリズムを指定します: 圧縮アルゴリズム名 or none" -#: lxc/delete.go:25 -#, fuzzy +#: lxc/delete.go:26 msgid "" "Delete containers or snapshots.\n" "\n" @@ -357,51 +355,47 @@ msgstr "" "コンテナもしくはコンテナのスナップショットを消去します。\n" "\n" -"lxc delete [remote:][/] [remote:]" -"[[]...]\n" +"lxc delete [:][/] [[:][/" +"]...]\n" "\n" "付属するデータ (設定、スナップショット、...) と一緒にコンテナもしくはコンテ\n" "ナのスナップショットを消去します。" -#: lxc/config.go:647 +#: lxc/config.go:652 #, c-format msgid "Device %s added to %s" msgstr "デバイス %s が %s に追加されました" -#: lxc/config.go:834 +#: lxc/config.go:839 #, c-format msgid "Device %s removed from %s" msgstr "デバイス %s が %s から削除されました" -#: lxc/list.go:570 +#: lxc/list.go:574 msgid "EPHEMERAL" msgstr "" -#: lxc/config.go:275 +#: lxc/config.go:276 msgid "EXPIRY DATE" msgstr "" #: lxc/main.go:41 -#, fuzzy msgid "Enable debug mode" -msgstr "デバッグモードを有効にします。" +msgstr "デバッグモードを有効にします" #: lxc/main.go:40 -#, fuzzy msgid "Enable verbose mode" -msgstr "詳細モードを有効にします。" +msgstr "詳細モードを有効にします" -#: lxc/exec.go:54 -#, fuzzy +#: lxc/exec.go:55 msgid "Environment variable to set (e.g. HOME=/home/foo)" -msgstr "環境変数を HOME=/home/foo の形式で指定します" +msgstr "環境変数を設定します (例: HOME=/home/foo)" #: lxc/help.go:69 msgid "Environment:" msgstr "環境変数:" -#: lxc/copy.go:35 lxc/copy.go:36 lxc/init.go:139 lxc/init.go:140 -#: lxc/launch.go:44 lxc/launch.go:45 +#: lxc/copy.go:36 lxc/copy.go:37 lxc/init.go:140 lxc/init.go:141 msgid "Ephemeral container" msgstr "Ephemeral コンテナ" @@ -409,8 +403,7 @@ msgid "Event type to listen for" msgstr "Listenするイベントタイプ" -#: lxc/exec.go:45 -#, fuzzy +#: lxc/exec.go:46 msgid "" "Execute the specified command in a container.\n" "\n" @@ -422,77 +415,73 @@ msgstr "" "指定したコマンドをコンテナ内で実行します。\n" "\n" -"lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env " -"EDITOR=/usr/bin/vim]... [--] \n" +"lxc exec [:] [--mode=auto|interactive|non-interactive] [--" +"env KEY=VALUE...] [--] \n" "\n" "デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナ\n" "ルの場合は interactive モードが選択されます (標準エラー出力は無視されます)。" -#: lxc/image.go:354 +#: lxc/image.go:355 #, c-format msgid "Expires: %s" msgstr "失効日時: %s" -#: lxc/image.go:356 +#: lxc/image.go:357 msgid "Expires: never" msgstr "失効日時: 失効しない" -#: lxc/config.go:272 lxc/image.go:636 lxc/image.go:678 +#: lxc/config.go:273 lxc/image.go:642 lxc/image.go:684 msgid "FINGERPRINT" msgstr "" -#: lxc/list.go:124 +#: lxc/list.go:125 msgid "Fast mode (same as --columns=nsacPt" msgstr "Fast モード (--columns=nsacPt と同じ)" -#: lxc/image.go:343 +#: lxc/image.go:344 #, c-format msgid "Fingerprint: %s" msgstr "証明書のフィンガープリント: %s" -#: lxc/action.go:42 lxc/action.go:43 -#, fuzzy +#: lxc/action.go:43 lxc/action.go:44 msgid "Force the container to shutdown" -msgstr "コンテナを強制シャットダウンします。" +msgstr "コンテナを強制シャットダウンします" -#: lxc/delete.go:34 lxc/delete.go:35 -#, fuzzy +#: lxc/delete.go:35 lxc/delete.go:36 msgid "Force the removal of stopped containers" -msgstr "停止したコンテナを強制的に削除します。" +msgstr "停止したコンテナを強制的に削除します" #: lxc/main.go:42 -#, fuzzy msgid "Force using the local unix socket" -msgstr "強制的にローカルのUNIXソケットを使います。" +msgstr "強制的にローカルのUNIXソケットを使います" -#: lxc/image.go:168 lxc/list.go:123 +#: lxc/image.go:169 lxc/list.go:124 msgid "Format" -msgstr "" +msgstr "フォーマット" #: lxc/remote.go:65 msgid "Generating a client certificate. This may take a minute..." msgstr "クライアント証明書を生成します。1分ぐらいかかります..." #: lxc/help.go:25 -#, fuzzy msgid "" "Help page for the LXD client.\n" "\n" "lxc help [--all]" msgstr "" -"LXDの使い方の詳細を表示します。\n" +"LXDクライアントのヘルプページを表示します。\n" "\n" "lxd help [--all]" -#: lxc/list.go:423 +#: lxc/list.go:425 msgid "IPV4" -msgstr "" +msgstr "IPV4" -#: lxc/list.go:424 +#: lxc/list.go:426 msgid "IPV6" -msgstr "" +msgstr "IPV6" -#: lxc/config.go:274 +#: lxc/config.go:275 msgid "ISSUE DATE" msgstr "" @@ -502,31 +491,28 @@ msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要があります" #: lxc/main.go:43 -#, fuzzy msgid "Ignore aliases when determining what command to run" -msgstr "どのコマンドを実行するか決める際にエイリアスを無視します。" +msgstr "どのコマンドを実行するか決める際にエイリアスを無視します" -#: lxc/action.go:46 -#, fuzzy +#: lxc/action.go:47 msgid "Ignore the container state (only for start)" -msgstr "コンテナの状態を無視します (startのみ)。" +msgstr "コンテナの状態を無視します (startのみ)" -#: lxc/image.go:281 +#: lxc/image.go:282 msgid "Image copied successfully!" msgstr "イメージのコピーが成功しました!" -#: lxc/image.go:421 lxc/image.go:433 +#: lxc/image.go:427 lxc/image.go:439 #, c-format msgid "Image imported with fingerprint: %s" msgstr "イメージは以下のフィンガープリントでインポートされました: %s" -#: lxc/image.go:418 +#: lxc/image.go:424 #, c-format msgid "Importing the image: %s" msgstr "イメージのインポート中: %s" #: lxc/init.go:74 -#, fuzzy msgid "" "Initialize a container from a particular image.\n" "\n" @@ -539,12 +525,12 @@ "Specifying \"-p\" with no argument will result in no profile.\n" "\n" "Example:\n" -" lxc init ubuntu u1" +" lxc init ubuntu:16.04 u1" msgstr "" "指定したイメージからコンテナを初期化します。\n" "\n" -"lxc init [remote:] [remote:][] [--ephemeral|-e] [--profile|-p " -"...] [--config|-c ...] [--network|-n ]\n" +"lxc init [:] [:][] [--ephemeral|-e] [--profile|-" +"p ...] [--config|-c ...] [--network|-n ]\n" "\n" "指定したイメージとコンテナ名を使ってコンテナを初期化します。\n" "\n" @@ -552,14 +538,14 @@ "\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n" "\n" "例:\n" -"lxc init ubuntu u1" +" lxc init ubuntu:16.04 u1" #: lxc/remote.go:135 #, c-format msgid "Invalid URL scheme \"%s\" in \"%s\"" msgstr "不正な URL スキーム \"%s\" (\"%s\" 内)" -#: lxc/config.go:253 +#: lxc/config.go:254 msgid "Invalid certificate" msgstr "不正な証明書です" @@ -567,7 +553,7 @@ msgid "Invalid configuration key" msgstr "正しくない設定項目 (key) です" -#: lxc/file.go:249 +#: lxc/file.go:285 #, c-format msgid "Invalid source %s" msgstr "不正なソース %s" @@ -581,11 +567,11 @@ msgid "Ips:" msgstr "IPアドレス:" -#: lxc/image.go:166 +#: lxc/image.go:167 msgid "Keep the image up to date after initial copy" msgstr "最初にコピーした後も常にイメージを最新の状態に保つ" -#: lxc/list.go:427 +#: lxc/list.go:429 msgid "LAST USED AT" msgstr "" @@ -593,8 +579,16 @@ msgid "LXD socket not found; is LXD installed and running?" msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?" -#: lxc/launch.go:22 -#, fuzzy +#: lxc/image.go:360 +#, c-format +msgid "Last used: %s" +msgstr "最終使用: %s" + +#: lxc/image.go:362 +msgid "Last used: never" +msgstr "" + +#: lxc/launch.go:23 msgid "" "Launch a container from a particular image.\n" "\n" @@ -612,8 +606,9 @@ msgstr "" "指定したイメージからコンテナを起動します。\n" "\n" -"lxc launch [remote:] [remote:][] [--ephemeral|-e] [--profile|-p " -"...] [--config|-c ...] [--network|-n ]\n" +"lxc launch [:] [:][] [--ephemeral|-e] [--" +"profile|-p ...] [--config|-c ...] [--network|-n " +"]\n" "\n" "指定したイメージと名前を使ってコンテナを起動します。\n" "\n" @@ -621,10 +616,9 @@ "\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n" "\n" "例:\n" -"lxc launch ubuntu:16.04 u1" +" lxc launch ubuntu:16.04 u1" #: lxc/info.go:25 -#, fuzzy msgid "" "List information on LXD servers and containers.\n" "\n" @@ -637,13 +631,12 @@ "LXD サーバとコンテナの情報を一覧表示します。\n" "\n" "コンテナ情報:\n" -" lxc info [:]container [--show-log]\n" +" lxc info [:] [--show-log]\n" "\n" "サーバ情報:\n" -" lxc info [:]" +" lxc info [:]" -#: lxc/list.go:68 -#, fuzzy +#: lxc/list.go:69 msgid "" "Lists the containers.\n" "\n" @@ -701,9 +694,10 @@ " lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0." "hwaddr:MAC" msgstr "" -"利用可能なリソースを一覧表示します。\n" +"コンテナを一覧表示します。\n" "\n" -"lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n" +"lxc list [:] [filters] [--format table|json] [-c ] [--" +"fast]\n" "\n" "フィルタの指定:\n" "* 単一の \"web\" のようなキーワードを指定すると、名前が \"web\" ではじまるコ" @@ -755,27 +749,26 @@ "Fast モードのカラムレイアウト: nsacPt\n" "\n" "例:\n" -"lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:" -"MAC\n" +" lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0." +"hwaddr:MAC" #: lxc/info.go:239 msgid "Log:" msgstr "ログ:" -#: lxc/network.go:423 +#: lxc/network.go:428 msgid "MANAGED" msgstr "" -#: lxc/image.go:164 +#: lxc/image.go:165 msgid "Make image public" msgstr "イメージを public にする" -#: lxc/publish.go:33 +#: lxc/publish.go:34 msgid "Make the image public" msgstr "イメージを public にする" -#: lxc/profile.go:48 -#, fuzzy +#: lxc/profile.go:49 msgid "" "Manage configuration profiles.\n" "\n" @@ -837,29 +830,29 @@ msgstr "" "設定プロファイルを管理します。\n" "\n" -"lxc profile list [filters]\n" +"lxc profile list [:]\n" " 利用可能なプロファイルを一覧します。\n" -"lxc profile show \n" +"lxc profile show [:]\n" " プロファイルの詳細を表示します。\n" -"lxc profile create \n" +"lxc profile create [:]\n" " プロファイルを作成します。\n" -"lxc profile copy \n" -" プロファイルを remote にコピーします。\n" -"lxc profile get \n" +"lxc profile copy [:] [:]\n" +" プロファイルをコピーします。\n" +"lxc profile get [:] \n" " プロファイルの設定を取得します。\n" -"lxc profile set \n" +"lxc profile set [:] \n" " プロファイルの設定を設定します。\n" -"lxc profile unset \n" +"lxc profile unset [:] \n" " プロファイルから設定項目を削除します。\n" -"lxc profile delete \n" +"lxc profile delete [:]\n" " プロファイルを削除します。\n" -"lxc profile edit \n" +"lxc profile edit [:]\n" " プロファイルを編集します。外部エディタもしくはSTDINから読み込みます。\n" " 例: lxc profile edit # エディタの起動\n" " cat profile.yaml | lxc profile edit # profile.yaml から読み" "込み\n" "\n" -"lxc profile assign \n" +"lxc profile assign [:] \n" " プロファイルのコンマ区切りのリストをコンテナに順番に割り当てます。\n" " このコマンドで指定したプロファイルだけが対象のコンテナに適用されます。\n" " つまり、コマンドで指定したコンテナに正確にプロファイルのリストを設定しま" @@ -870,31 +863,29 @@ " lxc profile assign foo default # defaultだけを有効化\n" " lxc profile assign '' # 一切のプロファイルを適用しない\n" " lxc profile assign bar,default # defaultを2番目に適用\n" -"lxc profile add \n" +"lxc profile add [:] \n" " コンテナにプロファイルを追加します。\n" -"lxc profile remove \n" +"lxc profile remove [:] \n" " コンテナからプロファイルを削除します。\n" "\n" "デバイス:\n" -"lxc profile device list \n" +"lxc profile device list [:]\n" " 指定したプロファイル内のデバイスを一覧表示します\n" -"lxc profile device show \n" +"lxc profile device show [:]\n" " 指定したプロファイル内の全デバイスの詳細を表示します\n" -"lxc profile device remove \n" +"lxc profile device remove [:] \n" " プロファイルからデバイスを削除します\n" -"lxc profile device get <[remote:]profile> \n" +"lxc profile device get [:] \n" " デバイスプロパティを取得します\n" -"lxc profile device set <[remote:]profile> \n" +"lxc profile device set [:] \n" " デバイスプロパティを設定します\n" "lxc profile device unset <[remote:]profile> \n" " デバイスプロパティを削除します\n" -"lxc profile device add " -"[key=value]...\n" +"lxc profile device add [:] [key=value...]\n" " ディスクやNICのようなプロファイルデバイスを指定したプロファイルを使って\n" " コンテナに追加します。" -#: lxc/config.go:57 -#, fuzzy +#: lxc/config.go:58 msgid "" "Manage configuration.\n" "\n" @@ -961,51 +952,51 @@ msgstr "" "設定を管理します。\n" "\n" -"lxc config device add <[remote:]container> [key=value]...\n" +"lxc config device add [:] [key=value...]\n" " コンテナにデバイスを追加します。\n" -"lxc config device get <[remote:]container> \n" +"lxc config device get [:] \n" " デバイスのプロパティを取得します。\n" -"lxc config device set <[remote:]container> \n" +"lxc config device set [:] \n" " デバイスのプロパティを設定します。\n" -"lxc config device unset <[remote:]container> \n" +"lxc config device unset [:] \n" " デバイスのプロパティを削除します。\n" -"lxc config device list <[remote:]container>\n" +"lxc config device list [:]\n" " コンテナのデバイスを一覧表示します。\n" -"lxc config device show <[remote:]container>\n" +"lxc config device show [:]\n" " 全てのデバイスの詳細を表示します。\n" -"lxc config device remove <[remote:]container> \n" +"lxc config device remove [:] \n" " コンテナからデバイスを削除します。\n" "\n" -"lxc config get [remote:][container] \n" +"lxc config get [:][container] \n" " コンテナもしくはサーバの設定項目の値を取得します。\n" -"lxc config set [remote:][container] \n" +"lxc config set [:][container] \n" " コンテナもしくはサーバの設定項目に値を設定します。\n" -"lxc config unset [remote:][container] \n" +"lxc config unset [:][container] \n" " コンテナもしくはサーバの設定項目を削除します。\n" -"lxc config show [remote:][container] [--expanded]\n" +"lxc config show [:][container] [--expanded]\n" " コンテナもしくはサーバの設定を表示します。\n" -"lxc config edit [remote:][container]\n" +"lxc config edit [:][container]\n" " コンテナもしくはサーバの設定を外部エディタで編集します。\n" " 設定の編集は外部エディタを起動するか、標準入力からの読み込みで行いま" "す。\n" " 例: lxc config edit # エディタの起動\n" -" cat config.yaml | lxc config edit # config.yamlから読み込" +" cat config.yaml | lxc config edit # config.yamlから読み込" "み\n" "\n" -"lxc config trust list [remote]\n" +"lxc config trust list [:]\n" " 信頼する証明書を全て表示します。\n" -"lxc config trust add [remote] \n" +"lxc config trust add [:] \n" " certfile.crt を信頼するホストに追加します。\n" -"lxc config trust remove [remote] [hostname|fingerprint]\n" +"lxc config trust remove [:] [hostname|fingerprint]\n" " 信頼するホストから証明書を消去します。\n" "\n" "例:\n" "ホストの /share/c1 をコンテナ内の /opt にマウントするには:\n" -" lxc config device add [remote:]container1 disk source=/" +" lxc config device add [:]container1 disk source=/" "share/c1 path=opt\n" "\n" "lxc 設定項目に値を設定するには:\n" -" lxc config set [remote:] raw.lxc 'lxc.aa_allow_incomplete = " +" lxc config set [:] raw.lxc 'lxc.aa_allow_incomplete = " "1'\n" "\n" "IPv4 と IPv6 のポート 8443 で Listen するには\n" @@ -1016,7 +1007,6 @@ " lxc config set core.trust_password blah" #: lxc/file.go:36 -#, fuzzy msgid "" "Manage files in a container.\n" "\n" @@ -1036,26 +1026,26 @@ "To pull /etc/hosts from the container:\n" " lxc file pull foo/etc/hosts ." msgstr "" -"コンテナ上のファイルを管理します。\n" +"コンテナ内のファイルを管理します。\n" "\n" -"lxc file pull [-r|--recursive] [...] \n" +"lxc file pull [-r|--recursive] [:] " +"[[:]...] \n" "lxc file push [-r|--recursive] [-p|--create-dirs] [--uid=UID] [--gid=GID] [--" -"mode=MODE] [...] \n" -"lxc file edit \n" +"mode=MODE] [...] [:]\n" +"lxc file edit [:]/\n" "\n" -"pull の場合の 、push の場合の 、edit の場合の は、い\n" -"ずれも / の形式です。\n" +"pull の場合の 、push の場合の 、edit の場合の は、いず" +"れも / の形式です\n" "\n" "例:\n" "\n" "/etc/hosts をコンテナ foo に push するには:\n" -" lxc file push /etc/hosts foo/etc/hosts\n" +" lxc file push /etc/hosts foo/etc/hosts\n" "\n" "/etc/hosts をコンテナから pull するには:\n" -" lxc file pull foo/etc/hosts\n" +" lxc file pull foo/etc/hosts" -#: lxc/network.go:48 -#, fuzzy +#: lxc/network.go:49 msgid "" "Manage networks.\n" "\n" @@ -1085,46 +1075,48 @@ msgstr "" "ネットワークを管理します。<\n" "\n" -"lxc network list 利用できるネットワークを一覧" -"します\n" -"lxc network show ネットワークの詳細を表示しま" +"lxc network list [:] 利用できるネットワーク" +"を一覧します\n" +"lxc network show [:] ネットワークの詳細を表" +"示します\n" +"lxc network create [:] [key=value...] ネットワークを作成しま" "す\n" -"lxc network create [key=value]... ネットワークを作成します\n" -"lxc network get ネットワークの設定を取得しま" +"lxc network get [:] ネットワークの設定を取" +"得します\n" +"lxc network set [:] ネットワークを設定しま" "す\n" -"lxc network set ネットワークを設定します\n" -"lxc network unset ネットワークの設定を削除しま" +"lxc network unset [:] ネットワークの設定を削" +"除します\n" +"lxc network delete [:] ネットワークを削除しま" "す\n" -"lxc network delete ネットワークを削除します\n" -"lxc network edit \n" +"lxc network edit [:]\n" " ネットワークを編集します。外部エディタもしくはSTDINから読み込みます。\n" " 例: lxc network edit # エディタの起動\n" " cat network.yaml | lxc network edit # network.yaml から読み" "込み\n" "\n" -"lxc network attach [device name]\n" -"lxc network attach-profile [device name]\n" +"lxc network attach [:] [device name]\n" +"lxc network attach-profile [:] [device name]\n" "\n" -"lxc network detach [device name]\n" -"lxc network detach-profile [device name]\n" +"lxc network detach [:] [device name]\n" +"lxc network detach-profile [:] [device name]" #: lxc/remote.go:37 -#, fuzzy msgid "" "Manage remote LXD servers.\n" "\n" "lxc remote add [] [--accept-certificate] [--" "password=PASSWORD]\n" " [--public] [--protocol=PROTOCOL] " -"Add the remote at .\n" +"Add the remote at .\n" "lxc remote remove " -"Remove the remote .\n" +"Remove the remote .\n" "lxc remote list " "List all remotes.\n" "lxc remote rename " -"Rename remote to .\n" +"Rename remote to .\n" "lxc remote set-url " -"Update 's url to .\n" +"Update 's url to .\n" "lxc remote set-default " "Set the default remote.\n" "lxc remote get-default " @@ -1132,25 +1124,24 @@ msgstr "" "リモートの LXD サーバを管理します。\n" "\n" -"lxc remote add [] \n" +"lxc remote add [] \n" " [--accept-certificate] [--password=PASSWORD]\n" " [--public] [--protocol=PROTOCOL]\n" -" をリモートホスト として追加します。\n" -"lxc remote remove \n" -" リモートホスト を削除します。\n" +" をリモートホスト として追加します。\n" +"lxc remote remove \n" +" リモートホスト を削除します。\n" "lxc remote list\n" " 登録済みのリモートホストを全て一覧表示します。\n" -"lxc remote rename \n" -" リモートホストの名前を から に変更します。\n" -"lxc remote set-url \n" -" の url を に更新します。\n" -"lxc remote set-default \n" -" をデフォルトのリモートホストに設定します。\n" +"lxc remote rename \n" +" リモートホストの名前を から に変更します。\n" +"lxc remote set-url \n" +" の url を に更新します。\n" +"lxc remote set-default \n" +" をデフォルトのリモートホストに設定します。\n" "lxc remote get-default\n" " デフォルトに設定されているリモートホストを表示します。" -#: lxc/image.go:95 -#, fuzzy +#: lxc/image.go:96 msgid "" "Manipulate container images.\n" "\n" @@ -1237,24 +1228,24 @@ "る場合は) エイリアスで参照できます。\n" "\n" "\n" -"lxc image import [rootfs tarball|URL] [remote:] [--public] [--" -"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--" -"alias=ALIAS].. [prop=value]\n" +"lxc image import [|] [:] [--public] " +"[--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] " +"[--alias=ALIAS...] [prop=value]\n" " イメージの tarball (複数も可能) を LXD のイメージストアにインポートしま\n" " す。\n" "\n" -"lxc image copy [remote:] : [--alias=ALIAS].. [--copy-aliases] " -"[--public] [--auto-update]\n" +"lxc image copy [:] : [--alias=ALIAS...] [--copy-" +"aliases] [--public] [--auto-update]\n" " ネットワーク経由である LXD デーモンから他の LXD デーモンへイメージを\n" " コピーします。\n" "\n" " auto-update フラグは、サーバがイメージを最新に保つように指示します。イ\n" " メージのソースがエイリアスであり、public である必要があります。\n" "\n" -"lxc image delete [remote:]\n" +"lxc image delete [:] [[:]...]\n" " LXD のイメージストアからイメージを削除します。\n" "\n" -"lxc image export [remote:]\n" +"lxc image export [:] [target]\n" " LXD のイメージストアから配布可能な tarball としてイメージをエクスポート\n" " します。\n" "\n" @@ -1267,32 +1258,32 @@ " も標準出力でもない場合)、イメージを圧縮するアルゴリズムにも土づい\n" " た適切な拡張子が対象のファイル名に追加されます。\n" "\n" -"lxc image info [remote:]\n" +"lxc image info [:]\n" " 指定したイメージについてのすべての情報を表示します。\n" "\n" -"lxc image list [remote:] [filter]\n" +"lxc image list [:] [filter] [--format table|json]\n" " LXD のイメージストア内のイメージを一覧表示します。プロパティでフィルタ\n" " を行う場合は、フィルタは = の形になります。フィルタはイメー\n" " ジハッシュの一部やイメージエイリアス名の一部も指定できます。\n" "\n" -"lxc image show [remote:]\n" +"lxc image show [:]\n" " ユーザが変更できるプロパティの YAML 形式の出力を行います。\n" "\n" -"lxc image edit [remote:]\n" +"lxc image edit [:]\n" " 外部エディタまたは標準入力からの読み込みにより、イメージを編集します。\n" " 例: lxc image edit # エディタの起動\n" " cat image.yaml | lxc image edit # image.yaml から読み込み\n" "\n" -"lxc image alias create [remote:] \n" +"lxc image alias create [:] \n" " 既存のイメージに新たにエイリアスを作成します。\n" "\n" -"lxc image alias delete [remote:]\n" +"lxc image alias delete [:]\n" " エイリアスを削除します。\n" "\n" -"lxc image alias list [remote:] [filter]\n" +"lxc image alias list [:] [filter]\n" " エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリア" "ス\n" -" 名の一部をフィルタとして指定できます。\n" +" 名の一部をフィルタとして指定できます。" #: lxc/info.go:161 msgid "Memory (current)" @@ -1307,7 +1298,6 @@ msgstr "サマリーはありません。" #: lxc/monitor.go:41 -#, fuzzy msgid "" "Monitor activity on the LXD server.\n" "\n" @@ -1323,7 +1313,7 @@ msgstr "" "LXD サーバの動作をモニタリングします。\n" "\n" -"lxc monitor [remote:] [--type=TYPE...]\n" +"lxc monitor [:] [--type=TYPE...]\n" "\n" "指定した LXD サーバのモニタリングインターフェースに接続します。\n" "\n" @@ -1331,20 +1321,19 @@ "--type により、モニタリングするタイプを指定できます。\n" "\n" "例:\n" -"lxc monitor --type=logging" +" lxc monitor --type=logging" -#: lxc/network.go:215 lxc/network.go:264 +#: lxc/network.go:216 lxc/network.go:265 msgid "More than one device matches, specify the device name." msgstr "複数のデバイスとマッチします。デバイス名を指定してください。" -#: lxc/file.go:237 +#: lxc/file.go:273 msgid "More than one file to download, but target is not a directory" msgstr "" "ダウンロード対象のファイルが複数ありますが、コピー先がディレクトリではありま" -"せん。" +"せん" #: lxc/move.go:16 -#, fuzzy msgid "" "Move containers within or in between lxd instances.\n" "\n" @@ -1360,22 +1349,25 @@ msgstr "" "LXD ホスト内、もしくは LXD ホスト間でコンテナを移動します。\n" "\n" -"lxc move [remote:] [remote:]\n" +"lxc move [:] [:][]\n" " 2 つのホスト間でコンテナを移動します。コピー先の名前が元と違う場合は\n" " 同時にリネームされます。\n" "\n" "lxc move \n" " ローカルのコンテナをリネームします。\n" +"\n" +"lxc move / /\n" +" スナップショットをリネームします。" -#: lxc/action.go:69 +#: lxc/action.go:70 msgid "Must supply container name for: " msgstr "コンテナ名を指定する必要があります: " -#: lxc/list.go:428 lxc/network.go:421 lxc/profile.go:446 lxc/remote.go:380 +#: lxc/list.go:430 lxc/network.go:426 lxc/profile.go:451 lxc/remote.go:380 msgid "NAME" msgstr "" -#: lxc/network.go:407 lxc/remote.go:354 lxc/remote.go:359 +#: lxc/network.go:412 lxc/remote.go:354 lxc/remote.go:359 msgid "NO" msgstr "" @@ -1384,33 +1376,33 @@ msgid "Name: %s" msgstr "コンテナ名: %s" -#: lxc/network.go:189 +#: lxc/network.go:190 #, c-format msgid "Network %s created" msgstr "ネットワーク %s を作成しました" -#: lxc/network.go:292 +#: lxc/network.go:293 #, c-format msgid "Network %s deleted" msgstr "ネットワーク %s を削除しました" -#: lxc/init.go:141 lxc/init.go:142 lxc/launch.go:46 lxc/launch.go:47 +#: lxc/init.go:142 lxc/init.go:143 msgid "Network name" msgstr "ネットワーク名:" -#: lxc/image.go:167 lxc/publish.go:34 +#: lxc/image.go:168 lxc/publish.go:35 msgid "New alias to define at target" msgstr "新しいエイリアスを定義する" -#: lxc/config.go:284 +#: lxc/config.go:285 msgid "No certificate provided to add" msgstr "追加すべき証明書が提供されていません" -#: lxc/network.go:224 lxc/network.go:273 +#: lxc/network.go:225 lxc/network.go:274 msgid "No device found for this network" msgstr "このネットワークに対するデバイスがありません" -#: lxc/config.go:307 +#: lxc/config.go:308 msgid "No fingerprint specified." msgstr "フィンガープリントが指定されていません。" @@ -1418,32 +1410,36 @@ msgid "Only https URLs are supported for simplestreams" msgstr "simplestreams は https の URL のみサポートします" -#: lxc/image.go:424 +#: lxc/image.go:430 msgid "Only https:// is supported for remote image import." msgstr "リモートイメージのインポートは https:// のみをサポートします。" +#: lxc/network.go:322 lxc/network.go:449 +msgid "Only managed networks can be modified." +msgstr "" + #: lxc/help.go:63 lxc/main.go:112 msgid "Options:" msgstr "オプション:" -#: lxc/image.go:535 +#: lxc/image.go:541 #, c-format msgid "Output is in %s" msgstr "%s に出力されます" -#: lxc/exec.go:55 +#: lxc/exec.go:56 msgid "Override the terminal mode (auto, interactive or non-interactive)" msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)" -#: lxc/list.go:572 +#: lxc/list.go:576 msgid "PERSISTENT" msgstr "" -#: lxc/list.go:429 +#: lxc/list.go:431 msgid "PID" -msgstr "" +msgstr "PID" -#: lxc/list.go:430 +#: lxc/list.go:432 msgid "PROFILES" msgstr "" @@ -1451,7 +1447,7 @@ msgid "PROTOCOL" msgstr "" -#: lxc/image.go:637 lxc/remote.go:383 +#: lxc/image.go:643 lxc/remote.go:383 msgid "PUBLIC" msgstr "" @@ -1464,12 +1460,10 @@ msgstr "送信パケット" #: lxc/help.go:70 -#, fuzzy msgid "Path to an alternate client configuration directory" msgstr "別のクライアント用設定ディレクトリ" #: lxc/help.go:71 -#, fuzzy msgid "Path to an alternate server directory" msgstr "別のサーバ用設定ディレクトリ" @@ -1478,37 +1472,33 @@ msgstr "アクセスが拒否されました。lxd グループに所属していますか?" #: lxc/info.go:106 -#, c-format +#, fuzzy, c-format msgid "Pid: %d" -msgstr "" +msgstr "Pid: %d" -#: lxc/network.go:342 lxc/profile.go:218 +#: lxc/network.go:347 lxc/profile.go:219 msgid "Press enter to open the editor again" msgstr "再度エディタを開くためには Enter キーを押します" -#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:732 +#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:738 msgid "Press enter to start the editor again" msgstr "再度エディタを起動するには Enter キーを押します" #: lxc/manpage.go:18 -#, fuzzy msgid "Print all the subcommands help." msgstr "全てのサブコマンドのヘルプを表示します。" #: lxc/help.go:65 -#, fuzzy msgid "Print debug information" -msgstr "デバッグ情報を表示します。" +msgstr "デバッグ情報を表示します" #: lxc/help.go:64 -#, fuzzy msgid "Print less common commands" -msgstr "全てのコマンドを表示します (主なコマンドだけではなく)。" +msgstr "全てのコマンドを表示します (主なコマンドだけではなく)" #: lxc/help.go:66 -#, fuzzy msgid "Print verbose information" -msgstr "詳細情報を表示します。" +msgstr "詳細情報を表示します" #: lxc/version.go:18 msgid "" @@ -1525,32 +1515,31 @@ msgid "Processes: %d" msgstr "プロセス数: %d" -#: lxc/profile.go:274 +#: lxc/profile.go:275 #, c-format msgid "Profile %s added to %s" msgstr "プロファイル %s が %s に追加されました" -#: lxc/profile.go:169 +#: lxc/profile.go:170 #, c-format msgid "Profile %s created" msgstr "プロファイル %s を作成しました" -#: lxc/profile.go:239 +#: lxc/profile.go:240 #, c-format msgid "Profile %s deleted" msgstr "プロファイル %s を削除しました" -#: lxc/profile.go:305 +#: lxc/profile.go:306 #, c-format msgid "Profile %s removed from %s" msgstr "プロファイル %s が %s から削除されました" -#: lxc/copy.go:33 lxc/copy.go:34 lxc/init.go:137 lxc/init.go:138 -#: lxc/launch.go:42 lxc/launch.go:43 +#: lxc/copy.go:34 lxc/copy.go:35 lxc/init.go:138 lxc/init.go:139 msgid "Profile to apply to the new container" msgstr "新しいコンテナに適用するプロファイル" -#: lxc/profile.go:255 +#: lxc/profile.go:256 #, c-format msgid "Profiles %s applied to %s" msgstr "プロファイル %s が %s に追加されました" @@ -1560,7 +1549,7 @@ msgid "Profiles: %s" msgstr "プロファイル: %s" -#: lxc/image.go:358 +#: lxc/image.go:364 msgid "Properties:" msgstr "プロパティ:" @@ -1568,13 +1557,12 @@ msgid "Public image server" msgstr "Public なイメージサーバとして設定します" -#: lxc/image.go:346 +#: lxc/image.go:347 #, c-format msgid "Public: %s" -msgstr "" +msgstr "パブリック: %s" -#: lxc/publish.go:26 -#, fuzzy +#: lxc/publish.go:27 msgid "" "Publish containers as images.\n" "\n" @@ -1583,8 +1571,8 @@ msgstr "" "イメージとしてコンテナを publish します。\n" "\n" -"lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-" -"value]..." +"lxc publish [:][/] [:] [--" +"alias=ALIAS...] [prop-key=prop-value...]" #: lxc/file.go:57 lxc/file.go:58 msgid "Recursively push or pull files" @@ -1599,22 +1587,20 @@ msgid "Remote: %s" msgstr "リモート名: %s" -#: lxc/delete.go:42 +#: lxc/delete.go:43 #, c-format msgid "Remove %s (yes/no): " msgstr "%s を消去しますか (yes/no): " -#: lxc/delete.go:36 lxc/delete.go:37 -#, fuzzy +#: lxc/delete.go:37 lxc/delete.go:38 msgid "Require user confirmation" -msgstr "ユーザの確認を要求する。" +msgstr "ユーザの確認を要求する" #: lxc/info.go:127 msgid "Resources:" msgstr "リソース:" #: lxc/restore.go:21 -#, fuzzy msgid "" "Restore a container's state to a previous snapshot.\n" "\n" @@ -1632,29 +1618,31 @@ msgstr "" "リソースの現在の状態をスナップショット時点の状態に設定します。\n" "\n" -"lxc restore [remote:] [--stateful]\n" +"lxc restore [:] [--stateful]\n" "\n" "スナップショットからコンテナをリストアします (オプションで実行状態もリスト\n" "アします。詳しくはスナップショットのヘルプをご覧ください)。\n" "\n" "例:\n" -"lxc snapshot u1 snap0 # スナップショットの作成\n" -"lxc restore u1 snap0 # スナップショットからリストア" +"スナップショットの作成:\n" +" lxc snapshot u1 snap0\n" +"スナップショットからのリストア:\n" +" lxc restore u1 snap0" -#: lxc/init.go:239 +#: lxc/init.go:240 #, c-format msgid "Retrieving image: %s" msgstr "イメージの取得中: %s" -#: lxc/image.go:640 +#: lxc/image.go:646 msgid "SIZE" msgstr "" -#: lxc/list.go:431 +#: lxc/list.go:433 msgid "SNAPSHOTS" msgstr "" -#: lxc/list.go:432 +#: lxc/list.go:434 msgid "STATE" msgstr "" @@ -1691,20 +1679,18 @@ msgstr "全てコマンドを表示します (主なコマンドだけではなく)" #: lxc/help.go:67 -#, fuzzy msgid "Show client version" -msgstr "クライアントのバージョンを表示します。" +msgstr "クライアントのバージョンを表示します" #: lxc/info.go:36 msgid "Show the container's last 100 log lines?" msgstr "コンテナログの最後の 100 行を表示しますか?" -#: lxc/config.go:32 -#, fuzzy +#: lxc/config.go:33 msgid "Show the expanded configuration" -msgstr "拡張した設定を表示するかどうか" +msgstr "拡張した設定を表示する" -#: lxc/image.go:344 +#: lxc/image.go:345 #, c-format msgid "Size: %.2fMB" msgstr "サイズ: %.2fMB" @@ -1713,11 +1699,11 @@ msgid "Snapshots:" msgstr "スナップショット:" -#: lxc/image.go:368 +#: lxc/image.go:374 msgid "Source:" msgstr "取得元:" -#: lxc/launch.go:144 +#: lxc/launch.go:136 #, c-format msgid "Starting %s" msgstr "%s を起動中" @@ -1727,18 +1713,17 @@ msgid "Status: %s" msgstr "状態: %s" -#: lxc/publish.go:35 lxc/publish.go:36 +#: lxc/publish.go:36 lxc/publish.go:37 msgid "Stop the container if currently running" msgstr "実行中の場合、コンテナを停止します" -#: lxc/delete.go:106 lxc/publish.go:113 +#: lxc/delete.go:107 lxc/publish.go:114 msgid "Stopping container failed!" msgstr "コンテナの停止に失敗しました!" -#: lxc/action.go:45 -#, fuzzy +#: lxc/action.go:46 msgid "Store the container state (only for stop)" -msgstr "コンテナの状態を保存します (stopのみ)。" +msgstr "コンテナの状態を保存します (stopのみ)" #: lxc/info.go:169 msgid "Swap (current)" @@ -1748,15 +1733,15 @@ msgid "Swap (peak)" msgstr "Swap (ピーク)" -#: lxc/list.go:433 lxc/network.go:422 +#: lxc/list.go:435 lxc/network.go:427 msgid "TYPE" msgstr "" -#: lxc/delete.go:92 +#: lxc/delete.go:93 msgid "The container is currently running, stop it first or pass --force." msgstr "コンテナは実行中です。先に停止させるか、--force を指定してください。" -#: lxc/publish.go:91 +#: lxc/publish.go:92 msgid "" "The container is currently running. Use --force to have it stopped and " "restarted." @@ -1764,17 +1749,16 @@ "コンテナは現在実行中です。停止して、再起動するために --force を使用してくだ\n" "さい。" -#: lxc/init.go:312 -#, fuzzy +#: lxc/init.go:313 msgid "The container you are starting doesn't have any network attached to it." msgstr "起動しようとしたコンテナに接続されているネットワークがありません。" -#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738 -#: lxc/config.go:776 lxc/config.go:794 +#: lxc/config.go:680 lxc/config.go:692 lxc/config.go:725 lxc/config.go:743 +#: lxc/config.go:781 lxc/config.go:799 msgid "The device doesn't exist" msgstr "デバイスが存在しません" -#: lxc/init.go:296 +#: lxc/init.go:297 #, c-format msgid "The local image '%s' couldn't be found, trying '%s:' instead." msgstr "" @@ -1784,53 +1768,50 @@ msgid "The opposite of `lxc pause` is `lxc start`." msgstr "`lxc pause` の反対のコマンドは `lxc start` です。" -#: lxc/network.go:229 lxc/network.go:278 +#: lxc/network.go:230 lxc/network.go:279 msgid "The specified device doesn't exist" msgstr "指定したデバイスが存在しません" -#: lxc/network.go:233 lxc/network.go:282 +#: lxc/network.go:234 lxc/network.go:283 msgid "The specified device doesn't match the network" msgstr "指定したデバイスはネットワークとマッチしません" -#: lxc/publish.go:64 +#: lxc/publish.go:65 msgid "There is no \"image name\". Did you want an alias?" msgstr "" -"publish 先にはイメージ名は指定できません。\"--alias\" オプションを使ってく" -"だ\n" +"publish 先にはイメージ名は指定できません。\"--alias\" オプションを使ってくだ" "さい。" -#: lxc/action.go:41 -#, fuzzy +#: lxc/action.go:42 msgid "Time to wait for the container before killing it" msgstr "コンテナを強制停止するまでの時間" -#: lxc/image.go:347 +#: lxc/image.go:348 msgid "Timestamps:" msgstr "タイムスタンプ:" -#: lxc/init.go:314 +#: lxc/init.go:315 msgid "To attach a network to a container, use: lxc network attach" msgstr "" -"コンテナにネットワークを接続するには、lxc network attach を使用してください。" +"コンテナにネットワークを接続するには、lxc network attach を使用してください" -#: lxc/init.go:313 +#: lxc/init.go:314 msgid "To create a new network, use: lxc network create" msgstr "" -"新しいネットワークを作成するには、lxc network create を使用してください。" +"新しいネットワークを作成するには、lxc network create を使用してください" #: lxc/main.go:137 msgid "To start your first container, try: lxc launch ubuntu:16.04" msgstr "" -"初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてく" -"だ\n" -"さい。" +"初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてくだ" +"さい" -#: lxc/image.go:426 -#, fuzzy, c-format +#: lxc/image.go:432 +#, c-format msgid "Transferring image: %s" -msgstr "イメージを転送中: %d%%" +msgstr "イメージを転送中: %s" -#: lxc/action.go:99 lxc/launch.go:157 +#: lxc/action.go:100 lxc/launch.go:149 #, c-format msgid "Try `lxc info --show-log %s` for more info" msgstr "更に情報を得るために `lxc info --show-log %s` を実行してみてください" @@ -1843,7 +1824,7 @@ msgid "Type: persistent" msgstr "タイプ: persistent" -#: lxc/image.go:641 +#: lxc/image.go:647 msgid "UPLOAD DATE" msgstr "" @@ -1851,7 +1832,7 @@ msgid "URL" msgstr "" -#: lxc/network.go:424 lxc/profile.go:447 +#: lxc/network.go:429 lxc/profile.go:452 msgid "USED BY" msgstr "" @@ -1859,7 +1840,7 @@ msgid "Unable to read remote TLS certificate" msgstr "リモートの TLS 証明書を読めません" -#: lxc/image.go:352 +#: lxc/image.go:353 #, c-format msgid "Uploaded: %s" msgstr "アップロード日時: %s" @@ -1870,11 +1851,10 @@ msgstr "使い方: %s" #: lxc/help.go:48 -#, fuzzy msgid "Usage: lxc [options]" -msgstr "使い方: lxc [サブコマンド] [オプション]" +msgstr "使い方: lxc <コマンド> [オプション]" -#: lxc/delete.go:46 +#: lxc/delete.go:47 msgid "User aborted delete operation." msgstr "ユーザが削除操作を中断しました。" @@ -1889,7 +1869,7 @@ msgid "Whether or not to snapshot the container's running state" msgstr "コンテナの稼動状態のスナップショットを取得するかどうか" -#: lxc/network.go:409 lxc/remote.go:356 lxc/remote.go:361 +#: lxc/network.go:414 lxc/remote.go:356 lxc/remote.go:361 msgid "YES" msgstr "" @@ -1897,23 +1877,23 @@ msgid "`lxc config profile` is deprecated, please use `lxc profile`" msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください" -#: lxc/launch.go:128 +#: lxc/launch.go:120 msgid "bad number of things scanned from image, container or snapshot" msgstr "" "イメージ、コンテナ、スナップショットのいずれかからスキャンされた数が不正" -#: lxc/action.go:95 +#: lxc/action.go:96 msgid "bad result type from action" msgstr "アクションからの結果タイプが不正です" -#: lxc/copy.go:115 +#: lxc/copy.go:116 msgid "can't copy to the same container name" msgstr "同じコンテナ名へはコピーできません" -#: lxc/file.go:272 +#: lxc/file.go:308 msgid "can't pull a directory without --recursive" msgstr "" -"ディレクトリを pull する場合は --recursive オプションを使用してください。" +"ディレクトリを pull する場合は --recursive オプションを使用してください" #: lxc/remote.go:344 msgid "can't remove the default remote" @@ -1921,24 +1901,24 @@ #: lxc/file.go:119 msgid "can't supply uid/gid/mode in recursive mode" -msgstr "再帰 (recursive) モードでは uid/gid/mode を指定できません。" +msgstr "再帰 (recursive) モードでは uid/gid/mode を指定できません" #: lxc/remote.go:370 msgid "default" msgstr "" -#: lxc/copy.go:131 lxc/copy.go:136 lxc/copy.go:232 lxc/copy.go:237 -#: lxc/init.go:219 lxc/init.go:224 lxc/launch.go:112 lxc/launch.go:117 +#: lxc/copy.go:132 lxc/copy.go:137 lxc/copy.go:233 lxc/copy.go:238 +#: lxc/init.go:220 lxc/init.go:225 lxc/launch.go:104 lxc/launch.go:109 msgid "didn't get any affected image, container or snapshot from server" msgstr "" "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n" "した" -#: lxc/image.go:338 +#: lxc/image.go:339 msgid "disabled" msgstr "無効" -#: lxc/image.go:340 +#: lxc/image.go:341 msgid "enabled" msgstr "有効" @@ -1952,15 +1932,15 @@ msgid "error: unknown command: %s" msgstr "エラー: 未知のコマンド: %s" -#: lxc/launch.go:132 +#: lxc/launch.go:124 msgid "got bad version" msgstr "不正なバージョンを得ました" -#: lxc/image.go:333 lxc/image.go:617 +#: lxc/image.go:334 lxc/image.go:623 msgid "no" msgstr "" -#: lxc/copy.go:164 +#: lxc/copy.go:165 msgid "not all the profiles from the source exist on the target" msgstr "コピー元の全てのプロファイルがターゲットに存在しません" @@ -1973,7 +1953,7 @@ msgid "processing aliases failed %s\n" msgstr "エイリアスの処理が失敗しました %s\n" -#: lxc/file.go:313 +#: lxc/file.go:349 msgid "recursive edit doesn't make sense :(" msgstr "再帰的な edit は意味がありません :(" @@ -2010,7 +1990,7 @@ msgid "taken at %s" msgstr "%s に取得しました" -#: lxc/exec.go:185 +#: lxc/exec.go:186 msgid "unreachable return reached" msgstr "到達しないはずのreturnに到達しました" @@ -2018,11 +1998,11 @@ msgid "wrong number of subcommand arguments" msgstr "サブコマンドの引数の数が正しくありません" -#: lxc/delete.go:45 lxc/image.go:335 lxc/image.go:621 +#: lxc/delete.go:46 lxc/image.go:336 lxc/image.go:627 msgid "yes" msgstr "" -#: lxc/copy.go:44 +#: lxc/copy.go:45 msgid "you must specify a source container name" msgstr "コピー元のコンテナ名を指定してください" diff -Nru lxd-2.7/po/lxd.pot lxd-2.8/po/lxd.pot --- lxd-2.7/po/lxd.pot 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/po/lxd.pot 2017-01-25 02:50:49.000000000 +0000 @@ -7,7 +7,7 @@ msgid "" msgstr "Project-Id-Version: lxd\n" "Report-Msgid-Bugs-To: lxc-devel@lists.linuxcontainers.org\n" - "POT-Creation-Date: 2016-12-20 17:29+0000\n" + "POT-Creation-Date: 2017-01-24 18:22-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -32,7 +32,7 @@ msgid " Network usage:" msgstr "" -#: lxc/config.go:36 +#: lxc/config.go:37 msgid "### This is a yaml representation of the configuration.\n" "### Any line starting with a '# will be ignored.\n" "###\n" @@ -52,7 +52,7 @@ "### Note that the name is shown but cannot be changed" msgstr "" -#: lxc/image.go:85 +#: lxc/image.go:86 msgid "### This is a yaml representation of the image properties.\n" "### Any line starting with a '# will be ignored.\n" "###\n" @@ -61,7 +61,7 @@ "### description: My custom image" msgstr "" -#: lxc/network.go:28 +#: lxc/network.go:29 msgid "### This is a yaml representation of the network.\n" "### Any line starting with a '# will be ignored.\n" "###\n" @@ -80,7 +80,7 @@ "### Note that only the configuration can be changed." msgstr "" -#: lxc/profile.go:27 +#: lxc/profile.go:28 msgid "### This is a yaml representation of the profile.\n" "### Any line starting with a '# will be ignored.\n" "###\n" @@ -100,7 +100,7 @@ "### Note that the name is shown but cannot be changed" msgstr "" -#: lxc/image.go:614 +#: lxc/image.go:620 #, c-format msgid "%s (%d more)" msgstr "" @@ -109,19 +109,19 @@ msgid "'/' not allowed in snapshot name" msgstr "" -#: lxc/profile.go:253 +#: lxc/profile.go:254 msgid "(none)" msgstr "" -#: lxc/image.go:635 lxc/image.go:677 +#: lxc/image.go:641 lxc/image.go:683 msgid "ALIAS" msgstr "" -#: lxc/image.go:639 +#: lxc/image.go:645 msgid "ARCH" msgstr "" -#: lxc/list.go:425 +#: lxc/list.go:427 msgid "ARCHITECTURE" msgstr "" @@ -134,16 +134,16 @@ msgid "Admin password for %s: " msgstr "" -#: lxc/image.go:362 +#: lxc/image.go:368 msgid "Aliases:" msgstr "" -#: lxc/image.go:345 lxc/info.go:93 +#: lxc/image.go:346 lxc/info.go:93 #, c-format msgid "Architecture: %s" msgstr "" -#: lxc/image.go:366 +#: lxc/image.go:372 #, c-format msgid "Auto update: %s" msgstr "" @@ -160,7 +160,7 @@ msgid "Bytes sent" msgstr "" -#: lxc/config.go:273 +#: lxc/config.go:274 msgid "COMMON NAME" msgstr "" @@ -168,21 +168,21 @@ msgid "CPU usage (in seconds)" msgstr "" -#: lxc/list.go:426 +#: lxc/list.go:428 msgid "CREATED AT" msgstr "" -#: lxc/config.go:113 +#: lxc/config.go:114 lxc/network.go:463 #, c-format msgid "Can't read from stdin: %s" msgstr "" -#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181 +#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182 #, c-format msgid "Can't unset key '%s', it's not currently set." msgstr "" -#: lxc/network.go:385 lxc/profile.go:419 +#: lxc/network.go:390 lxc/profile.go:424 msgid "Cannot provide container name to list" msgstr "" @@ -191,7 +191,7 @@ msgid "Certificate fingerprint: %x" msgstr "" -#: lxc/action.go:33 +#: lxc/action.go:34 #, c-format msgid "Change state of one or more containers to %s.\n" "\n" @@ -208,15 +208,15 @@ msgid "Client certificate stored at server: " msgstr "" -#: lxc/list.go:121 lxc/list.go:122 +#: lxc/list.go:122 lxc/list.go:123 msgid "Columns" msgstr "" -#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:135 lxc/init.go:136 +#: lxc/copy.go:32 lxc/copy.go:33 lxc/init.go:136 lxc/init.go:137 msgid "Config key/value to apply to the new container" msgstr "" -#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:731 lxc/network.go:341 lxc/profile.go:217 +#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:737 lxc/network.go:346 lxc/profile.go:218 #, c-format msgid "Config parsing error: %s" msgstr "" @@ -225,31 +225,31 @@ msgid "Connection refused; is LXD running?" msgstr "" -#: lxc/publish.go:61 +#: lxc/publish.go:62 msgid "Container name is mandatory" msgstr "" -#: lxc/copy.go:140 lxc/copy.go:241 lxc/init.go:229 +#: lxc/copy.go:141 lxc/copy.go:242 lxc/init.go:230 #, c-format msgid "Container name is: %s" msgstr "" -#: lxc/publish.go:151 lxc/publish.go:166 +#: lxc/publish.go:152 lxc/publish.go:167 #, c-format msgid "Container published with fingerprint: %s" msgstr "" -#: lxc/image.go:165 +#: lxc/image.go:166 msgid "Copy aliases from source" msgstr "" -#: lxc/copy.go:24 +#: lxc/copy.go:25 msgid "Copy containers within or in between LXD instances.\n" "\n" "lxc copy [:][/] [[:]] [--ephemeral|e] [--profile|-p ...] [--config|-c ...]" msgstr "" -#: lxc/image.go:278 +#: lxc/image.go:279 #, c-format msgid "Copying the image: %s" msgstr "" @@ -283,29 +283,29 @@ msgid "Create any directories necessary" msgstr "" -#: lxc/image.go:350 lxc/info.go:95 +#: lxc/image.go:351 lxc/info.go:95 #, c-format msgid "Created: %s" msgstr "" -#: lxc/init.go:180 lxc/launch.go:126 +#: lxc/init.go:181 lxc/launch.go:127 #, c-format msgid "Creating %s" msgstr "" -#: lxc/init.go:178 +#: lxc/init.go:179 msgid "Creating the container" msgstr "" -#: lxc/image.go:638 lxc/image.go:679 +#: lxc/image.go:644 lxc/image.go:685 msgid "DESCRIPTION" msgstr "" -#: lxc/publish.go:37 +#: lxc/publish.go:38 msgid "Define a compression algorithm: for image or none" msgstr "" -#: lxc/delete.go:25 +#: lxc/delete.go:26 msgid "Delete containers or snapshots.\n" "\n" "lxc delete [:][/] [[:][/]...]\n" @@ -313,21 +313,21 @@ "Destroy containers or snapshots with any attached data (configuration, snapshots, ...)." msgstr "" -#: lxc/config.go:647 +#: lxc/config.go:652 #, c-format msgid "Device %s added to %s" msgstr "" -#: lxc/config.go:834 +#: lxc/config.go:839 #, c-format msgid "Device %s removed from %s" msgstr "" -#: lxc/list.go:570 +#: lxc/list.go:574 msgid "EPHEMERAL" msgstr "" -#: lxc/config.go:275 +#: lxc/config.go:276 msgid "EXPIRY DATE" msgstr "" @@ -339,7 +339,7 @@ msgid "Enable verbose mode" msgstr "" -#: lxc/exec.go:54 +#: lxc/exec.go:55 msgid "Environment variable to set (e.g. HOME=/home/foo)" msgstr "" @@ -347,7 +347,7 @@ msgid "Environment:" msgstr "" -#: lxc/copy.go:35 lxc/copy.go:36 lxc/init.go:139 lxc/init.go:140 +#: lxc/copy.go:36 lxc/copy.go:37 lxc/init.go:140 lxc/init.go:141 msgid "Ephemeral container" msgstr "" @@ -355,7 +355,7 @@ msgid "Event type to listen for" msgstr "" -#: lxc/exec.go:45 +#: lxc/exec.go:46 msgid "Execute the specified command in a container.\n" "\n" "lxc exec [:] [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] \n" @@ -363,33 +363,33 @@ "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)." msgstr "" -#: lxc/image.go:354 +#: lxc/image.go:355 #, c-format msgid "Expires: %s" msgstr "" -#: lxc/image.go:356 +#: lxc/image.go:357 msgid "Expires: never" msgstr "" -#: lxc/config.go:272 lxc/image.go:636 lxc/image.go:678 +#: lxc/config.go:273 lxc/image.go:642 lxc/image.go:684 msgid "FINGERPRINT" msgstr "" -#: lxc/list.go:124 +#: lxc/list.go:125 msgid "Fast mode (same as --columns=nsacPt" msgstr "" -#: lxc/image.go:343 +#: lxc/image.go:344 #, c-format msgid "Fingerprint: %s" msgstr "" -#: lxc/action.go:42 lxc/action.go:43 +#: lxc/action.go:43 lxc/action.go:44 msgid "Force the container to shutdown" msgstr "" -#: lxc/delete.go:34 lxc/delete.go:35 +#: lxc/delete.go:35 lxc/delete.go:36 msgid "Force the removal of stopped containers" msgstr "" @@ -397,7 +397,7 @@ msgid "Force using the local unix socket" msgstr "" -#: lxc/image.go:168 lxc/list.go:123 +#: lxc/image.go:169 lxc/list.go:124 msgid "Format" msgstr "" @@ -411,15 +411,15 @@ "lxc help [--all]" msgstr "" -#: lxc/list.go:423 +#: lxc/list.go:425 msgid "IPV4" msgstr "" -#: lxc/list.go:424 +#: lxc/list.go:426 msgid "IPV6" msgstr "" -#: lxc/config.go:274 +#: lxc/config.go:275 msgid "ISSUE DATE" msgstr "" @@ -431,20 +431,20 @@ msgid "Ignore aliases when determining what command to run" msgstr "" -#: lxc/action.go:46 +#: lxc/action.go:47 msgid "Ignore the container state (only for start)" msgstr "" -#: lxc/image.go:281 +#: lxc/image.go:282 msgid "Image copied successfully!" msgstr "" -#: lxc/image.go:421 lxc/image.go:433 +#: lxc/image.go:427 lxc/image.go:439 #, c-format msgid "Image imported with fingerprint: %s" msgstr "" -#: lxc/image.go:418 +#: lxc/image.go:424 #, c-format msgid "Importing the image: %s" msgstr "" @@ -468,7 +468,7 @@ msgid "Invalid URL scheme \"%s\" in \"%s\"" msgstr "" -#: lxc/config.go:253 +#: lxc/config.go:254 msgid "Invalid certificate" msgstr "" @@ -476,7 +476,7 @@ msgid "Invalid configuration key" msgstr "" -#: lxc/file.go:249 +#: lxc/file.go:285 #, c-format msgid "Invalid source %s" msgstr "" @@ -490,11 +490,11 @@ msgid "Ips:" msgstr "" -#: lxc/image.go:166 +#: lxc/image.go:167 msgid "Keep the image up to date after initial copy" msgstr "" -#: lxc/list.go:427 +#: lxc/list.go:429 msgid "LAST USED AT" msgstr "" @@ -502,7 +502,16 @@ msgid "LXD socket not found; is LXD installed and running?" msgstr "" -#: lxc/launch.go:22 +#: lxc/image.go:360 +#, c-format +msgid "Last used: %s" +msgstr "" + +#: lxc/image.go:362 +msgid "Last used: never" +msgstr "" + +#: lxc/launch.go:23 msgid "Launch a container from a particular image.\n" "\n" "lxc launch [:] [:][] [--ephemeral|-e] [--profile|-p ...] [--config|-c ...] [--network|-n ]\n" @@ -526,7 +535,7 @@ " lxc info []" msgstr "" -#: lxc/list.go:68 +#: lxc/list.go:69 msgid "Lists the containers.\n" "\n" "lxc list [:] [filters] [--format table|json] [-c ] [--fast]\n" @@ -582,19 +591,19 @@ msgid "Log:" msgstr "" -#: lxc/network.go:423 +#: lxc/network.go:428 msgid "MANAGED" msgstr "" -#: lxc/image.go:164 +#: lxc/image.go:165 msgid "Make image public" msgstr "" -#: lxc/publish.go:33 +#: lxc/publish.go:34 msgid "Make the image public" msgstr "" -#: lxc/profile.go:48 +#: lxc/profile.go:49 msgid "Manage configuration profiles.\n" "\n" "lxc profile list [:] List available profiles.\n" @@ -634,7 +643,7 @@ " Add a profile device, such as a disk or a nic, to the containers using the specified profile." msgstr "" -#: lxc/config.go:57 +#: lxc/config.go:58 msgid "Manage configuration.\n" "\n" "lxc config device add [:] [key=value...] Add a device to a container.\n" @@ -689,7 +698,7 @@ " lxc file pull foo/etc/hosts ." msgstr "" -#: lxc/network.go:48 +#: lxc/network.go:49 msgid "Manage networks.\n" "\n" "lxc network list [:] List available networks.\n" @@ -715,16 +724,16 @@ msgid "Manage remote LXD servers.\n" "\n" "lxc remote add [] [--accept-certificate] [--password=PASSWORD]\n" - " [--public] [--protocol=PROTOCOL] Add the remote at .\n" - "lxc remote remove Remove the remote .\n" + " [--public] [--protocol=PROTOCOL] Add the remote at .\n" + "lxc remote remove Remove the remote .\n" "lxc remote list List all remotes.\n" - "lxc remote rename Rename remote to .\n" - "lxc remote set-url Update 's url to .\n" + "lxc remote rename Rename remote to .\n" + "lxc remote set-url Update 's url to .\n" "lxc remote set-default Set the default remote.\n" "lxc remote get-default Print the default remote." msgstr "" -#: lxc/image.go:95 +#: lxc/image.go:96 msgid "Manipulate container images.\n" "\n" "In LXD containers are created from images. Those images were themselves\n" @@ -818,11 +827,11 @@ " lxc monitor --type=logging" msgstr "" -#: lxc/network.go:215 lxc/network.go:264 +#: lxc/network.go:216 lxc/network.go:265 msgid "More than one device matches, specify the device name." msgstr "" -#: lxc/file.go:237 +#: lxc/file.go:273 msgid "More than one file to download, but target is not a directory" msgstr "" @@ -839,15 +848,15 @@ " Rename a snapshot." msgstr "" -#: lxc/action.go:69 +#: lxc/action.go:70 msgid "Must supply container name for: " msgstr "" -#: lxc/list.go:428 lxc/network.go:421 lxc/profile.go:446 lxc/remote.go:380 +#: lxc/list.go:430 lxc/network.go:426 lxc/profile.go:451 lxc/remote.go:380 msgid "NAME" msgstr "" -#: lxc/network.go:407 lxc/remote.go:354 lxc/remote.go:359 +#: lxc/network.go:412 lxc/remote.go:354 lxc/remote.go:359 msgid "NO" msgstr "" @@ -856,33 +865,33 @@ msgid "Name: %s" msgstr "" -#: lxc/network.go:189 +#: lxc/network.go:190 #, c-format msgid "Network %s created" msgstr "" -#: lxc/network.go:292 +#: lxc/network.go:293 #, c-format msgid "Network %s deleted" msgstr "" -#: lxc/init.go:141 lxc/init.go:142 +#: lxc/init.go:142 lxc/init.go:143 msgid "Network name" msgstr "" -#: lxc/image.go:167 lxc/publish.go:34 +#: lxc/image.go:168 lxc/publish.go:35 msgid "New alias to define at target" msgstr "" -#: lxc/config.go:284 +#: lxc/config.go:285 msgid "No certificate provided to add" msgstr "" -#: lxc/network.go:224 lxc/network.go:273 +#: lxc/network.go:225 lxc/network.go:274 msgid "No device found for this network" msgstr "" -#: lxc/config.go:307 +#: lxc/config.go:308 msgid "No fingerprint specified." msgstr "" @@ -890,32 +899,36 @@ msgid "Only https URLs are supported for simplestreams" msgstr "" -#: lxc/image.go:424 +#: lxc/image.go:430 msgid "Only https:// is supported for remote image import." msgstr "" +#: lxc/network.go:322 lxc/network.go:449 +msgid "Only managed networks can be modified." +msgstr "" + #: lxc/help.go:63 lxc/main.go:112 msgid "Options:" msgstr "" -#: lxc/image.go:535 +#: lxc/image.go:541 #, c-format msgid "Output is in %s" msgstr "" -#: lxc/exec.go:55 +#: lxc/exec.go:56 msgid "Override the terminal mode (auto, interactive or non-interactive)" msgstr "" -#: lxc/list.go:572 +#: lxc/list.go:576 msgid "PERSISTENT" msgstr "" -#: lxc/list.go:429 +#: lxc/list.go:431 msgid "PID" msgstr "" -#: lxc/list.go:430 +#: lxc/list.go:432 msgid "PROFILES" msgstr "" @@ -923,7 +936,7 @@ msgid "PROTOCOL" msgstr "" -#: lxc/image.go:637 lxc/remote.go:383 +#: lxc/image.go:643 lxc/remote.go:383 msgid "PUBLIC" msgstr "" @@ -952,11 +965,11 @@ msgid "Pid: %d" msgstr "" -#: lxc/network.go:342 lxc/profile.go:218 +#: lxc/network.go:347 lxc/profile.go:219 msgid "Press enter to open the editor again" msgstr "" -#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:732 +#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:738 msgid "Press enter to start the editor again" msgstr "" @@ -987,31 +1000,31 @@ msgid "Processes: %d" msgstr "" -#: lxc/profile.go:274 +#: lxc/profile.go:275 #, c-format msgid "Profile %s added to %s" msgstr "" -#: lxc/profile.go:169 +#: lxc/profile.go:170 #, c-format msgid "Profile %s created" msgstr "" -#: lxc/profile.go:239 +#: lxc/profile.go:240 #, c-format msgid "Profile %s deleted" msgstr "" -#: lxc/profile.go:305 +#: lxc/profile.go:306 #, c-format msgid "Profile %s removed from %s" msgstr "" -#: lxc/copy.go:33 lxc/copy.go:34 lxc/init.go:137 lxc/init.go:138 +#: lxc/copy.go:34 lxc/copy.go:35 lxc/init.go:138 lxc/init.go:139 msgid "Profile to apply to the new container" msgstr "" -#: lxc/profile.go:255 +#: lxc/profile.go:256 #, c-format msgid "Profiles %s applied to %s" msgstr "" @@ -1021,7 +1034,7 @@ msgid "Profiles: %s" msgstr "" -#: lxc/image.go:358 +#: lxc/image.go:364 msgid "Properties:" msgstr "" @@ -1029,12 +1042,12 @@ msgid "Public image server" msgstr "" -#: lxc/image.go:346 +#: lxc/image.go:347 #, c-format msgid "Public: %s" msgstr "" -#: lxc/publish.go:26 +#: lxc/publish.go:27 msgid "Publish containers as images.\n" "\n" "lxc publish [:][/] [:] [--alias=ALIAS...] [prop-key=prop-value...]" @@ -1053,12 +1066,12 @@ msgid "Remote: %s" msgstr "" -#: lxc/delete.go:42 +#: lxc/delete.go:43 #, c-format msgid "Remove %s (yes/no): " msgstr "" -#: lxc/delete.go:36 lxc/delete.go:37 +#: lxc/delete.go:37 lxc/delete.go:38 msgid "Require user confirmation" msgstr "" @@ -1082,20 +1095,20 @@ " lxc restore u1 snap0" msgstr "" -#: lxc/init.go:239 +#: lxc/init.go:240 #, c-format msgid "Retrieving image: %s" msgstr "" -#: lxc/image.go:640 +#: lxc/image.go:646 msgid "SIZE" msgstr "" -#: lxc/list.go:431 +#: lxc/list.go:433 msgid "SNAPSHOTS" msgstr "" -#: lxc/list.go:432 +#: lxc/list.go:434 msgid "STATE" msgstr "" @@ -1139,11 +1152,11 @@ msgid "Show the container's last 100 log lines?" msgstr "" -#: lxc/config.go:32 +#: lxc/config.go:33 msgid "Show the expanded configuration" msgstr "" -#: lxc/image.go:344 +#: lxc/image.go:345 #, c-format msgid "Size: %.2fMB" msgstr "" @@ -1152,11 +1165,11 @@ msgid "Snapshots:" msgstr "" -#: lxc/image.go:368 +#: lxc/image.go:374 msgid "Source:" msgstr "" -#: lxc/launch.go:135 +#: lxc/launch.go:136 #, c-format msgid "Starting %s" msgstr "" @@ -1166,15 +1179,15 @@ msgid "Status: %s" msgstr "" -#: lxc/publish.go:35 lxc/publish.go:36 +#: lxc/publish.go:36 lxc/publish.go:37 msgid "Stop the container if currently running" msgstr "" -#: lxc/delete.go:106 lxc/publish.go:113 +#: lxc/delete.go:107 lxc/publish.go:114 msgid "Stopping container failed!" msgstr "" -#: lxc/action.go:45 +#: lxc/action.go:46 msgid "Store the container state (only for stop)" msgstr "" @@ -1186,27 +1199,27 @@ msgid "Swap (peak)" msgstr "" -#: lxc/list.go:433 lxc/network.go:422 +#: lxc/list.go:435 lxc/network.go:427 msgid "TYPE" msgstr "" -#: lxc/delete.go:92 +#: lxc/delete.go:93 msgid "The container is currently running, stop it first or pass --force." msgstr "" -#: lxc/publish.go:91 +#: lxc/publish.go:92 msgid "The container is currently running. Use --force to have it stopped and restarted." msgstr "" -#: lxc/init.go:312 +#: lxc/init.go:313 msgid "The container you are starting doesn't have any network attached to it." msgstr "" -#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738 lxc/config.go:776 lxc/config.go:794 +#: lxc/config.go:680 lxc/config.go:692 lxc/config.go:725 lxc/config.go:743 lxc/config.go:781 lxc/config.go:799 msgid "The device doesn't exist" msgstr "" -#: lxc/init.go:296 +#: lxc/init.go:297 #, c-format msgid "The local image '%s' couldn't be found, trying '%s:' instead." msgstr "" @@ -1215,31 +1228,31 @@ msgid "The opposite of `lxc pause` is `lxc start`." msgstr "" -#: lxc/network.go:229 lxc/network.go:278 +#: lxc/network.go:230 lxc/network.go:279 msgid "The specified device doesn't exist" msgstr "" -#: lxc/network.go:233 lxc/network.go:282 +#: lxc/network.go:234 lxc/network.go:283 msgid "The specified device doesn't match the network" msgstr "" -#: lxc/publish.go:64 +#: lxc/publish.go:65 msgid "There is no \"image name\". Did you want an alias?" msgstr "" -#: lxc/action.go:41 +#: lxc/action.go:42 msgid "Time to wait for the container before killing it" msgstr "" -#: lxc/image.go:347 +#: lxc/image.go:348 msgid "Timestamps:" msgstr "" -#: lxc/init.go:314 +#: lxc/init.go:315 msgid "To attach a network to a container, use: lxc network attach" msgstr "" -#: lxc/init.go:313 +#: lxc/init.go:314 msgid "To create a new network, use: lxc network create" msgstr "" @@ -1247,12 +1260,12 @@ msgid "To start your first container, try: lxc launch ubuntu:16.04" msgstr "" -#: lxc/image.go:426 +#: lxc/image.go:432 #, c-format msgid "Transferring image: %s" msgstr "" -#: lxc/action.go:99 lxc/launch.go:148 +#: lxc/action.go:100 lxc/launch.go:149 #, c-format msgid "Try `lxc info --show-log %s` for more info" msgstr "" @@ -1265,7 +1278,7 @@ msgid "Type: persistent" msgstr "" -#: lxc/image.go:641 +#: lxc/image.go:647 msgid "UPLOAD DATE" msgstr "" @@ -1273,7 +1286,7 @@ msgid "URL" msgstr "" -#: lxc/network.go:424 lxc/profile.go:447 +#: lxc/network.go:429 lxc/profile.go:452 msgid "USED BY" msgstr "" @@ -1281,7 +1294,7 @@ msgid "Unable to read remote TLS certificate" msgstr "" -#: lxc/image.go:352 +#: lxc/image.go:353 #, c-format msgid "Uploaded: %s" msgstr "" @@ -1295,7 +1308,7 @@ msgid "Usage: lxc [options]" msgstr "" -#: lxc/delete.go:46 +#: lxc/delete.go:47 msgid "User aborted delete operation." msgstr "" @@ -1307,7 +1320,7 @@ msgid "Whether or not to snapshot the container's running state" msgstr "" -#: lxc/network.go:409 lxc/remote.go:356 lxc/remote.go:361 +#: lxc/network.go:414 lxc/remote.go:356 lxc/remote.go:361 msgid "YES" msgstr "" @@ -1315,19 +1328,19 @@ msgid "`lxc config profile` is deprecated, please use `lxc profile`" msgstr "" -#: lxc/launch.go:119 +#: lxc/launch.go:120 msgid "bad number of things scanned from image, container or snapshot" msgstr "" -#: lxc/action.go:95 +#: lxc/action.go:96 msgid "bad result type from action" msgstr "" -#: lxc/copy.go:115 +#: lxc/copy.go:116 msgid "can't copy to the same container name" msgstr "" -#: lxc/file.go:272 +#: lxc/file.go:308 msgid "can't pull a directory without --recursive" msgstr "" @@ -1343,15 +1356,15 @@ msgid "default" msgstr "" -#: lxc/copy.go:131 lxc/copy.go:136 lxc/copy.go:232 lxc/copy.go:237 lxc/init.go:219 lxc/init.go:224 lxc/launch.go:103 lxc/launch.go:108 +#: lxc/copy.go:132 lxc/copy.go:137 lxc/copy.go:233 lxc/copy.go:238 lxc/init.go:220 lxc/init.go:225 lxc/launch.go:104 lxc/launch.go:109 msgid "didn't get any affected image, container or snapshot from server" msgstr "" -#: lxc/image.go:338 +#: lxc/image.go:339 msgid "disabled" msgstr "" -#: lxc/image.go:340 +#: lxc/image.go:341 msgid "enabled" msgstr "" @@ -1365,15 +1378,15 @@ msgid "error: unknown command: %s" msgstr "" -#: lxc/launch.go:123 +#: lxc/launch.go:124 msgid "got bad version" msgstr "" -#: lxc/image.go:333 lxc/image.go:617 +#: lxc/image.go:334 lxc/image.go:623 msgid "no" msgstr "" -#: lxc/copy.go:164 +#: lxc/copy.go:165 msgid "not all the profiles from the source exist on the target" msgstr "" @@ -1386,7 +1399,7 @@ msgid "processing aliases failed %s\n" msgstr "" -#: lxc/file.go:313 +#: lxc/file.go:349 msgid "recursive edit doesn't make sense :(" msgstr "" @@ -1423,7 +1436,7 @@ msgid "taken at %s" msgstr "" -#: lxc/exec.go:185 +#: lxc/exec.go:186 msgid "unreachable return reached" msgstr "" @@ -1431,11 +1444,11 @@ msgid "wrong number of subcommand arguments" msgstr "" -#: lxc/delete.go:45 lxc/image.go:335 lxc/image.go:621 +#: lxc/delete.go:46 lxc/image.go:336 lxc/image.go:627 msgid "yes" msgstr "" -#: lxc/copy.go:44 +#: lxc/copy.go:45 msgid "you must specify a source container name" msgstr "" diff -Nru lxd-2.7/README.md lxd-2.8/README.md --- lxd-2.7/README.md 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/README.md 2017-01-25 02:50:49.000000000 +0000 @@ -6,11 +6,14 @@ To easily see what LXD is about, you can [try it online](https://linuxcontainers.org/lxd/try-it). -## CI status +## Status + +* GoDoc: [![GoDoc](https://godoc.org/github.com/lxc/lxd?status.svg)](https://godoc.org/github.com/lxc/lxd) +* Jenkins (Linux): [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/) +* Travis (macOS): [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd/) +* AppVeyor (Windows): [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd/) +* Weblate (translations): [![Translation status](https://hosted.weblate.org/widgets/linux-containers/-/svg-badge.svg)](https://hosted.weblate.org/projects/linux-containers/lxd/) - * Travis: [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd) - * AppVeyor: [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd) - * Jenkins: [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/) ## Getting started with LXD diff -Nru lxd-2.7/shared/api/certificate.go lxd-2.8/shared/api/certificate.go --- lxd-2.7/shared/api/certificate.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/certificate.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,30 @@ +package api + +// CertificatesPost represents the fields of a new LXD certificate +type CertificatesPost struct { + CertificatePut `yaml:",inline"` + + Certificate string `json:"certificate"` + Password string `json:"password"` +} + +// CertificatePut represents the modifiable fields of a LXD certificate +// +// API extension: certificate_update +type CertificatePut struct { + Name string `json:"name"` + Type string `json:"type"` +} + +// Certificate represents a LXD certificate +type Certificate struct { + CertificatePut `yaml:",inline"` + + Certificate string `json:"certificate"` + Fingerprint string `json:"fingerprint"` +} + +// Writable converts a full Certificate struct into a CertificatePut struct (filters read-only fields) +func (cert *Certificate) Writable() CertificatePut { + return cert.CertificatePut +} diff -Nru lxd-2.7/shared/api/container_exec.go lxd-2.8/shared/api/container_exec.go --- lxd-2.7/shared/api/container_exec.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/container_exec.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,21 @@ +package api + +// ContainerExecControl represents a message on the container exec "control" socket +type ContainerExecControl struct { + Command string `json:"command"` + Args map[string]string `json:"args"` + Signal int `json:"signal"` +} + +// ContainerExecPost represents a LXD container exec request +type ContainerExecPost struct { + Command []string `json:"command"` + WaitForWS bool `json:"wait-for-websocket"` + Interactive bool `json:"interactive"` + Environment map[string]string `json:"environment"` + Width int `json:"width"` + Height int `json:"height"` + + // API extension: container_exec_recording + RecordOutput bool `json:"record-output"` +} diff -Nru lxd-2.7/shared/api/container.go lxd-2.8/shared/api/container.go --- lxd-2.7/shared/api/container.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/container.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,90 @@ +package api + +import ( + "time" +) + +// ContainersPost represents the fields available for a new LXD container +type ContainersPost struct { + ContainerPut `yaml:",inline"` + + Name string `json:"name"` + Source ContainerSource `json:"source"` +} + +// ContainerPost represents the fields required to rename/move a LXD container +type ContainerPost struct { + Migration bool `json:"migration"` + Name string `json:"name"` +} + +// ContainerPut represents the modifiable fields of a LXD container +type ContainerPut struct { + Architecture string `json:"architecture"` + Config map[string]string `json:"config"` + Devices map[string]map[string]string `json:"devices"` + Ephemeral bool `json:"ephemeral"` + Profiles []string `json:"profiles"` + Restore string `json:"restore,omitempty" yaml:"restore,omitempty"` +} + +// Container represents a LXD container +type Container struct { + ContainerPut `yaml:",inline"` + + CreatedAt time.Time `json:"created_at"` + ExpandedConfig map[string]string `json:"expanded_config"` + ExpandedDevices map[string]map[string]string `json:"expanded_devices"` + Name string `json:"name"` + Stateful bool `json:"stateful"` + Status string `json:"status"` + StatusCode StatusCode `json:"status_code"` + + // API extension: container_last_used_at + LastUsedAt time.Time `json:"last_used_at"` +} + +// Writable converts a full Container struct into a ContainerPut struct (filters read-only fields) +func (c *Container) Writable() ContainerPut { + return c.ContainerPut +} + +// IsActive checks whether the container state indicates the container is active +func (c Container) IsActive() bool { + switch c.StatusCode { + case Stopped: + return false + case Error: + return false + default: + return true + } +} + +// ContainerSource represents the creation source for a new container +type ContainerSource struct { + Type string `json:"type"` + Certificate string `json:"certificate"` + + // For "image" type + Alias string `json:"alias,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + Properties map[string]string `json:"properties,omitempty"` + Server string `json:"server,omitempty"` + Secret string `json:"secret,omitempty"` + Protocol string `json:"protocol,omitempty"` + + // For "migration" and "copy" types + BaseImage string `json:"base-image,omitempty"` + + // For "migration" type + Mode string `json:"mode,omitempty"` + Operation string `json:"operation,omitempty"` + Websockets map[string]string `json:"secrets,omitempty"` + + // API extension: container_push + Live bool `json:"live,omitempty"` + + // For "copy" type + Source string `json:"source,omitempty"` +} diff -Nru lxd-2.7/shared/api/container_snapshot.go lxd-2.8/shared/api/container_snapshot.go --- lxd-2.7/shared/api/container_snapshot.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/container_snapshot.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,32 @@ +package api + +import ( + "time" +) + +// ContainerSnapshotsPost represents the fields available for a new LXD container snapshot +type ContainerSnapshotsPost struct { + Name string `json:"name"` + Stateful bool `json:"stateful"` +} + +// ContainerSnapshotPost represents the fields required to rename/move a LXD container snapshot +type ContainerSnapshotPost struct { + Name string `json:"name"` + Migration bool `json:"migration"` +} + +// ContainerSnapshot represents a LXD conainer snapshot +type ContainerSnapshot struct { + Architecture string `json:"architecture"` + Config map[string]string `json:"config"` + CreationDate time.Time `json:"created_at"` + Devices map[string]map[string]string `json:"devices"` + Ephemeral bool `json:"ephemeral"` + ExpandedConfig map[string]string `json:"expanded_config"` + ExpandedDevices map[string]map[string]string `json:"expanded_devices"` + LastUsedDate time.Time `json:"last_used_at"` + Name string `json:"name"` + Profiles []string `json:"profiles"` + Stateful bool `json:"stateful"` +} diff -Nru lxd-2.7/shared/api/container_state.go lxd-2.8/shared/api/container_state.go --- lxd-2.7/shared/api/container_state.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/container_state.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,70 @@ +package api + +// ContainerStatePut represents the modifiable fields of a LXD container's state +type ContainerStatePut struct { + Action string `json:"action"` + Timeout int `json:"timeout"` + Force bool `json:"force"` + Stateful bool `json:"stateful"` +} + +// ContainerState represents a LXD container's state +type ContainerState struct { + Status string `json:"status"` + StatusCode StatusCode `json:"status_code"` + Disk map[string]ContainerStateDisk `json:"disk"` + Memory ContainerStateMemory `json:"memory"` + Network map[string]ContainerStateNetwork `json:"network"` + Pid int64 `json:"pid"` + Processes int64 `json:"processes"` + + // API extension: container_cpu_time + CPU ContainerStateCPU `json:"cpu"` +} + +// ContainerStateDisk represents the disk information section of a LXD container's state +type ContainerStateDisk struct { + Usage int64 `json:"usage"` +} + +// ContainerStateCPU represents the cpu information section of a LXD container's state +// +// API extension: container_cpu_time +type ContainerStateCPU struct { + Usage int64 `json:"usage"` +} + +// ContainerStateMemory represents the memory information section of a LXD container's state +type ContainerStateMemory struct { + Usage int64 `json:"usage"` + UsagePeak int64 `json:"usage_peak"` + SwapUsage int64 `json:"swap_usage"` + SwapUsagePeak int64 `json:"swap_usage_peak"` +} + +// ContainerStateNetwork represents the network information section of a LXD container's state +type ContainerStateNetwork struct { + Addresses []ContainerStateNetworkAddress `json:"addresses"` + Counters ContainerStateNetworkCounters `json:"counters"` + Hwaddr string `json:"hwaddr"` + HostName string `json:"host_name"` + Mtu int `json:"mtu"` + State string `json:"state"` + Type string `json:"type"` +} + +// ContainerStateNetworkAddress represents a network address as part of the network section of a LXD container's state +type ContainerStateNetworkAddress struct { + Family string `json:"family"` + Address string `json:"address"` + Netmask string `json:"netmask"` + Scope string `json:"scope"` +} + +// ContainerStateNetworkCounters represents packet counters as part of the network section of a LXD container's state +type ContainerStateNetworkCounters struct { + BytesReceived int64 `json:"bytes_received"` + BytesSent int64 `json:"bytes_sent"` + PacketsReceived int64 `json:"packets_received"` + PacketsSent int64 `json:"packets_sent"` +} diff -Nru lxd-2.7/shared/api/doc.go lxd-2.8/shared/api/doc.go --- lxd-2.7/shared/api/doc.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/doc.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,13 @@ +// Package api contains Go structs for all LXD API objects +// +// Overview +// +// This package has Go structs for every API object, all the various +// structs are named after the object they represent and some variations of +// those structs exist for initial object creation, object update and +// object retrieval. +// +// A few convenience functions are also tied to those structs which let +// you convert between the various strucs for a given object and also query +// some of the more complex metadata that LXD can export. +package api diff -Nru lxd-2.7/shared/api/image.go lxd-2.8/shared/api/image.go --- lxd-2.7/shared/api/image.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/image.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,83 @@ +package api + +import ( + "time" +) + +// ImagesPost represents the fields available for a new LXD image +type ImagesPost struct { + ImagePut `yaml:",inline"` + + Filename string `json:"filename"` + Source map[string]string `json:"source"` + + // API extension: image_compression_algorithm + CompressionAlgorithm string `json:"compression_algorithm"` +} + +// ImagePut represents the modifiable fields of a LXD image +type ImagePut struct { + AutoUpdate bool `json:"auto_update"` + Properties map[string]string `json:"properties"` + Public bool `json:"public"` +} + +// Image represents a LXD image +type Image struct { + ImagePut `yaml:",inline"` + + Aliases []ImageAlias `json:"aliases"` + Architecture string `json:"architecture"` + Cached bool `json:"cached"` + Filename string `json:"filename"` + Fingerprint string `json:"fingerprint"` + Size int64 `json:"size"` + UpdateSource *ImageSource `json:"update_source,omitempty"` + + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + LastUsedAt time.Time `json:"last_used_at"` + UploadedAt time.Time `json:"uploaded_at"` +} + +// Writable converts a full Image struct into a ImagePut struct (filters read-only fields) +func (img *Image) Writable() ImagePut { + return img.ImagePut +} + +// ImageAlias represents an alias from the alias list of a LXD image +type ImageAlias struct { + Name string `json:"name"` + Description string `json:"description"` +} + +// ImageSource represents the source of a LXD image +type ImageSource struct { + Alias string `json:"alias"` + Certificate string `json:"certificate"` + Protocol string `json:"protocol"` + Server string `json:"server"` +} + +// ImageAliasesPost represents a new LXD image alias +type ImageAliasesPost struct { + ImageAliasesEntry `yaml:",inline"` +} + +// ImageAliasesEntryPost represents the required fields to rename a LXD image alias +type ImageAliasesEntryPost struct { + Name string `json:"name"` +} + +// ImageAliasesEntryPut represents the modifiable fields of a LXD image alias +type ImageAliasesEntryPut struct { + Description string `json:"description"` + Target string `json:"target"` +} + +// ImageAliasesEntry represents a LXD image alias +type ImageAliasesEntry struct { + ImageAliasesEntryPut `yaml:",inline"` + + Name string `json:"name"` +} diff -Nru lxd-2.7/shared/api/network.go lxd-2.8/shared/api/network.go --- lxd-2.7/shared/api/network.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/network.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,43 @@ +package api + +// NetworksPost represents the fields of a new LXD network +// +// API extension: network +type NetworksPost struct { + NetworkPut `yaml:",inline"` + + Managed bool `json:"managed"` + Name string `json:"name"` + Type string `json:"type"` +} + +// NetworkPost represents the fields required to rename a LXD network +// +// API extension: network +type NetworkPost struct { + Name string `json:"name"` +} + +// NetworkPut represents the modifiable fields of a LXD network +// +// API extension: network +type NetworkPut struct { + Config map[string]string `json:"config"` +} + +// Network represents a LXD network +type Network struct { + NetworkPut `yaml:",inline"` + + Name string `json:"name"` + Type string `json:"type"` + UsedBy []string `json:"used_by"` + + // API extension: network + Managed bool `json:"managed"` +} + +// Writable converts a full Network struct into a NetworkPut struct (filters read-only fields) +func (network *Network) Writable() NetworkPut { + return network.NetworkPut +} diff -Nru lxd-2.7/shared/api/operation.go lxd-2.8/shared/api/operation.go --- lxd-2.7/shared/api/operation.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/operation.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,19 @@ +package api + +import ( + "time" +) + +// Operation represents a LXD background operation +type Operation struct { + ID string `json:"id"` + Class string `json:"class"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Status string `json:"status"` + StatusCode StatusCode `json:"status_code"` + Resources map[string][]string `json:"resources"` + Metadata map[string]interface{} `json:"metadata"` + MayCancel bool `json:"may_cancel"` + Err string `json:"err"` +} diff -Nru lxd-2.7/shared/api/profile.go lxd-2.8/shared/api/profile.go --- lxd-2.7/shared/api/profile.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/profile.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,35 @@ +package api + +// ProfilesPost represents the fields of a new LXD profile +type ProfilesPost struct { + ProfilePut `yaml:",inline"` + + Name string `json:"name"` +} + +// ProfilePost represents the fields required to rename a LXD profile +type ProfilePost struct { + Name string `json:"name"` +} + +// ProfilePut represents the modifiable fields of a LXD profile +type ProfilePut struct { + Config map[string]string `json:"config"` + Description string `json:"description"` + Devices map[string]map[string]string `json:"devices"` +} + +// Profile represents a LXD profile +type Profile struct { + ProfilePut `yaml:",inline"` + + Name string `json:"name"` + + // API extension: profile_usedby + UsedBy []string `json:"used_by"` +} + +// Writable converts a full Profile struct into a ProfilePut struct (filters read-only fields) +func (profile *Profile) Writable() ProfilePut { + return profile.ProfilePut +} diff -Nru lxd-2.7/shared/api/response.go lxd-2.8/shared/api/response.go --- lxd-2.7/shared/api/response.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/response.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,83 @@ +package api + +import ( + "encoding/json" +) + +// ResponseRaw represents a LXD operation in its original form +type ResponseRaw struct { + Response `yaml:",inline"` + + Metadata interface{} `json:"metadata"` +} + +// Response represents a LXD operation +type Response struct { + Type ResponseType `json:"type"` + + // Valid only for Sync responses + Status string `json:"status"` + StatusCode int `json:"status_code"` + + // Valid only for Async responses + Operation string `json:"operation"` + + // Valid only for Error responses + Code int `json:"error_code"` + Error string `json:"error"` + + // Valid for Sync and Error responses + Metadata json.RawMessage `json:"metadata"` +} + +// MetadataAsMap parses the Response metadata into a map +func (r *Response) MetadataAsMap() (map[string]interface{}, error) { + ret := map[string]interface{}{} + err := r.MetadataAsStruct(&ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +// MetadataAsOperation turns the Response metadata into an Operation +func (r *Response) MetadataAsOperation() (*Operation, error) { + op := Operation{} + err := r.MetadataAsStruct(&op) + if err != nil { + return nil, err + } + + return &op, nil +} + +// MetadataAsStringSlice parses the Response metadata into a slice of string +func (r *Response) MetadataAsStringSlice() ([]string, error) { + sl := []string{} + err := r.MetadataAsStruct(&sl) + if err != nil { + return nil, err + } + + return sl, nil +} + +// MetadataAsStruct parses the Response metadata into a provided struct +func (r *Response) MetadataAsStruct(target interface{}) error { + if err := json.Unmarshal(r.Metadata, &target); err != nil { + return err + } + + return nil +} + +// ResponseType represents a valid LXD response type +type ResponseType string + +// LXD response types +const ( + SyncResponse ResponseType = "sync" + AsyncResponse ResponseType = "async" + ErrorResponse ResponseType = "error" +) diff -Nru lxd-2.7/shared/api/server.go lxd-2.8/shared/api/server.go --- lxd-2.7/shared/api/server.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/server.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,46 @@ +package api + +// ServerEnvironment represents the read-only environment fields of a LXD server +type ServerEnvironment struct { + Addresses []string `json:"addresses"` + Architectures []string `json:"architectures"` + Certificate string `json:"certificate"` + CertificateFingerprint string `json:"certificate_fingerprint"` + Driver string `json:"driver"` + DriverVersion string `json:"driver_version"` + Kernel string `json:"kernel"` + KernelArchitecture string `json:"kernel_architecture"` + KernelVersion string `json:"kernel_version"` + Server string `json:"server"` + ServerPid int `json:"server_pid"` + ServerVersion string `json:"server_version"` + Storage string `json:"storage"` + StorageVersion string `json:"storage_version"` +} + +// ServerPut represents the modifiable fields of a LXD server configuration +type ServerPut struct { + Config map[string]interface{} `json:"config"` +} + +// ServerUntrusted represents a LXD server for an untrusted client +type ServerUntrusted struct { + APIExtensions []string `json:"api_extensions"` + APIStatus string `json:"api_status"` + APIVersion string `json:"api_version"` + Auth string `json:"auth"` + Public bool `json:"public"` +} + +// Server represents a LXD server +type Server struct { + ServerPut `yaml:",inline"` + ServerUntrusted `yaml:",inline"` + + Environment ServerEnvironment `json:"environment"` +} + +// Writable converts a full Server struct into a ServerPut struct (filters read-only fields) +func (srv *Server) Writable() ServerPut { + return srv.ServerPut +} diff -Nru lxd-2.7/shared/api/status_code.go lxd-2.8/shared/api/status_code.go --- lxd-2.7/shared/api/status_code.go 1970-01-01 00:00:00.000000000 +0000 +++ lxd-2.8/shared/api/status_code.go 2017-01-25 02:50:49.000000000 +0000 @@ -0,0 +1,53 @@ +package api + +// StatusCode represents a valid LXD operation and container status +type StatusCode int + +// LXD status codes +const ( + OperationCreated StatusCode = 100 + Started StatusCode = 101 + Stopped StatusCode = 102 + Running StatusCode = 103 + Cancelling StatusCode = 104 + Pending StatusCode = 105 + Starting StatusCode = 106 + Stopping StatusCode = 107 + Aborting StatusCode = 108 + Freezing StatusCode = 109 + Frozen StatusCode = 110 + Thawed StatusCode = 111 + Error StatusCode = 112 + + Success StatusCode = 200 + + Failure StatusCode = 400 + Cancelled StatusCode = 401 +) + +// String returns a suitable string representation for the status code +func (o StatusCode) String() string { + return map[StatusCode]string{ + OperationCreated: "Operation created", + Started: "Started", + Stopped: "Stopped", + Running: "Running", + Cancelling: "Cancelling", + Pending: "Pending", + Success: "Success", + Failure: "Failure", + Cancelled: "Cancelled", + Starting: "Starting", + Stopping: "Stopping", + Aborting: "Aborting", + Freezing: "Freezing", + Frozen: "Frozen", + Thawed: "Thawed", + Error: "Error", + }[o] +} + +// IsFinal will return true if the status code indicates an end state +func (o StatusCode) IsFinal() bool { + return int(o) >= 200 +} diff -Nru lxd-2.7/shared/cert.go lxd-2.8/shared/cert.go --- lxd-2.7/shared/cert.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/cert.go 2017-01-25 02:50:49.000000000 +0000 @@ -23,14 +23,6 @@ "time" ) -// CertInfo is the representation of a Certificate in the API. -type CertInfo struct { - Certificate string `json:"certificate"` - Fingerprint string `json:"fingerprint"` - Name string `json:"name"` - Type string `json:"type"` -} - /* * Generate a list of names for which the certificate will be valid. * This will include the hostname and ip address diff -Nru lxd-2.7/shared/container.go lxd-2.8/shared/container.go --- lxd-2.7/shared/container.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/container.go 2017-01-25 02:50:49.000000000 +0000 @@ -4,137 +4,8 @@ "fmt" "strconv" "strings" - "syscall" - "time" ) -type ContainerState struct { - Status string `json:"status"` - StatusCode StatusCode `json:"status_code"` - CPU ContainerStateCPU `json:"cpu"` - Disk map[string]ContainerStateDisk `json:"disk"` - Memory ContainerStateMemory `json:"memory"` - Network map[string]ContainerStateNetwork `json:"network"` - Pid int64 `json:"pid"` - Processes int64 `json:"processes"` -} - -type ContainerStateDisk struct { - Usage int64 `json:"usage"` -} - -type ContainerStateCPU struct { - Usage int64 `json:"usage"` -} - -type ContainerStateMemory struct { - Usage int64 `json:"usage"` - UsagePeak int64 `json:"usage_peak"` - SwapUsage int64 `json:"swap_usage"` - SwapUsagePeak int64 `json:"swap_usage_peak"` -} - -type ContainerStateNetwork struct { - Addresses []ContainerStateNetworkAddress `json:"addresses"` - Counters ContainerStateNetworkCounters `json:"counters"` - Hwaddr string `json:"hwaddr"` - HostName string `json:"host_name"` - Mtu int `json:"mtu"` - State string `json:"state"` - Type string `json:"type"` -} - -type ContainerStateNetworkAddress struct { - Family string `json:"family"` - Address string `json:"address"` - Netmask string `json:"netmask"` - Scope string `json:"scope"` -} - -type ContainerStateNetworkCounters struct { - BytesReceived int64 `json:"bytes_received"` - BytesSent int64 `json:"bytes_sent"` - PacketsReceived int64 `json:"packets_received"` - PacketsSent int64 `json:"packets_sent"` -} - -type ContainerExecControl struct { - Command string `json:"command"` - Args map[string]string `json:"args"` - Signal syscall.Signal `json:"signal"` -} - -type SnapshotInfo struct { - Architecture string `json:"architecture"` - Config map[string]string `json:"config"` - CreationDate time.Time `json:"created_at"` - Devices Devices `json:"devices"` - Ephemeral bool `json:"ephemeral"` - ExpandedConfig map[string]string `json:"expanded_config"` - ExpandedDevices Devices `json:"expanded_devices"` - LastUsedDate time.Time `json:"last_used_at"` - Name string `json:"name"` - Profiles []string `json:"profiles"` - Stateful bool `json:"stateful"` -} - -type ContainerInfo struct { - Architecture string `json:"architecture"` - Config map[string]string `json:"config"` - CreationDate time.Time `json:"created_at"` - Devices Devices `json:"devices"` - Ephemeral bool `json:"ephemeral"` - ExpandedConfig map[string]string `json:"expanded_config"` - ExpandedDevices Devices `json:"expanded_devices"` - LastUsedDate time.Time `json:"last_used_at"` - Name string `json:"name"` - Profiles []string `json:"profiles"` - Stateful bool `json:"stateful"` - Status string `json:"status"` - StatusCode StatusCode `json:"status_code"` -} - -func (c ContainerInfo) IsActive() bool { - switch c.StatusCode { - case Stopped: - return false - case Error: - return false - default: - return true - } -} - -/* - * BriefContainerState contains a subset of the fields in - * ContainerState, namely those which a user may update - */ -type BriefContainerInfo struct { - Name string `json:"name"` - Profiles []string `json:"profiles"` - Config map[string]string `json:"config"` - Devices Devices `json:"devices"` - Ephemeral bool `json:"ephemeral"` -} - -func (c *ContainerInfo) Brief() BriefContainerInfo { - retstate := BriefContainerInfo{Name: c.Name, - Profiles: c.Profiles, - Config: c.Config, - Devices: c.Devices, - Ephemeral: c.Ephemeral} - return retstate -} - -func (c *ContainerInfo) BriefExpanded() BriefContainerInfo { - retstate := BriefContainerInfo{Name: c.Name, - Profiles: c.Profiles, - Config: c.ExpandedConfig, - Devices: c.ExpandedDevices, - Ephemeral: c.Ephemeral} - return retstate -} - type ContainerAction string const ( @@ -145,22 +16,6 @@ Unfreeze ContainerAction = "unfreeze" ) -type ProfileConfig struct { - Name string `json:"name"` - Config map[string]string `json:"config"` - Description string `json:"description"` - Devices Devices `json:"devices"` - UsedBy []string `json:"used_by"` -} - -type NetworkConfig struct { - Name string `json:"name"` - Config map[string]string `json:"config"` - Managed bool `json:"managed"` - Type string `json:"type"` - UsedBy []string `json:"used_by"` -} - func IsInt64(value string) error { if value == "" { return nil diff -Nru lxd-2.7/shared/devices.go lxd-2.8/shared/devices.go --- lxd-2.7/shared/devices.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/devices.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,164 +0,0 @@ -package shared - -import ( - "sort" -) - -type Device map[string]string -type Devices map[string]Device - -func (list Devices) ContainsName(k string) bool { - if list[k] != nil { - return true - } - return false -} - -func (d Device) get(key string) string { - return d[key] -} - -func (list Devices) Contains(k string, d Device) bool { - // If it didn't exist, it's different - if list[k] == nil { - return false - } - - old := list[k] - - return deviceEquals(old, d) -} - -func deviceEquals(old Device, d Device) bool { - // Check for any difference and addition/removal of properties - for k, _ := range d { - if d[k] != old[k] { - return false - } - } - - for k, _ := range old { - if d[k] != old[k] { - return false - } - } - - return true -} - -func (old Devices) Update(newlist Devices) (map[string]Device, map[string]Device, map[string]Device) { - rmlist := map[string]Device{} - addlist := map[string]Device{} - updatelist := map[string]Device{} - - for key, d := range old { - if !newlist.Contains(key, d) { - rmlist[key] = d - } - } - - for key, d := range newlist { - if !old.Contains(key, d) { - addlist[key] = d - } - } - - for key, d := range addlist { - srcOldDevice := rmlist[key] - var oldDevice Device - err := DeepCopy(&srcOldDevice, &oldDevice) - if err != nil { - continue - } - - srcNewDevice := newlist[key] - var newDevice Device - err = DeepCopy(&srcNewDevice, &newDevice) - if err != nil { - continue - } - - for _, k := range []string{"limits.max", "limits.read", "limits.write", "limits.egress", "limits.ingress", "ipv4.address", "ipv6.address"} { - delete(oldDevice, k) - delete(newDevice, k) - } - - if deviceEquals(oldDevice, newDevice) { - delete(rmlist, key) - delete(addlist, key) - updatelist[key] = d - } - } - - return rmlist, addlist, updatelist -} - -func (newBaseDevices Devices) ExtendFromProfile(currentFullDevices Devices, newDevicesFromProfile Devices) error { - // For any entry which exists in a profile and doesn't in the container config, add it - - for name, newDev := range newDevicesFromProfile { - if curDev, ok := currentFullDevices[name]; ok { - newBaseDevices[name] = curDev - } else { - newBaseDevices[name] = newDev - } - } - - return nil -} - -type namedDevice struct { - name string - device Device -} -type sortableDevices []namedDevice - -func (devices Devices) toSortable() sortableDevices { - named := []namedDevice{} - for k, d := range devices { - named = append(named, namedDevice{k, d}) - } - - return named -} - -func (devices sortableDevices) Len() int { - return len(devices) -} - -func (devices sortableDevices) Less(i, j int) bool { - a := devices[i] - b := devices[j] - - if a.device["type"] == "disk" && b.device["type"] == "disk" { - if a.device["path"] == b.device["path"] { - return a.name < b.name - } - - return a.device["path"] < b.device["path"] - } - - return a.name < b.name -} - -func (devices sortableDevices) Swap(i, j int) { - tmp := devices[i] - devices[i] = devices[j] - devices[j] = tmp -} - -func (devices sortableDevices) Names() []string { - result := []string{} - for _, d := range devices { - result = append(result, d.name) - } - - return result -} - -/* DeviceNames returns the device names for this Devices in sorted order */ -func (devices Devices) DeviceNames() []string { - sortable := devices.toSortable() - sort.Sort(sortable) - return sortable.Names() -} diff -Nru lxd-2.7/shared/devices_test.go lxd-2.8/shared/devices_test.go --- lxd-2.7/shared/devices_test.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/devices_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -package shared - -import ( - "reflect" - "testing" -) - -func TestSortableDevices(t *testing.T) { - devices := Devices{ - "1": Device{"type": "nic"}, - "3": Device{"type": "disk", "path": "/foo/bar"}, - "4": Device{"type": "disk", "path": "/foo"}, - "2": Device{"type": "nic"}, - } - - expected := []string{"1", "2", "4", "3"} - - result := devices.DeviceNames() - if !reflect.DeepEqual(result, expected) { - t.Error("devices sorted incorrectly") - } -} diff -Nru lxd-2.7/shared/idmapset_linux_test.go lxd-2.8/shared/idmapset_linux_test.go --- lxd-2.7/shared/idmapset_linux_test.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/idmapset_linux_test.go 2017-01-25 02:50:49.000000000 +0000 @@ -6,7 +6,7 @@ ) func TestIdmapSetAddSafe_split(t *testing.T) { - orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}} + orig := IdmapSet{Idmap: []IdmapEntry{{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}} if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 500, Maprange: 10}); err != nil { t.Error(err) @@ -35,7 +35,7 @@ } func TestIdmapSetAddSafe_lower(t *testing.T) { - orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}} + orig := IdmapSet{Idmap: []IdmapEntry{{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}} if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 0, Maprange: 10}); err != nil { t.Error(err) @@ -59,7 +59,7 @@ } func TestIdmapSetAddSafe_upper(t *testing.T) { - orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}} + orig := IdmapSet{Idmap: []IdmapEntry{{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}} if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 995, Maprange: 10}); err != nil { t.Error(err) @@ -83,7 +83,7 @@ } func TestIdmapSetIntersects(t *testing.T) { - orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 165536, Nsid: 0, Maprange: 65536}}} + orig := IdmapSet{Idmap: []IdmapEntry{{Isuid: true, Hostid: 165536, Nsid: 0, Maprange: 65536}}} if !orig.Intersects(IdmapEntry{Isuid: true, Hostid: 231071, Nsid: 0, Maprange: 65536}) { t.Error("ranges don't intersect") diff -Nru lxd-2.7/shared/image.go lxd-2.8/shared/image.go --- lxd-2.7/shared/image.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/image.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ -package shared - -import ( - "time" -) - -type ImageProperties map[string]string - -type ImageAliasesEntry struct { - Name string `json:"name"` - Description string `json:"description"` - Target string `json:"target"` -} - -type ImageAliases []ImageAliasesEntry - -type ImageAlias struct { - Name string `json:"name"` - Description string `json:"description"` -} - -type ImageSource struct { - Server string `json:"server"` - Protocol string `json:"protocol"` - Certificate string `json:"certificate"` - Alias string `json:"alias"` -} - -type ImageInfo struct { - Aliases []ImageAlias `json:"aliases"` - Architecture string `json:"architecture"` - Cached bool `json:"cached"` - Filename string `json:"filename"` - Fingerprint string `json:"fingerprint"` - Properties map[string]string `json:"properties"` - Public bool `json:"public"` - Size int64 `json:"size"` - - AutoUpdate bool `json:"auto_update"` - Source *ImageSource `json:"update_source,omitempty"` - - CreationDate time.Time `json:"created_at"` - ExpiryDate time.Time `json:"expires_at"` - LastUsedDate time.Time `json:"last_used_at"` - UploadDate time.Time `json:"uploaded_at"` -} - -/* - * BriefImageInfo contains a subset of the fields in - * ImageInfo, namely those which a user may update - */ -type BriefImageInfo struct { - AutoUpdate bool `json:"auto_update"` - Properties map[string]string `json:"properties"` - Public bool `json:"public"` -} - -func (i *ImageInfo) Brief() BriefImageInfo { - retstate := BriefImageInfo{ - AutoUpdate: i.AutoUpdate, - Properties: i.Properties, - Public: i.Public} - return retstate -} diff -Nru lxd-2.7/shared/operation.go lxd-2.8/shared/operation.go --- lxd-2.7/shared/operation.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/operation.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ -package shared - -import ( - "time" -) - -type Operation struct { - Id string `json:"id"` - Class string `json:"class"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Status string `json:"status"` - StatusCode StatusCode `json:"status_code"` - Resources map[string][]string `json:"resources"` - Metadata *Jmap `json:"metadata"` - MayCancel bool `json:"may_cancel"` - Err string `json:"err"` -} diff -Nru lxd-2.7/shared/osarch/architectures.go lxd-2.8/shared/osarch/architectures.go --- lxd-2.7/shared/osarch/architectures.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/osarch/architectures.go 2017-01-25 02:50:49.000000000 +0000 @@ -28,13 +28,13 @@ } var architectureAliases = map[int][]string{ - ARCH_32BIT_INTEL_X86: []string{"i386"}, - ARCH_64BIT_INTEL_X86: []string{"amd64"}, - ARCH_32BIT_ARMV7_LITTLE_ENDIAN: []string{"armel", "armhf"}, - ARCH_64BIT_ARMV8_LITTLE_ENDIAN: []string{"arm64"}, - ARCH_32BIT_POWERPC_BIG_ENDIAN: []string{"powerpc"}, - ARCH_64BIT_POWERPC_BIG_ENDIAN: []string{"powerpc64"}, - ARCH_64BIT_POWERPC_LITTLE_ENDIAN: []string{"ppc64el"}, + ARCH_32BIT_INTEL_X86: {"i386"}, + ARCH_64BIT_INTEL_X86: {"amd64"}, + ARCH_32BIT_ARMV7_LITTLE_ENDIAN: {"armel", "armhf"}, + ARCH_64BIT_ARMV8_LITTLE_ENDIAN: {"arm64"}, + ARCH_32BIT_POWERPC_BIG_ENDIAN: {"powerpc"}, + ARCH_64BIT_POWERPC_BIG_ENDIAN: {"powerpc64"}, + ARCH_64BIT_POWERPC_LITTLE_ENDIAN: {"ppc64el"}, } var architecturePersonalities = map[int]string{ @@ -49,14 +49,14 @@ } var architectureSupportedPersonalities = map[int][]int{ - ARCH_32BIT_INTEL_X86: []int{}, - ARCH_64BIT_INTEL_X86: []int{ARCH_32BIT_INTEL_X86}, - ARCH_32BIT_ARMV7_LITTLE_ENDIAN: []int{}, - ARCH_64BIT_ARMV8_LITTLE_ENDIAN: []int{ARCH_32BIT_ARMV7_LITTLE_ENDIAN}, - ARCH_32BIT_POWERPC_BIG_ENDIAN: []int{}, - ARCH_64BIT_POWERPC_BIG_ENDIAN: []int{ARCH_32BIT_POWERPC_BIG_ENDIAN}, - ARCH_64BIT_POWERPC_LITTLE_ENDIAN: []int{}, - ARCH_64BIT_S390_BIG_ENDIAN: []int{}, + ARCH_32BIT_INTEL_X86: {}, + ARCH_64BIT_INTEL_X86: {ARCH_32BIT_INTEL_X86}, + ARCH_32BIT_ARMV7_LITTLE_ENDIAN: {}, + ARCH_64BIT_ARMV8_LITTLE_ENDIAN: {ARCH_32BIT_ARMV7_LITTLE_ENDIAN}, + ARCH_32BIT_POWERPC_BIG_ENDIAN: {}, + ARCH_64BIT_POWERPC_BIG_ENDIAN: {ARCH_32BIT_POWERPC_BIG_ENDIAN}, + ARCH_64BIT_POWERPC_LITTLE_ENDIAN: {}, + ARCH_64BIT_S390_BIG_ENDIAN: {}, } const ArchitectureDefault = "x86_64" diff -Nru lxd-2.7/shared/server.go lxd-2.8/shared/server.go --- lxd-2.7/shared/server.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/server.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -package shared - -type ServerStateEnvironment struct { - Addresses []string `json:"addresses"` - Architectures []string `json:"architectures"` - Certificate string `json:"certificate"` - CertificateFingerprint string `json:"certificate_fingerprint"` - Driver string `json:"driver"` - DriverVersion string `json:"driver_version"` - Kernel string `json:"kernel"` - KernelArchitecture string `json:"kernel_architecture"` - KernelVersion string `json:"kernel_version"` - Server string `json:"server"` - ServerPid int `json:"server_pid"` - ServerVersion string `json:"server_version"` - Storage string `json:"storage"` - StorageVersion string `json:"storage_version"` -} - -type ServerState struct { - APIExtensions []string `json:"api_extensions"` - APIStatus string `json:"api_status"` - APIVersion string `json:"api_version"` - Auth string `json:"auth"` - Environment ServerStateEnvironment `json:"environment"` - Config map[string]interface{} `json:"config"` - Public bool `json:"public"` -} - -type BriefServerState struct { - Config map[string]interface{} `json:"config"` -} - -func (c *ServerState) Brief() BriefServerState { - retstate := BriefServerState{Config: c.Config} - return retstate -} diff -Nru lxd-2.7/shared/simplestreams/simplestreams.go lxd-2.8/shared/simplestreams/simplestreams.go --- lxd-2.7/shared/simplestreams/simplestreams.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/simplestreams/simplestreams.go 2017-01-25 02:50:49.000000000 +0000 @@ -14,11 +14,12 @@ "time" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/ioprogress" "github.com/lxc/lxd/shared/osarch" ) -type ssSortImage []shared.ImageInfo +type ssSortImage []api.Image func (a ssSortImage) Len() int { return len(a) @@ -31,15 +32,15 @@ func (a ssSortImage) Less(i, j int) bool { if a[i].Properties["os"] == a[j].Properties["os"] { if a[i].Properties["release"] == a[j].Properties["release"] { - if a[i].CreationDate.UTC().Unix() == 0 { + if !shared.TimeIsSet(a[i].CreatedAt) { return true } - if a[j].CreationDate.UTC().Unix() == 0 { + if !shared.TimeIsSet(a[j].CreatedAt) { return false } - return a[i].CreationDate.UTC().Unix() > a[j].CreationDate.UTC().Unix() + return a[i].CreatedAt.UTC().Unix() > a[j].CreatedAt.UTC().Unix() } if a[i].Properties["release"] == "" { @@ -76,10 +77,10 @@ Products map[string]SimpleStreamsManifestProduct `json:"products"` } -func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]string) { +func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) { downloads := map[string][][]string{} - images := []shared.ImageInfo{} + images := []api.Image{} nameLayout := "20060102" eolLayout := "2006-01-02" @@ -169,12 +170,12 @@ } description = fmt.Sprintf("%s (%s)", description, name) - image := shared.ImageInfo{} + image := api.Image{} image.Architecture = architectureName image.Public = true image.Size = size - image.CreationDate = creationDate - image.UploadDate = creationDate + image.CreatedAt = creationDate + image.UploadedAt = creationDate image.Filename = filename image.Fingerprint = fingerprint image.Properties = map[string]string{ @@ -189,9 +190,9 @@ // Add the provided aliases if product.Aliases != "" { - image.Aliases = []shared.ImageAlias{} + image.Aliases = []api.ImageAlias{} for _, entry := range strings.Split(product.Aliases, ",") { - image.Aliases = append(image.Aliases, shared.ImageAlias{Name: entry}) + image.Aliases = append(image.Aliases, api.ImageAlias{Name: entry}) } } @@ -203,15 +204,15 @@ } // Attempt to parse the EOL - image.ExpiryDate = time.Unix(0, 0).UTC() + image.ExpiresAt = time.Unix(0, 0).UTC() if product.SupportedEOL != "" { eolDate, err := time.Parse(eolLayout, product.SupportedEOL) if err == nil { - image.ExpiryDate = eolDate + image.ExpiresAt = eolDate } } - downloads[fingerprint] = [][]string{[]string{metaPath, metaHash, "meta"}, []string{rootfsPath, rootfsHash, "root"}} + downloads[fingerprint] = [][]string{{metaPath, metaHash, "meta"}, {rootfsPath, rootfsHash, "root"}} images = append(images, image) } } @@ -278,8 +279,8 @@ cachedIndex *SimpleStreamsIndex cachedManifest map[string]*SimpleStreamsManifest - cachedImages []shared.ImageInfo - cachedAliases map[string]*shared.ImageAliasesEntry + cachedImages []api.Image + cachedAliases map[string]*api.ImageAliasesEntry } func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) { @@ -356,8 +357,8 @@ return &ssManifest, nil } -func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) { - aliases := map[string]*shared.ImageAliasesEntry{} +func (s *SimpleStreams) applyAliases(images []api.Image) ([]api.Image, map[string]*api.ImageAliasesEntry, error) { + aliases := map[string]*api.ImageAliasesEntry{} sort.Sort(ssSortImage(images)) @@ -369,7 +370,7 @@ } } - addAlias := func(name string, fingerprint string) *shared.ImageAlias { + addAlias := func(name string, fingerprint string) *api.ImageAlias { if defaultOS != "" { name = strings.TrimPrefix(name, fmt.Sprintf("%s/", defaultOS)) } @@ -378,17 +379,17 @@ return nil } - alias := shared.ImageAliasesEntry{} + alias := api.ImageAliasesEntry{} alias.Name = name alias.Target = fingerprint aliases[name] = &alias - return &shared.ImageAlias{Name: name} + return &api.ImageAlias{Name: name} } architectureName, _ := osarch.ArchitectureGetLocal() - newImages := []shared.ImageInfo{} + newImages := []api.Image{} for _, image := range images { if image.Aliases != nil { // Build a new list of aliases from the provided ones @@ -418,12 +419,12 @@ return newImages, aliases, nil } -func (s *SimpleStreams) getImages() ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) { +func (s *SimpleStreams) getImages() ([]api.Image, map[string]*api.ImageAliasesEntry, error) { if s.cachedImages != nil && s.cachedAliases != nil { return s.cachedImages, s.cachedAliases, nil } - images := []shared.ImageInfo{} + images := []api.Image{} // Load the main index ssIndex, err := s.parseIndex() @@ -573,13 +574,13 @@ return nil } -func (s *SimpleStreams) ListAliases() (shared.ImageAliases, error) { +func (s *SimpleStreams) ListAliases() ([]api.ImageAliasesEntry, error) { _, aliasesMap, err := s.getImages() if err != nil { return nil, err } - aliases := shared.ImageAliases{} + aliases := []api.ImageAliasesEntry{} for _, alias := range aliasesMap { aliases = append(aliases, *alias) @@ -588,7 +589,7 @@ return aliases, nil } -func (s *SimpleStreams) ListImages() ([]shared.ImageInfo, error) { +func (s *SimpleStreams) ListImages() ([]api.Image, error) { images, _, err := s.getImages() return images, err } @@ -607,7 +608,7 @@ return alias.Target } -func (s *SimpleStreams) GetImageInfo(fingerprint string) (*shared.ImageInfo, error) { +func (s *SimpleStreams) GetImageInfo(fingerprint string) (*api.Image, error) { images, _, err := s.getImages() if err != nil { return nil, err diff -Nru lxd-2.7/shared/status.go lxd-2.8/shared/status.go --- lxd-2.7/shared/status.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/status.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -package shared - -type StatusCode int - -const ( - OperationCreated StatusCode = 100 - Started StatusCode = 101 - Stopped StatusCode = 102 - Running StatusCode = 103 - Cancelling StatusCode = 104 - Pending StatusCode = 105 - Starting StatusCode = 106 - Stopping StatusCode = 107 - Aborting StatusCode = 108 - Freezing StatusCode = 109 - Frozen StatusCode = 110 - Thawed StatusCode = 111 - Error StatusCode = 112 - - Success StatusCode = 200 - - Failure StatusCode = 400 - Cancelled StatusCode = 401 -) - -func (o StatusCode) String() string { - return map[StatusCode]string{ - OperationCreated: "Operation created", - Started: "Started", - Stopped: "Stopped", - Running: "Running", - Cancelling: "Cancelling", - Pending: "Pending", - Success: "Success", - Failure: "Failure", - Cancelled: "Cancelled", - Starting: "Starting", - Stopping: "Stopping", - Aborting: "Aborting", - Freezing: "Freezing", - Frozen: "Frozen", - Thawed: "Thawed", - Error: "Error", - }[o] -} - -func (o StatusCode) IsFinal() bool { - return int(o) >= 200 -} diff -Nru lxd-2.7/shared/stringset.go lxd-2.8/shared/stringset.go --- lxd-2.7/shared/stringset.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/stringset.go 2017-01-25 02:50:49.000000000 +0000 @@ -5,7 +5,7 @@ type StringSet map[string]bool func (ss StringSet) IsSubset(oss StringSet) bool { - for k, _ := range map[string]bool(ss) { + for k := range map[string]bool(ss) { if _, ok := map[string]bool(oss)[k]; !ok { return false } diff -Nru lxd-2.7/shared/util.go lxd-2.8/shared/util.go --- lxd-2.7/shared/util.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/util.go 2017-01-25 02:50:49.000000000 +0000 @@ -19,6 +19,8 @@ "regexp" "strconv" "strings" + "time" + "unicode" ) const SnapshotDelimiter = "/" @@ -394,6 +396,15 @@ return false } +func Int64InSlice(key int64, list []int64) bool { + for _, entry := range list { + if entry == key { + return true + } + } + return false +} + func IsTrue(value string) bool { if StringInSlice(strings.ToLower(value), []string{"true", "1", "yes", "on"}) { return true @@ -617,19 +628,32 @@ // Parse a size string in bytes (e.g. 200kB or 5GB) into the number of bytes it // represents. Supports suffixes up to EB. "" == 0. func ParseByteSizeString(input string) (int64, error) { + suffixLen := 2 + if input == "" { return 0, nil } - if len(input) < 3 { + if unicode.IsNumber(rune(input[len(input)-1])) { + // COMMENT(brauner): No suffix --> bytes. + suffixLen = 0 + } else if (len(input) >= 2) && (input[len(input)-1] == 'B') && unicode.IsNumber(rune(input[len(input)-2])) { + // COMMENT(brauner): "B" suffix --> bytes. + suffixLen = 1 + } else if strings.HasSuffix(input, " bytes") { + // COMMENT(brauner): Backward compatible behaviour in case we + // talk to a LXD that still uses GetByteSizeString() that + // returns "n bytes". + suffixLen = 6 + } else if (len(input) < 3) && (suffixLen == 2) { return -1, fmt.Errorf("Invalid value: %s", input) } // Extract the suffix - suffix := input[len(input)-2:] + suffix := input[len(input)-suffixLen:] // Extract the value - value := input[0 : len(input)-2] + value := input[0 : len(input)-suffixLen] valueInt, err := strconv.ParseInt(value, 10, 64) if err != nil { return -1, fmt.Errorf("Invalid integer: %s", input) @@ -639,6 +663,11 @@ return -1, fmt.Errorf("Invalid value: %d", valueInt) } + // COMMENT(brauner): The value is already in bytes. + if suffixLen != 2 { + return valueInt, nil + } + // Figure out the multiplicator multiplicator := int64(0) switch suffix { @@ -708,9 +737,9 @@ return valueInt * multiplicator, nil } -func GetByteSizeString(input int64) string { +func GetByteSizeString(input int64, precision uint) string { if input < 1024 { - return fmt.Sprintf("%d bytes", input) + return fmt.Sprintf("%dB", input) } value := float64(input) @@ -718,15 +747,15 @@ for _, unit := range []string{"kB", "MB", "GB", "TB", "PB", "EB"} { value = value / 1024 if value < 1024 { - return fmt.Sprintf("%.2f%s", value, unit) + return fmt.Sprintf("%.*f%s", precision, value, unit) } } - return fmt.Sprintf("%.2fEB", value) + return fmt.Sprintf("%.*fEB", precision, value) } // RemoveDuplicatesFromString removes all duplicates of the string 'sep' -// from the specified string 's'. Leading and trailing occurences of sep +// from the specified string 's'. Leading and trailing occurrences of sep // are NOT removed (duplicate leading/trailing are). Performs poorly if // there are multiple consecutive redundant separators. func RemoveDuplicatesFromString(s string, sep string) string { @@ -745,3 +774,15 @@ return nil } + +func TimeIsSet(ts time.Time) bool { + if ts.Unix() <= 0 { + return false + } + + if ts.UTC().Unix() <= 0 { + return false + } + + return true +} diff -Nru lxd-2.7/shared/util_linux.go lxd-2.8/shared/util_linux.go --- lxd-2.7/shared/util_linux.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/util_linux.go 2017-01-25 02:50:49.000000000 +0000 @@ -19,18 +19,19 @@ // #cgo LDFLAGS: -lutil -lpthread /* #define _GNU_SOURCE -#include -#include -#include -#include -#include -#include #include #include +#include #include #include -#include +#include +#include #include +#include +#include +#include +#include +#include #ifndef AT_SYMLINK_FOLLOW #define AT_SYMLINK_FOLLOW 0x400 @@ -277,36 +278,36 @@ return master, slave, nil } -// GroupName is an adaption from https://codereview.appspot.com/4589049. -func GroupName(gid int) (string, error) { - var grp C.struct_group - var result *C.struct_group +// UserId is an adaption from https://codereview.appspot.com/4589049. +func UserId(name string) (int, error) { + var pw C.struct_passwd + var result *C.struct_passwd - bufSize := C.size_t(C.sysconf(C._SC_GETGR_R_SIZE_MAX)) + bufSize := C.size_t(C.sysconf(C._SC_GETPW_R_SIZE_MAX)) buf := C.malloc(bufSize) if buf == nil { - return "", fmt.Errorf("allocation failed") + return -1, fmt.Errorf("allocation failed") } defer C.free(buf) - // mygetgrgid_r is a wrapper around getgrgid_r to - // to avoid using gid_t because C.gid_t(gid) for - // unknown reasons doesn't work on linux. - rv := C.mygetgrgid_r(C.int(gid), - &grp, + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + rv := C.getpwnam_r(cname, + &pw, (*C.char)(buf), bufSize, &result) if rv != 0 { - return "", fmt.Errorf("failed group lookup: %s", syscall.Errno(rv)) + return -1, fmt.Errorf("failed user lookup: %s", syscall.Errno(rv)) } if result == nil { - return "", fmt.Errorf("unknown group %d", gid) + return -1, fmt.Errorf("unknown user %s", name) } - return C.GoString(result.gr_name), nil + return int(C.int(result.pw_uid)), nil } // GroupId is an adaption from https://codereview.appspot.com/4589049. @@ -321,9 +322,6 @@ } defer C.free(buf) - // mygetgrgid_r is a wrapper around getgrgid_r to - // to avoid using gid_t because C.gid_t(gid) for - // unknown reasons doesn't work on linux. cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -413,7 +411,7 @@ } if lines < 0 { - return string(data[i+1 : len(data)]), nil + return string(data[i+1:]), nil } } @@ -642,7 +640,7 @@ // still be handling a pure POLLIN event from a write prior to the childs // exit. But the child might have exited right before and performed // atomic.StoreInt32() to update attachedChildIsDead before we - // performed our atomic.LoadInt32(). This means we accidently hit this + // performed our atomic.LoadInt32(). This means we accidentally hit this // codepath and are misinformed about the available poll() events. So we // need to perform a non-blocking poll() again to exclude that case: // diff -Nru lxd-2.7/shared/version/flex.go lxd-2.8/shared/version/flex.go --- lxd-2.7/shared/version/flex.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/shared/version/flex.go 2017-01-25 02:50:49.000000000 +0000 @@ -3,7 +3,7 @@ */ package version -var Version = "2.7" +var Version = "2.8" var UserAgent = "LXD " + Version /* diff -Nru lxd-2.7/test/lxd-benchmark/main.go lxd-2.8/test/lxd-benchmark/main.go --- lxd-2.7/test/lxd-benchmark/main.go 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/test/lxd-benchmark/main.go 2017-01-25 02:50:49.000000000 +0000 @@ -10,6 +10,7 @@ "github.com/lxc/lxd" "github.com/lxc/lxd/shared" + "github.com/lxc/lxd/shared/api" "github.com/lxc/lxd/shared/gnuflag" ) @@ -258,7 +259,7 @@ return err } - containers := []shared.ContainerInfo{} + containers := []api.Container{} for _, container := range allContainers { if container.Config["user.lxd-benchmark"] != "true" { continue @@ -277,7 +278,7 @@ wgBatch := sync.WaitGroup{} nextStat := batch - deleteContainer := func(ct shared.ContainerInfo) { + deleteContainer := func(ct api.Container) { defer wgBatch.Done() // Stop diff -Nru lxd-2.7/test/suites/basic.sh lxd-2.8/test/suites/basic.sh --- lxd-2.7/test/suites/basic.sh 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/test/suites/basic.sh 2017-01-25 02:50:49.000000000 +0000 @@ -404,7 +404,7 @@ lxc launch testimage foo -e OLD_INIT=$(lxc info foo | grep ^Pid) - lxc exec foo reboot + lxc exec foo reboot || true REBOOTED="false" diff -Nru lxd-2.7/test/suites/config.sh lxd-2.8/test/suites/config.sh --- lxd-2.7/test/suites/config.sh 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/test/suites/config.sh 2017-01-25 02:50:49.000000000 +0000 @@ -12,13 +12,13 @@ lxc start foo lxc config device add foo tty unix-char "$@" lxc exec foo -- stat /dev/ttyS0 - lxc exec foo reboot + lxc restart foo lxc exec foo -- stat /dev/ttyS0 lxc restart foo --force lxc exec foo -- stat /dev/ttyS0 lxc config device remove foo tty ensure_removed "was not hot-removed" - lxc exec foo reboot + lxc restart foo ensure_removed "removed device re-appeared after container reboot" lxc restart foo --force ensure_removed "removed device re-appaared after lxc reboot" @@ -70,7 +70,7 @@ lxc exec foo stat /mnt/hello lxc config device remove foo mnt ensure_fs_unmounted "fs should have been hot-unmounted" - lxc exec foo reboot + lxc restart foo ensure_fs_unmounted "removed fs re-appeared after reboot" lxc restart foo --force ensure_fs_unmounted "removed fs re-appeared after restart" diff -Nru lxd-2.7/test/suites/database_update.sh lxd-2.8/test/suites/database_update.sh --- lxd-2.7/test/suites/database_update.sh 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/test/suites/database_update.sh 2017-01-25 02:50:49.000000000 +0000 @@ -15,7 +15,7 @@ tables=$(sqlite3 "${MIGRATE_DB}" ".dump" | grep -c "CREATE TABLE") [ "${tables}" -eq "${expected_tables}" ] || { echo "FAIL: Wrong number of tables after database migration. Found: ${tables}, expected ${expected_tables}"; false; } - # There should be 13 "ON DELETE CASCADE" occurences + # There should be 13 "ON DELETE CASCADE" occurrences expected_cascades=12 cascades=$(sqlite3 "${MIGRATE_DB}" ".dump" | grep -c "ON DELETE CASCADE") [ "${cascades}" -eq "${expected_cascades}" ] || { echo "FAIL: Wrong number of ON DELETE CASCADE foreign keys. Found: ${cascades}, exected: ${expected_cascades}"; false; } diff -Nru lxd-2.7/test/suites/filemanip.sh lxd-2.8/test/suites/filemanip.sh --- lxd-2.7/test/suites/filemanip.sh 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/test/suites/filemanip.sh 2017-01-25 02:50:49.000000000 +0000 @@ -1,6 +1,8 @@ #!/bin/sh test_filemanip() { + # Workaround for shellcheck getting confused by "cd" + set -e ensure_import_testimage ensure_has_localhost_remote "${LXD_ADDR}" @@ -20,6 +22,7 @@ # lxc {push|pull} -r mkdir "${TEST_DIR}"/source mkdir "${TEST_DIR}"/source/another_level + chown 1000:1000 "${TEST_DIR}"/source/another_level echo "foo" > "${TEST_DIR}"/source/foo echo "bar" > "${TEST_DIR}"/source/bar @@ -27,6 +30,8 @@ [ "$(lxc exec filemanip -- stat -c "%u" /tmp/ptest/source)" = "$(id -u)" ] [ "$(lxc exec filemanip -- stat -c "%g" /tmp/ptest/source)" = "$(id -g)" ] + [ "$(lxc exec filemanip -- stat -c "%u" /tmp/ptest/source/another_level)" = "1000" ] + [ "$(lxc exec filemanip -- stat -c "%g" /tmp/ptest/source/another_level)" = "1000" ] [ "$(lxc exec filemanip -- stat -c "%a" /tmp/ptest/source)" = "755" ] lxc exec filemanip -- rm -rf /tmp/ptest/source diff -Nru lxd-2.7/test/suites/static_analysis.sh lxd-2.8/test/suites/static_analysis.sh --- lxd-2.7/test/suites/static_analysis.sh 2016-12-21 00:52:23.000000000 +0000 +++ lxd-2.8/test/suites/static_analysis.sh 2017-01-25 02:50:49.000000000 +0000 @@ -38,8 +38,8 @@ ## deadcode if which deadcode >/dev/null 2>&1; then - for path in . lxc/ lxd/ shared/ shared/i18n shared/termios fuidshift/; do - OUT=$(deadcode ${path} 2>&1 | grep -v lxd/migrate.pb.go || true) + for path in . fuidshift lxc lxd lxd/types shared shared/api shared/i18n shared/ioprogress shared/logging shared/osarch shared/simplestreams shared/termios shared/version test/lxd-benchmark; do + OUT=$(deadcode ./${path} 2>&1 | grep -v lxd/migrate.pb.go: | grep -v /C: | grep -vi _cgo | grep -vi _cfunc || true) if [ -n "${OUT}" ]; then echo "${OUT}" >&2 false