diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/bindings/README.md | 156 | ||||
-rw-r--r-- | pkg/bindings/containers/rename.go | 28 | ||||
-rw-r--r-- | pkg/bindings/containers/types.go | 7 | ||||
-rw-r--r-- | pkg/bindings/containers/types_rename_options.go | 104 | ||||
-rw-r--r-- | pkg/bindings/images/build.go | 138 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 2 | ||||
-rw-r--r-- | pkg/specgen/generate/oci.go | 39 |
7 files changed, 432 insertions, 42 deletions
diff --git a/pkg/bindings/README.md b/pkg/bindings/README.md new file mode 100644 index 000000000..149deda5e --- /dev/null +++ b/pkg/bindings/README.md @@ -0,0 +1,156 @@ +# Podman Golang bindings +The Podman Go bindings are a set of functions to allow developers to execute Podman operations from within their Go based application. The Go bindings +connect to a Podman service which can run locally or on a remote machine. You can perform many operations including pulling and listing images, starting, +stopping or inspecting containers. Currently, the Podman repository has bindings available for operations on images, containers, pods, +networks and manifests among others. + +## Quick Start +The bindings require that the Podman system service is running for the specified user. This can be done with systemd using the `systemctl` command or manually +by calling the service directly. + +### Starting the service with system +The command to start the Podman service differs slightly depending on the user that is running the service. For a rootful service, +start the service like this: +``` +# systemctl start podman.socket +``` +For a non-privileged, aka rootless, user, start the service like this: + +``` +$ systemctl start --user podman.socket +``` + +### Starting the service manually +It can be handy to run the system service manually. Doing so allows you to enable debug messaging. +``` +$ podman --log-level=debug system service -t0 +``` +If you do not provide a specific path for the socket, a default is provided. The location of that socket for +rootful connections is `/run/podman/podman.sock` and for rootless it is `/run/USERID#/podman/podman.sock`. For more +information about the Podman system service, see `man podman-system-service`. + +### Creating a connection +The first step for using the bindings is to create a connection to the socket. As mentioned earlier, the destination +of the socket depends on the user who owns it. In this case, a rootful connection is made. + +``` +import ( + "context" + "fmt" + "os" + + "github.com/containers/podman/v2/pkg/bindings" +) + +func main() { + conn, err := bindings.NewConnection(context.Background(), "unix://run/podman/podman.sock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + +} +``` +The `conn` variable returned from the `bindings.NewConnection` function can then be used in subsequent function calls +to interact with containers. + +### Examples +The following examples build upon the connection example from above. They are all rootful connections as well. + +#### Inspect a container +The following example obtains the inspect information for a container named `foorbar` and then prints +the container's ID. Note the use of optional inspect options for size. +``` +import ( + "context" + "fmt" + "os" + + "github.com/containers/podman/v2/pkg/bindings" + "github.com/containers/podman/v2/pkg/bindings/containers" +) + +func main() { + conn, err := bindings.NewConnection(context.Background(), "unix://run/podman/podman.sock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + inspectData, err := containers.Inspect(conn, "foobar", new(containers.InspectOptions).WithSize(true)) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + // Print the container ID + fmt.Println(inspectData.ID) +} +``` + +#### Pull an image +The following example pulls the image `quay.ioo/libpod/alpine_nginx` to the local image store. +``` +import ( + "context" + "fmt" + "os" + + "github.com/containers/podman/v2/pkg/bindings" + "github.com/containers/podman/v2/pkg/bindings/images" +) + +func main() { + conn, err := bindings.NewConnection(context.Background(), "unix://run/podman/podman.sock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + _, err = images.Pull(conn, "quay.io/libpod/alpine_nginx", nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +``` + +#### Pull an image, create a container, and start the container +The following example pulls the `quay.io/libpod/alpine_nginx` image and then creates a container named `foobar` +from it. And finally, it starts the container. +``` +import ( + "context" + "fmt" + "os" + + "github.com/containers/podman/v2/pkg/bindings" + "github.com/containers/podman/v2/pkg/bindings/containers" + "github.com/containers/podman/v2/pkg/bindings/images" + "github.com/containers/podman/v2/pkg/specgen" +) + +func main() { + conn, err := bindings.NewConnection(context.Background(), "unix://run/podman/podman.sock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + _, err = images.Pull(conn, "quay.io/libpod/alpine_nginx", nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + s := specgen.NewSpecGenerator("quay.io/libpod/alpine_nginx", false) + s.Name = "foobar" + createResponse, err := containers.CreateWithSpec(conn, s, nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Container created.") + if err := containers.Start(conn, createResponse.ID, nil); err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Container started.") +} +``` diff --git a/pkg/bindings/containers/rename.go b/pkg/bindings/containers/rename.go new file mode 100644 index 000000000..0e8c7f198 --- /dev/null +++ b/pkg/bindings/containers/rename.go @@ -0,0 +1,28 @@ +package containers + +import ( + "context" + "net/http" + + "github.com/containers/podman/v2/pkg/bindings" +) + +// Rename an existing container. +func Rename(ctx context.Context, nameOrID string, options *RenameOptions) error { + if options == nil { + options = new(RenameOptions) + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params, err := options.ToParams() + if err != nil { + return err + } + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/rename", params, nil, nameOrID) + if err != nil { + return err + } + return response.Process(nil) +} diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index 24402e982..10f553e52 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -194,6 +194,13 @@ type InitOptions struct{} // ShouldRestartOptions type ShouldRestartOptions struct{} +//go:generate go run ../generator/generator.go RenameOptions +// RenameOptions are options for renaming containers. +// The Name field is required. +type RenameOptions struct { + Name *string +} + //go:generate go run ../generator/generator.go ResizeTTYOptions // ResizeTTYOptions are optional options for resizing // container TTYs diff --git a/pkg/bindings/containers/types_rename_options.go b/pkg/bindings/containers/types_rename_options.go new file mode 100644 index 000000000..b7a723f7a --- /dev/null +++ b/pkg/bindings/containers/types_rename_options.go @@ -0,0 +1,104 @@ +package containers + +import ( + "net/url" + "reflect" + "strconv" + "strings" + + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" +) + +/* +This file is generated automatically by go generate. Do not edit. +*/ + +// Changed +func (o *RenameOptions) Changed(fieldName string) bool { + r := reflect.ValueOf(o) + value := reflect.Indirect(r).FieldByName(fieldName) + return !value.IsNil() +} + +// ToParams +func (o *RenameOptions) ToParams() (url.Values, error) { + params := url.Values{} + if o == nil { + return params, nil + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + s := reflect.ValueOf(o) + if reflect.Ptr == s.Kind() { + s = s.Elem() + } + sType := s.Type() + for i := 0; i < s.NumField(); i++ { + fieldName := sType.Field(i).Name + if !o.Changed(fieldName) { + continue + } + fieldName = strings.ToLower(fieldName) + f := s.Field(i) + if reflect.Ptr == f.Kind() { + f = f.Elem() + } + switch f.Kind() { + case reflect.Bool: + params.Set(fieldName, strconv.FormatBool(f.Bool())) + case reflect.String: + params.Set(fieldName, f.String()) + case reflect.Int, reflect.Int64: + // f.Int() is always an int64 + params.Set(fieldName, strconv.FormatInt(f.Int(), 10)) + case reflect.Uint, reflect.Uint64: + // f.Uint() is always an uint64 + params.Set(fieldName, strconv.FormatUint(f.Uint(), 10)) + case reflect.Slice: + typ := reflect.TypeOf(f.Interface()).Elem() + switch typ.Kind() { + case reflect.String: + sl := f.Slice(0, f.Len()) + s, ok := sl.Interface().([]string) + if !ok { + return nil, errors.New("failed to convert to string slice") + } + for _, val := range s { + params.Add(fieldName, val) + } + default: + return nil, errors.Errorf("unknown slice type %s", f.Kind().String()) + } + case reflect.Map: + lowerCaseKeys := make(map[string][]string) + iter := f.MapRange() + for iter.Next() { + lowerCaseKeys[iter.Key().Interface().(string)] = iter.Value().Interface().([]string) + + } + s, err := json.MarshalToString(lowerCaseKeys) + if err != nil { + return nil, err + } + + params.Set(fieldName, s) + } + } + return params, nil +} + +// WithName +func (o *RenameOptions) WithName(value string) *RenameOptions { + v := &value + o.Name = v + return o +} + +// GetName +func (o *RenameOptions) GetName() string { + var name string + if o.Name == nil { + return name + } + return *o.Name +} diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index d34ab87d9..02765816f 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -2,6 +2,7 @@ package images import ( "archive/tar" + "compress/gzip" "context" "encoding/json" "io" @@ -18,6 +19,7 @@ import ( "github.com/containers/podman/v2/pkg/auth" "github.com/containers/podman/v2/pkg/bindings" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/storage/pkg/fileutils" "github.com/docker/go-units" "github.com/hashicorp/go-multierror" jsoniter "github.com/json-iterator/go" @@ -138,12 +140,38 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO entries := make([]string, len(containerFiles)) copy(entries, containerFiles) entries = append(entries, options.ContextDirectory) - tarfile, err := nTar(entries...) + + excludes := options.Excludes + if len(excludes) == 0 { + excludes, err = parseDockerignore(options.ContextDirectory) + if err != nil { + return nil, err + } + } + + tarfile, err := nTar(excludes, entries...) if err != nil { + logrus.Errorf("cannot tar container entries %v error: %v", entries, err) return nil, err } defer tarfile.Close() - params.Set("dockerfile", filepath.Base(containerFiles[0])) + + containerFile, err := filepath.Abs(entries[0]) + if err != nil { + logrus.Errorf("cannot find absolute path of %v: %v", entries[0], err) + return nil, err + } + contextDir, err := filepath.Abs(entries[1]) + if err != nil { + logrus.Errorf("cannot find absolute path of %v: %v", entries[1], err) + return nil, err + } + + if strings.HasPrefix(containerFile, contextDir+string(filepath.Separator)) { + containerFile = strings.TrimPrefix(containerFile, contextDir+string(filepath.Separator)) + } + + params.Set("dockerfile", containerFile) conn, err := bindings.GetClient(ctx) if err != nil { @@ -200,52 +228,116 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } } -func nTar(sources ...string) (io.ReadCloser, error) { +func nTar(excludes []string, sources ...string) (io.ReadCloser, error) { + pm, err := fileutils.NewPatternMatcher(excludes) + if err != nil { + return nil, errors.Wrapf(err, "error processing excludes list %v", excludes) + } + if len(sources) == 0 { return nil, errors.New("No source(s) provided for build") } pr, pw := io.Pipe() - tw := tar.NewWriter(pw) + gw := gzip.NewWriter(pw) + tw := tar.NewWriter(gw) var merr error go func() { defer pw.Close() + defer gw.Close() defer tw.Close() for _, src := range sources { - s := src - err := filepath.Walk(s, func(path string, info os.FileInfo, err error) error { + s, err := filepath.Abs(src) + if err != nil { + logrus.Errorf("cannot stat one of source context: %v", err) + merr = multierror.Append(merr, err) + return + } + + err = filepath.Walk(s, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - if !info.Mode().IsRegular() || path == s { - return nil - } - f, lerr := os.Open(path) - if lerr != nil { - return lerr + if path == s { + return nil // skip root dir } name := strings.TrimPrefix(path, s+string(filepath.Separator)) - hdr, lerr := tar.FileInfoHeader(info, name) - if lerr != nil { - f.Close() - return lerr + + excluded, err := pm.Matches(filepath.ToSlash(name)) // nolint:staticcheck + if err != nil { + return errors.Wrapf(err, "error checking if %q is excluded", name) } - hdr.Name = name - if lerr := tw.WriteHeader(hdr); lerr != nil { - f.Close() - return lerr + if excluded { + return nil } - _, cerr := io.Copy(tw, f) - f.Close() - return cerr + if info.Mode().IsRegular() { // add file item + f, lerr := os.Open(path) + if lerr != nil { + return lerr + } + + hdr, lerr := tar.FileInfoHeader(info, name) + if lerr != nil { + f.Close() + return lerr + } + hdr.Name = name + if lerr := tw.WriteHeader(hdr); lerr != nil { + f.Close() + return lerr + } + + _, cerr := io.Copy(tw, f) + f.Close() + return cerr + } else if info.Mode().IsDir() { // add folders + hdr, lerr := tar.FileInfoHeader(info, name) + if lerr != nil { + return lerr + } + hdr.Name = name + if lerr := tw.WriteHeader(hdr); lerr != nil { + return lerr + } + } else if info.Mode()&os.ModeSymlink != 0 { // add symlinks as it, not content + link, err := os.Readlink(path) + if err != nil { + return err + } + hdr, lerr := tar.FileInfoHeader(info, link) + if lerr != nil { + return lerr + } + hdr.Name = name + if lerr := tw.WriteHeader(hdr); lerr != nil { + return lerr + } + } //skip other than file,folder and symlinks + return nil }) merr = multierror.Append(merr, err) } }() return pr, merr } + +func parseDockerignore(root string) ([]string, error) { + ignore, err := ioutil.ReadFile(filepath.Join(root, ".dockerignore")) + if err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "error reading .dockerignore: '%s'", root) + } + rawexcludes := strings.Split(string(ignore), "\n") + excludes := make([]string, 0, len(rawexcludes)) + for _, e := range rawexcludes { + if len(e) == 0 || e[0] == '#' { + continue + } + excludes = append(excludes, e) + } + return excludes, nil +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 8aab4a9cd..4ee623703 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -823,5 +823,5 @@ func (ic *ContainerEngine) ShouldRestart(_ context.Context, id string) (bool, er // ContainerRename renames the given container. func (ic *ContainerEngine) ContainerRename(ctx context.Context, nameOrID string, opts entities.ContainerRenameOptions) error { - return errors.Errorf("NOT YET IMPLEMENTED") + return containers.Rename(ic.ClientCtx, nameOrID, new(containers.RenameOptions).WithName(opts.NewName)) } diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 7dc32a314..e62131244 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -138,10 +138,23 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image return finalCommand, nil } +// canMountSys is a best-effort heuristic to detect whether mounting a new sysfs is permitted in the container +func canMountSys(isRootless, isNewUserns bool, s *specgen.SpecGenerator) bool { + if s.NetNS.IsHost() && (isRootless || isNewUserns) { + return false + } + if isNewUserns { + switch s.NetNS.NSMode { + case specgen.Slirp, specgen.Private, specgen.NoNetwork, specgen.Bridge: + return true + default: + return false + } + } + return true +} + func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) { - var ( - inUserNS bool - ) cgroupPerm := "ro" g, err := generate.New("linux") if err != nil { @@ -151,23 +164,11 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt g.RemoveMount("/dev/shm") g.HostSpecific = true addCgroup := true - canMountSys := true isRootless := rootless.IsRootless() - if isRootless { - inUserNS = true - } - if !s.UserNS.IsHost() { - if s.UserNS.IsContainer() || s.UserNS.IsPath() { - inUserNS = true - } - if s.UserNS.IsPrivate() { - inUserNS = true - } - } - if inUserNS && s.NetNS.NSMode != specgen.NoNetwork { - canMountSys = false - } + isNewUserns := s.UserNS.IsContainer() || s.UserNS.IsPath() || s.UserNS.IsPrivate() + + canMountSys := canMountSys(isRootless, isNewUserns, s) if s.Privileged && canMountSys { cgroupPerm = "rw" @@ -232,6 +233,8 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt g.AddMount(devPts) } + inUserNS := isRootless || isNewUserns + if inUserNS && s.IpcNS.IsHost() { g.RemoveMount("/dev/mqueue") devMqueue := spec.Mount{ |