summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/images_build.go183
-rw-r--r--pkg/api/handlers/compat/secrets.go121
-rw-r--r--pkg/api/handlers/libpod/secrets.go40
-rw-r--r--pkg/api/handlers/utils/errors.go8
-rw-r--r--pkg/api/server/register_secrets.go194
-rw-r--r--pkg/api/server/server.go1
-rw-r--r--pkg/api/tags.yaml4
-rw-r--r--pkg/bindings/images/build.go154
-rw-r--r--pkg/bindings/secrets/secrets.go78
-rw-r--r--pkg/bindings/secrets/types.go23
-rw-r--r--pkg/bindings/secrets/types_create_options.go107
-rw-r--r--pkg/bindings/secrets/types_inspect_options.go75
-rw-r--r--pkg/bindings/secrets/types_list_options.go75
-rw-r--r--pkg/bindings/secrets/types_remove_options.go75
-rw-r--r--pkg/bindings/test/secrets_test.go133
-rw-r--r--pkg/domain/entities/engine_container.go4
-rw-r--r--pkg/domain/entities/secrets.go104
-rw-r--r--pkg/domain/infra/abi/secrets.go138
-rw-r--r--pkg/domain/infra/tunnel/images.go11
-rw-r--r--pkg/domain/infra/tunnel/secrets.go82
-rw-r--r--pkg/specgen/generate/container_create.go4
-rw-r--r--pkg/specgen/specgen.go3
22 files changed, 1499 insertions, 118 deletions
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index 415ff85cd..0f27a090f 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -60,29 +60,39 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}()
query := struct {
- BuildArgs string `schema:"buildargs"`
- CacheFrom string `schema:"cachefrom"`
- CpuPeriod uint64 `schema:"cpuperiod"` // nolint
- CpuQuota int64 `schema:"cpuquota"` // nolint
- CpuSetCpus string `schema:"cpusetcpus"` // nolint
- CpuShares uint64 `schema:"cpushares"` // nolint
- Dockerfile string `schema:"dockerfile"`
- ExtraHosts string `schema:"extrahosts"`
- ForceRm bool `schema:"forcerm"`
- HTTPProxy bool `schema:"httpproxy"`
- Labels string `schema:"labels"`
- Layers bool `schema:"layers"`
- MemSwap int64 `schema:"memswap"`
- Memory int64 `schema:"memory"`
- NetworkMode string `schema:"networkmode"`
- NoCache bool `schema:"nocache"`
- Outputs string `schema:"outputs"`
- Platform string `schema:"platform"`
- Pull bool `schema:"pull"`
- Quiet bool `schema:"q"`
- Registry string `schema:"registry"`
- Remote string `schema:"remote"`
- Rm bool `schema:"rm"`
+ AddHosts string `schema:"extrahosts"`
+ AdditionalCapabilities string `schema:"addcaps"`
+ Annotations string `schema:"annotations"`
+ BuildArgs string `schema:"buildargs"`
+ CacheFrom string `schema:"cachefrom"`
+ ConfigureNetwork int64 `schema:"networkmode"`
+ CpuPeriod uint64 `schema:"cpuperiod"` // nolint
+ CpuQuota int64 `schema:"cpuquota"` // nolint
+ CpuSetCpus string `schema:"cpusetcpus"` // nolint
+ CpuShares uint64 `schema:"cpushares"` // nolint
+ Devices string `schema:"devices"`
+ Dockerfile string `schema:"dockerfile"`
+ DropCapabilities string `schema:"dropcaps"`
+ ForceRm bool `schema:"forcerm"`
+ From string `schema:"from"`
+ HTTPProxy bool `schema:"httpproxy"`
+ Isolation int64 `schema:"isolation"`
+ Jobs uint64 `schema:"jobs"` // nolint
+ Labels string `schema:"labels"`
+ Layers bool `schema:"layers"`
+ LogRusage bool `schema:"rusage"`
+ Manifest string `schema:"manifest"`
+ MemSwap int64 `schema:"memswap"`
+ Memory int64 `schema:"memory"`
+ NoCache bool `schema:"nocache"`
+ OutputFormat string `schema:"outputformat"`
+ Platform string `schema:"platform"`
+ Pull bool `schema:"pull"`
+ Quiet bool `schema:"q"`
+ Registry string `schema:"registry"`
+ Rm bool `schema:"rm"`
+ //FIXME SecurityOpt in remote API is not handled
+ SecurityOpt string `schema:"securityopt"`
ShmSize int `schema:"shmsize"`
Squash bool `schema:"squash"`
Tag []string `schema:"t"`
@@ -101,14 +111,57 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
return
}
+ // convert label formats
+ var addCaps = []string{}
+ if _, found := r.URL.Query()["addcaps"]; found {
+ var m = []string{}
+ if err := json.Unmarshal([]byte(query.AdditionalCapabilities), &m); err != nil {
+ utils.BadRequest(w, "addcaps", query.AdditionalCapabilities, err)
+ return
+ }
+ addCaps = m
+ }
+ addhosts := []string{}
+ if _, found := r.URL.Query()["extrahosts"]; found {
+ if err := json.Unmarshal([]byte(query.AddHosts), &addhosts); err != nil {
+ utils.BadRequest(w, "extrahosts", query.AddHosts, err)
+ return
+ }
+ }
+
+ // convert label formats
+ var dropCaps = []string{}
+ if _, found := r.URL.Query()["dropcaps"]; found {
+ var m = []string{}
+ if err := json.Unmarshal([]byte(query.DropCapabilities), &m); err != nil {
+ utils.BadRequest(w, "dropcaps", query.DropCapabilities, err)
+ return
+ }
+ dropCaps = m
+ }
+
+ // convert label formats
+ var devices = []string{}
+ if _, found := r.URL.Query()["devices"]; found {
+ var m = []string{}
+ if err := json.Unmarshal([]byte(query.DropCapabilities), &m); err != nil {
+ utils.BadRequest(w, "devices", query.DropCapabilities, err)
+ return
+ }
+ devices = m
+ }
+
var output string
if len(query.Tag) > 0 {
output = query.Tag[0]
}
-
- var additionalNames []string
+ format := buildah.Dockerv2ImageManifest
+ if utils.IsLibpodRequest(r) {
+ format = query.OutputFormat
+ }
+ var additionalTags []string
if len(query.Tag) > 1 {
- additionalNames = query.Tag[1:]
+ additionalTags = query.Tag[1:]
}
var buildArgs = map[string]string{}
@@ -120,17 +173,21 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}
// convert label formats
+ var annotations = []string{}
+ if _, found := r.URL.Query()["annotations"]; found {
+ if err := json.Unmarshal([]byte(query.Annotations), &annotations); err != nil {
+ utils.BadRequest(w, "annotations", query.Annotations, err)
+ return
+ }
+ }
+
+ // convert label formats
var labels = []string{}
if _, found := r.URL.Query()["labels"]; found {
- var m = map[string]string{}
- if err := json.Unmarshal([]byte(query.Labels), &m); err != nil {
+ if err := json.Unmarshal([]byte(query.Labels), &labels); err != nil {
utils.BadRequest(w, "labels", query.Labels, err)
return
}
-
- for k, v := range m {
- labels = append(labels, k+"="+v)
- }
}
pullPolicy := buildah.PullIfMissing
@@ -160,27 +217,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
reporter := channel.NewWriter(make(chan []byte, 1))
defer reporter.Close()
+
buildOptions := imagebuildah.BuildOptions{
- ContextDirectory: contextDirectory,
- PullPolicy: pullPolicy,
- Registry: query.Registry,
- IgnoreUnrecognizedInstructions: true,
- Quiet: query.Quiet,
- Layers: query.Layers,
- Isolation: buildah.IsolationChroot,
- Compression: archive.Gzip,
- Args: buildArgs,
- Output: output,
- AdditionalTags: additionalNames,
- Out: stdout,
- Err: auxout,
- ReportWriter: reporter,
- OutputFormat: buildah.Dockerv2ImageManifest,
- SystemContext: &types.SystemContext{
- AuthFilePath: authfile,
- DockerAuthConfig: creds,
- },
+ AddCapabilities: addCaps,
+ AdditionalTags: additionalTags,
+ Annotations: annotations,
+ Args: buildArgs,
CommonBuildOpts: &buildah.CommonBuildOptions{
+ AddHost: addhosts,
CPUPeriod: query.CpuPeriod,
CPUQuota: query.CpuQuota,
CPUShares: query.CpuShares,
@@ -190,12 +234,37 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
MemorySwap: query.MemSwap,
ShmSize: strconv.Itoa(query.ShmSize),
},
- Squash: query.Squash,
- Labels: labels,
- NoCache: query.NoCache,
- RemoveIntermediateCtrs: query.Rm,
- ForceRmIntermediateCtrs: query.ForceRm,
- Target: query.Target,
+ Compression: archive.Gzip,
+ ConfigureNetwork: buildah.NetworkConfigurationPolicy(query.ConfigureNetwork),
+ ContextDirectory: contextDirectory,
+ Devices: devices,
+ DropCapabilities: dropCaps,
+ Err: auxout,
+ ForceRmIntermediateCtrs: query.ForceRm,
+ From: query.From,
+ IgnoreUnrecognizedInstructions: true,
+ // FIXME, This is very broken. Buildah will only work with chroot
+ // Isolation: buildah.Isolation(query.Isolation),
+ Isolation: buildah.IsolationChroot,
+
+ Labels: labels,
+ Layers: query.Layers,
+ Manifest: query.Manifest,
+ NoCache: query.NoCache,
+ Out: stdout,
+ Output: output,
+ OutputFormat: format,
+ PullPolicy: pullPolicy,
+ Quiet: query.Quiet,
+ Registry: query.Registry,
+ RemoveIntermediateCtrs: query.Rm,
+ ReportWriter: reporter,
+ Squash: query.Squash,
+ SystemContext: &types.SystemContext{
+ AuthFilePath: authfile,
+ DockerAuthConfig: creds,
+ },
+ Target: query.Target,
}
runtime := r.Context().Value("runtime").(*libpod.Runtime)
diff --git a/pkg/api/handlers/compat/secrets.go b/pkg/api/handlers/compat/secrets.go
new file mode 100644
index 000000000..ea2dfc707
--- /dev/null
+++ b/pkg/api/handlers/compat/secrets.go
@@ -0,0 +1,121 @@
+package compat
+
+import (
+ "bytes"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func ListSecrets(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ Filters map[string][]string `schema:"filters"`
+ }{}
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ if len(query.Filters) > 0 {
+ utils.Error(w, "filters not supported", http.StatusBadRequest, errors.New("bad parameter"))
+ }
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, err := ic.SecretList(r.Context())
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, reports)
+}
+
+func InspectSecret(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ )
+ name := utils.GetName(r)
+ names := []string{name}
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, errs, err := ic.SecretInspect(r.Context(), names)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if len(errs) > 0 {
+ utils.SecretNotFound(w, name, errs[0])
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, reports[0])
+
+}
+
+func RemoveSecret(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ )
+
+ opts := entities.SecretRmOptions{}
+ name := utils.GetName(r)
+ ic := abi.ContainerEngine{Libpod: runtime}
+ reports, err := ic.SecretRm(r.Context(), []string{name}, opts)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if reports[0].Err != nil {
+ utils.SecretNotFound(w, name, reports[0].Err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusNoContent, nil)
+}
+
+func CreateSecret(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ )
+ opts := entities.SecretCreateOptions{}
+ createParams := struct {
+ *entities.SecretCreateRequest
+ Labels map[string]string `schema:"labels"`
+ }{}
+
+ if err := json.NewDecoder(r.Body).Decode(&createParams); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ if len(createParams.Labels) > 0 {
+ utils.Error(w, "labels not supported", http.StatusBadRequest, errors.New("bad parameter"))
+ }
+
+ decoded, _ := base64.StdEncoding.DecodeString(createParams.Data)
+ reader := bytes.NewReader(decoded)
+ opts.Driver = createParams.Driver.Name
+
+ ic := abi.ContainerEngine{Libpod: runtime}
+ report, err := ic.SecretCreate(r.Context(), createParams.Name, reader, opts)
+ if err != nil {
+ if errors.Cause(err).Error() == "secret name in use" {
+ utils.Error(w, "name conflicts with an existing object", http.StatusConflict, err)
+ return
+ }
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
+
+func UpdateSecret(w http.ResponseWriter, r *http.Request) {
+ utils.Error(w, fmt.Sprintf("unsupported endpoint: %v", r.Method), http.StatusNotImplemented, errors.New("update is not supported"))
+}
diff --git a/pkg/api/handlers/libpod/secrets.go b/pkg/api/handlers/libpod/secrets.go
new file mode 100644
index 000000000..447a5d021
--- /dev/null
+++ b/pkg/api/handlers/libpod/secrets.go
@@ -0,0 +1,40 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func CreateSecret(w http.ResponseWriter, r *http.Request) {
+ var (
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ )
+ query := struct {
+ Name string `schema:"name"`
+ Driver string `schema:"driver"`
+ }{
+ // override any golang type defaults
+ }
+ opts := entities.SecretCreateOptions{}
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ opts.Driver = query.Driver
+
+ ic := abi.ContainerEngine{Libpod: runtime}
+ report, err := ic.SecretCreate(r.Context(), query.Name, r.Body, opts)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, report)
+}
diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go
index e2c287c45..c8785fb89 100644
--- a/pkg/api/handlers/utils/errors.go
+++ b/pkg/api/handlers/utils/errors.go
@@ -80,6 +80,14 @@ func SessionNotFound(w http.ResponseWriter, name string, err error) {
Error(w, msg, http.StatusNotFound, err)
}
+func SecretNotFound(w http.ResponseWriter, nameOrID string, err error) {
+ if errors.Cause(err).Error() != "no such secret" {
+ InternalServerError(w, err)
+ }
+ msg := fmt.Sprintf("No such secret: %s", nameOrID)
+ Error(w, msg, http.StatusNotFound, err)
+}
+
func ContainerNotRunning(w http.ResponseWriter, containerID string, err error) {
msg := fmt.Sprintf("Container %s is not running", containerID)
Error(w, msg, http.StatusConflict, err)
diff --git a/pkg/api/server/register_secrets.go b/pkg/api/server/register_secrets.go
new file mode 100644
index 000000000..95abf83e8
--- /dev/null
+++ b/pkg/api/server/register_secrets.go
@@ -0,0 +1,194 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/podman/v2/pkg/api/handlers/compat"
+ "github.com/containers/podman/v2/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerSecretHandlers(r *mux.Router) error {
+ // swagger:operation POST /libpod/secrets/create libpod libpodCreateSecret
+ // ---
+ // tags:
+ // - secrets
+ // summary: Create a secret
+ // parameters:
+ // - in: query
+ // name: name
+ // type: string
+ // description: User-defined name of the secret.
+ // required: true
+ // - in: query
+ // name: driver
+ // type: string
+ // description: Secret driver
+ // default: "file"
+ // - in: body
+ // name: request
+ // description: Secret
+ // schema:
+ // type: string
+ // produces:
+ // - application/json
+ // responses:
+ // '201':
+ // $ref: "#/responses/SecretCreateResponse"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/secrets/create"), s.APIHandler(libpod.CreateSecret)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/secrets/json libpod libpodListSecret
+ // ---
+ // tags:
+ // - secrets
+ // summary: List secrets
+ // description: Returns a list of secrets
+ // produces:
+ // - application/json
+ // parameters:
+ // responses:
+ // '200':
+ // "$ref": "#/responses/SecretListResponse"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/secrets/json"), s.APIHandler(compat.ListSecrets)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/secrets/{name}/json libpod libpodInspectSecret
+ // ---
+ // tags:
+ // - secrets
+ // summary: Inspect secret
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name or ID of the secret
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/SecretInspectResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchSecret"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/secrets/{name}/json"), s.APIHandler(compat.InspectSecret)).Methods(http.MethodGet)
+ // swagger:operation DELETE /libpod/secrets/{name} libpod libpodRemoveSecret
+ // ---
+ // tags:
+ // - secrets
+ // summary: Remove secret
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name or ID of the secret
+ // - in: query
+ // name: all
+ // type: boolean
+ // description: Remove all secrets
+ // default: false
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchSecret"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/secrets/{name}"), s.APIHandler(compat.RemoveSecret)).Methods(http.MethodDelete)
+
+ /*
+ * Docker compatibility endpoints
+ */
+ // swagger:operation GET /secrets compat ListSecret
+ // ---
+ // tags:
+ // - secrets (compat)
+ // summary: List secrets
+ // description: Returns a list of secrets
+ // produces:
+ // - application/json
+ // parameters:
+ // responses:
+ // '200':
+ // "$ref": "#/responses/SecretListResponse"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle(VersionedPath("/secrets"), s.APIHandler(compat.ListSecrets)).Methods(http.MethodGet)
+ r.Handle("/secrets", s.APIHandler(compat.ListSecrets)).Methods(http.MethodGet)
+ // swagger:operation POST /secrets/create compat CreateSecret
+ // ---
+ // tags:
+ // - secrets (compat)
+ // summary: Create a secret
+ // parameters:
+ // - in: body
+ // name: create
+ // description: |
+ // attributes for creating a secret
+ // schema:
+ // $ref: "#/definitions/SecretCreate"
+ // produces:
+ // - application/json
+ // responses:
+ // '201':
+ // $ref: "#/responses/SecretCreateResponse"
+ // '409':
+ // "$ref": "#/responses/SecretInUse"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle(VersionedPath("/secrets/create"), s.APIHandler(compat.CreateSecret)).Methods(http.MethodPost)
+ r.Handle("/secrets/create", s.APIHandler(compat.CreateSecret)).Methods(http.MethodPost)
+ // swagger:operation GET /secrets/{name} compat InspectSecret
+ // ---
+ // tags:
+ // - secrets (compat)
+ // summary: Inspect secret
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name or ID of the secret
+ // produces:
+ // - application/json
+ // responses:
+ // '200':
+ // "$ref": "#/responses/SecretInspectResponse"
+ // '404':
+ // "$ref": "#/responses/NoSuchSecret"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle(VersionedPath("/secrets/{name}"), s.APIHandler(compat.InspectSecret)).Methods(http.MethodGet)
+ r.Handle("/secrets/{name}", s.APIHandler(compat.InspectSecret)).Methods(http.MethodGet)
+ // swagger:operation DELETE /secrets/{name} compat RemoveSecret
+ // ---
+ // tags:
+ // - secrets (compat)
+ // summary: Remove secret
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name or ID of the secret
+ // produces:
+ // - application/json
+ // responses:
+ // '204':
+ // description: no error
+ // '404':
+ // "$ref": "#/responses/NoSuchSecret"
+ // '500':
+ // "$ref": "#/responses/InternalError"
+ r.Handle(VersionedPath("/secrets/{name}"), s.APIHandler(compat.RemoveSecret)).Methods(http.MethodDelete)
+ r.Handle("/secret/{name}", s.APIHandler(compat.RemoveSecret)).Methods(http.MethodDelete)
+
+ r.Handle(VersionedPath("/secrets/{name}/update"), s.APIHandler(compat.UpdateSecret)).Methods(http.MethodPost)
+ r.Handle("/secrets/{name}/update", s.APIHandler(compat.UpdateSecret)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index d612041f6..6926eda62 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -124,6 +124,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
server.registerPlayHandlers,
server.registerPluginsHandlers,
server.registerPodsHandlers,
+ server.registerSecretHandlers,
server.RegisterSwaggerHandlers,
server.registerSwarmHandlers,
server.registerSystemHandlers,
diff --git a/pkg/api/tags.yaml b/pkg/api/tags.yaml
index 0cfb3f440..bb56098eb 100644
--- a/pkg/api/tags.yaml
+++ b/pkg/api/tags.yaml
@@ -13,6 +13,8 @@ tags:
description: Actions related to pods
- name: volumes
description: Actions related to volumes
+ - name: secrets
+ description: Actions related to secrets
- name: system
description: Actions related to Podman engine
- name: containers (compat)
@@ -25,5 +27,7 @@ tags:
description: Actions related to compatibility networks
- name: volumes (compat)
description: Actions related to volumes for the compatibility endpoints
+ - name: secrets (compat)
+ description: Actions related to secrets for the compatibility endpoints
- name: system (compat)
description: Actions related to Podman and compatibility engines
diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go
index 02765816f..8ea09b881 100644
--- a/pkg/bindings/images/build.go
+++ b/pkg/bindings/images/build.go
@@ -31,36 +31,31 @@ import (
func Build(ctx context.Context, containerFiles []string, options entities.BuildOptions) (*entities.BuildReport, error) {
params := url.Values{}
- if t := options.Output; len(t) > 0 {
- params.Set("t", t)
+ if caps := options.AddCapabilities; len(caps) > 0 {
+ c, err := jsoniter.MarshalToString(caps)
+ if err != nil {
+ return nil, err
+ }
+ params.Add("addcaps", c)
}
+
+ if annotations := options.Annotations; len(annotations) > 0 {
+ l, err := jsoniter.MarshalToString(annotations)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("annotations", l)
+ }
+ params.Add("t", options.Output)
for _, tag := range options.AdditionalTags {
params.Add("t", tag)
}
- if options.Quiet {
- params.Set("q", "1")
- }
- if options.NoCache {
- params.Set("nocache", "1")
- }
- if options.Layers {
- params.Set("layers", "1")
- }
- // TODO cachefrom
- if options.PullPolicy == buildah.PullAlways {
- params.Set("pull", "1")
- }
- if options.RemoveIntermediateCtrs {
- params.Set("rm", "1")
- }
- if options.ForceRmIntermediateCtrs {
- params.Set("forcerm", "1")
- }
- if mem := options.CommonBuildOpts.Memory; mem > 0 {
- params.Set("memory", strconv.Itoa(int(mem)))
- }
- if memSwap := options.CommonBuildOpts.MemorySwap; memSwap > 0 {
- params.Set("memswap", strconv.Itoa(int(memSwap)))
+ if buildArgs := options.Args; len(buildArgs) > 0 {
+ bArgs, err := jsoniter.MarshalToString(buildArgs)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("buildargs", bArgs)
}
if cpuShares := options.CommonBuildOpts.CPUShares; cpuShares > 0 {
params.Set("cpushares", strconv.Itoa(int(cpuShares)))
@@ -74,22 +69,38 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
if cpuQuota := options.CommonBuildOpts.CPUQuota; cpuQuota > 0 {
params.Set("cpuquota", strconv.Itoa(int(cpuQuota)))
}
- if buildArgs := options.Args; len(buildArgs) > 0 {
- bArgs, err := jsoniter.MarshalToString(buildArgs)
+ params.Set("networkmode", strconv.Itoa(int(options.ConfigureNetwork)))
+ params.Set("outputformat", options.OutputFormat)
+
+ if devices := options.Devices; len(devices) > 0 {
+ d, err := jsoniter.MarshalToString(devices)
if err != nil {
return nil, err
}
- params.Set("buildargs", bArgs)
+ params.Add("devices", d)
}
- if shmSize := options.CommonBuildOpts.ShmSize; len(shmSize) > 0 {
- shmBytes, err := units.RAMInBytes(shmSize)
+
+ if caps := options.DropCapabilities; len(caps) > 0 {
+ c, err := jsoniter.MarshalToString(caps)
if err != nil {
return nil, err
}
- params.Set("shmsize", strconv.Itoa(int(shmBytes)))
+ params.Add("dropcaps", c)
}
- if options.Squash {
- params.Set("squash", "1")
+
+ if options.ForceRmIntermediateCtrs {
+ params.Set("forcerm", "1")
+ }
+ if len(options.From) > 0 {
+ params.Set("from", options.From)
+ }
+
+ params.Set("isolation", strconv.Itoa(int(options.Isolation)))
+ if options.CommonBuildOpts.HTTPProxy {
+ params.Set("httpproxy", "1")
+ }
+ if options.Jobs != nil {
+ params.Set("jobs", strconv.FormatUint(uint64(*options.Jobs), 10))
}
if labels := options.Labels; len(labels) > 0 {
l, err := jsoniter.MarshalToString(labels)
@@ -98,10 +109,66 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
}
params.Set("labels", l)
}
- if options.CommonBuildOpts.HTTPProxy {
- params.Set("httpproxy", "1")
+ if options.Layers {
+ params.Set("layers", "1")
+ }
+ if options.LogRusage {
+ params.Set("rusage", "1")
+ }
+ if len(options.Manifest) > 0 {
+ params.Set("manifest", options.Manifest)
+ }
+ if memSwap := options.CommonBuildOpts.MemorySwap; memSwap > 0 {
+ params.Set("memswap", strconv.Itoa(int(memSwap)))
+ }
+ if mem := options.CommonBuildOpts.Memory; mem > 0 {
+ params.Set("memory", strconv.Itoa(int(mem)))
+ }
+ if options.NoCache {
+ params.Set("nocache", "1")
+ }
+ if t := options.Output; len(t) > 0 {
+ params.Set("output", t)
+ }
+ var platform string
+ if len(options.OS) > 0 {
+ platform = options.OS
+ }
+ if len(options.Architecture) > 0 {
+ if len(platform) == 0 {
+ platform = "linux"
+ }
+ platform += "/" + options.Architecture
+ }
+ if len(platform) > 0 {
+ params.Set("platform", platform)
+ }
+ if options.PullPolicy == buildah.PullAlways {
+ params.Set("pull", "1")
+ }
+ if options.Quiet {
+ params.Set("q", "1")
+ }
+ if options.RemoveIntermediateCtrs {
+ params.Set("rm", "1")
+ }
+ if hosts := options.CommonBuildOpts.AddHost; len(hosts) > 0 {
+ h, err := jsoniter.MarshalToString(hosts)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("extrahosts", h)
+ }
+ if shmSize := options.CommonBuildOpts.ShmSize; len(shmSize) > 0 {
+ shmBytes, err := units.RAMInBytes(shmSize)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("shmsize", strconv.Itoa(int(shmBytes)))
+ }
+ if options.Squash {
+ params.Set("squash", "1")
}
-
var (
headers map[string]string
err error
@@ -124,19 +191,6 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
stdout = options.Out
}
- // TODO network?
-
- var platform string
- if OS := options.OS; len(OS) > 0 {
- platform += OS
- }
- if arch := options.Architecture; len(arch) > 0 {
- platform += "/" + arch
- }
- if len(platform) > 0 {
- params.Set("platform", platform)
- }
-
entries := make([]string, len(containerFiles))
copy(entries, containerFiles)
entries = append(entries, options.ContextDirectory)
diff --git a/pkg/bindings/secrets/secrets.go b/pkg/bindings/secrets/secrets.go
new file mode 100644
index 000000000..3fd70dcad
--- /dev/null
+++ b/pkg/bindings/secrets/secrets.go
@@ -0,0 +1,78 @@
+package secrets
+
+import (
+ "context"
+ "io"
+ "net/http"
+
+ "github.com/containers/podman/v2/pkg/bindings"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+)
+
+// List returns information about existing secrets in the form of a slice.
+func List(ctx context.Context, options *ListOptions) ([]*entities.SecretInfoReport, error) {
+ var (
+ secrs []*entities.SecretInfoReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/secrets/json", nil, nil)
+ if err != nil {
+ return secrs, err
+ }
+ return secrs, response.Process(&secrs)
+}
+
+// Inspect returns low-level information about a secret.
+func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*entities.SecretInfoReport, error) {
+ var (
+ inspect *entities.SecretInfoReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/secrets/%s/json", nil, nil, nameOrID)
+ if err != nil {
+ return inspect, err
+ }
+ return inspect, response.Process(&inspect)
+}
+
+// Remove removes a secret from storage
+func Remove(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/secrets/%s", nil, nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Create creates a secret given some data
+func Create(ctx context.Context, reader io.Reader, options *CreateOptions) (*entities.SecretCreateReport, error) {
+ var (
+ create *entities.SecretCreateReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ params, err := options.ToParams()
+ if err != nil {
+ return nil, err
+ }
+
+ response, err := conn.DoRequest(reader, http.MethodPost, "/secrets/create", params, nil)
+ if err != nil {
+ return nil, err
+ }
+ return create, response.Process(&create)
+}
diff --git a/pkg/bindings/secrets/types.go b/pkg/bindings/secrets/types.go
new file mode 100644
index 000000000..a98e894dc
--- /dev/null
+++ b/pkg/bindings/secrets/types.go
@@ -0,0 +1,23 @@
+package secrets
+
+//go:generate go run ../generator/generator.go ListOptions
+// ListOptions are optional options for inspecting secrets
+type ListOptions struct {
+}
+
+//go:generate go run ../generator/generator.go InspectOptions
+// InspectOptions are optional options for inspecting secrets
+type InspectOptions struct {
+}
+
+//go:generate go run ../generator/generator.go RemoveOptions
+// RemoveOptions are optional options for removing secrets
+type RemoveOptions struct {
+}
+
+//go:generate go run ../generator/generator.go CreateOptions
+// CreateOptions are optional options for Creating secrets
+type CreateOptions struct {
+ Driver *string
+ Name *string
+}
diff --git a/pkg/bindings/secrets/types_create_options.go b/pkg/bindings/secrets/types_create_options.go
new file mode 100644
index 000000000..84cf38fa3
--- /dev/null
+++ b/pkg/bindings/secrets/types_create_options.go
@@ -0,0 +1,107 @@
+package secrets
+
+import (
+ "net/url"
+ "reflect"
+ "strings"
+
+ "github.com/containers/podman/v2/pkg/bindings/util"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+)
+
+/*
+This file is generated automatically by go generate. Do not edit.
+*/
+
+// Changed
+func (o *CreateOptions) Changed(fieldName string) bool {
+ r := reflect.ValueOf(o)
+ value := reflect.Indirect(r).FieldByName(fieldName)
+ return !value.IsNil()
+}
+
+// ToParams
+func (o *CreateOptions) 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 {
+ case util.IsSimpleType(f):
+ params.Set(fieldName, util.SimpleTypeToParam(f))
+ case f.Kind() == reflect.Slice:
+ for i := 0; i < f.Len(); i++ {
+ elem := f.Index(i)
+ if util.IsSimpleType(elem) {
+ params.Add(fieldName, util.SimpleTypeToParam(elem))
+ } else {
+ return nil, errors.New("slices must contain only simple types")
+ }
+ }
+ case f.Kind() == 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
+}
+
+// WithDriver
+func (o *CreateOptions) WithDriver(value string) *CreateOptions {
+ v := &value
+ o.Driver = v
+ return o
+}
+
+// GetDriver
+func (o *CreateOptions) GetDriver() string {
+ var driver string
+ if o.Driver == nil {
+ return driver
+ }
+ return *o.Driver
+}
+
+// WithName
+func (o *CreateOptions) WithName(value string) *CreateOptions {
+ v := &value
+ o.Name = v
+ return o
+}
+
+// GetName
+func (o *CreateOptions) GetName() string {
+ var name string
+ if o.Name == nil {
+ return name
+ }
+ return *o.Name
+}
diff --git a/pkg/bindings/secrets/types_inspect_options.go b/pkg/bindings/secrets/types_inspect_options.go
new file mode 100644
index 000000000..cd36b0531
--- /dev/null
+++ b/pkg/bindings/secrets/types_inspect_options.go
@@ -0,0 +1,75 @@
+package secrets
+
+import (
+ "net/url"
+ "reflect"
+ "strings"
+
+ "github.com/containers/podman/v2/pkg/bindings/util"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+)
+
+/*
+This file is generated automatically by go generate. Do not edit.
+*/
+
+// Changed
+func (o *InspectOptions) Changed(fieldName string) bool {
+ r := reflect.ValueOf(o)
+ value := reflect.Indirect(r).FieldByName(fieldName)
+ return !value.IsNil()
+}
+
+// ToParams
+func (o *InspectOptions) 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 {
+ case util.IsSimpleType(f):
+ params.Set(fieldName, util.SimpleTypeToParam(f))
+ case f.Kind() == reflect.Slice:
+ for i := 0; i < f.Len(); i++ {
+ elem := f.Index(i)
+ if util.IsSimpleType(elem) {
+ params.Add(fieldName, util.SimpleTypeToParam(elem))
+ } else {
+ return nil, errors.New("slices must contain only simple types")
+ }
+ }
+ case f.Kind() == 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
+}
diff --git a/pkg/bindings/secrets/types_list_options.go b/pkg/bindings/secrets/types_list_options.go
new file mode 100644
index 000000000..d313d8f73
--- /dev/null
+++ b/pkg/bindings/secrets/types_list_options.go
@@ -0,0 +1,75 @@
+package secrets
+
+import (
+ "net/url"
+ "reflect"
+ "strings"
+
+ "github.com/containers/podman/v2/pkg/bindings/util"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+)
+
+/*
+This file is generated automatically by go generate. Do not edit.
+*/
+
+// Changed
+func (o *ListOptions) Changed(fieldName string) bool {
+ r := reflect.ValueOf(o)
+ value := reflect.Indirect(r).FieldByName(fieldName)
+ return !value.IsNil()
+}
+
+// ToParams
+func (o *ListOptions) 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 {
+ case util.IsSimpleType(f):
+ params.Set(fieldName, util.SimpleTypeToParam(f))
+ case f.Kind() == reflect.Slice:
+ for i := 0; i < f.Len(); i++ {
+ elem := f.Index(i)
+ if util.IsSimpleType(elem) {
+ params.Add(fieldName, util.SimpleTypeToParam(elem))
+ } else {
+ return nil, errors.New("slices must contain only simple types")
+ }
+ }
+ case f.Kind() == 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
+}
diff --git a/pkg/bindings/secrets/types_remove_options.go b/pkg/bindings/secrets/types_remove_options.go
new file mode 100644
index 000000000..ca970e30e
--- /dev/null
+++ b/pkg/bindings/secrets/types_remove_options.go
@@ -0,0 +1,75 @@
+package secrets
+
+import (
+ "net/url"
+ "reflect"
+ "strings"
+
+ "github.com/containers/podman/v2/pkg/bindings/util"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+)
+
+/*
+This file is generated automatically by go generate. Do not edit.
+*/
+
+// Changed
+func (o *RemoveOptions) Changed(fieldName string) bool {
+ r := reflect.ValueOf(o)
+ value := reflect.Indirect(r).FieldByName(fieldName)
+ return !value.IsNil()
+}
+
+// ToParams
+func (o *RemoveOptions) 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 {
+ case util.IsSimpleType(f):
+ params.Set(fieldName, util.SimpleTypeToParam(f))
+ case f.Kind() == reflect.Slice:
+ for i := 0; i < f.Len(); i++ {
+ elem := f.Index(i)
+ if util.IsSimpleType(elem) {
+ params.Add(fieldName, util.SimpleTypeToParam(elem))
+ } else {
+ return nil, errors.New("slices must contain only simple types")
+ }
+ }
+ case f.Kind() == 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
+}
diff --git a/pkg/bindings/test/secrets_test.go b/pkg/bindings/test/secrets_test.go
new file mode 100644
index 000000000..17c043e4b
--- /dev/null
+++ b/pkg/bindings/test/secrets_test.go
@@ -0,0 +1,133 @@
+package test_bindings
+
+import (
+ "context"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/containers/podman/v2/pkg/bindings"
+ "github.com/containers/podman/v2/pkg/bindings/secrets"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman secrets", func() {
+ var (
+ bt *bindingTest
+ s *gexec.Session
+ connText context.Context
+ err error
+ )
+
+ BeforeEach(func() {
+ bt = newBindingTest()
+ bt.RestoreImagesFromCache()
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ connText, err = bindings.NewConnection(context.Background(), bt.sock)
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("create secret", func() {
+ r := strings.NewReader("mysecret")
+ name := "mysecret"
+ opts := &secrets.CreateOptions{
+ Name: &name,
+ }
+ _, err := secrets.Create(connText, r, opts)
+ Expect(err).To(BeNil())
+
+ // should not be allowed to create duplicate secret name
+ _, err = secrets.Create(connText, r, opts)
+ Expect(err).To(Not(BeNil()))
+ })
+
+ It("inspect secret", func() {
+ r := strings.NewReader("mysecret")
+ name := "mysecret"
+ opts := &secrets.CreateOptions{
+ Name: &name,
+ }
+ _, err := secrets.Create(connText, r, opts)
+ Expect(err).To(BeNil())
+
+ data, err := secrets.Inspect(connText, name, nil)
+ Expect(err).To(BeNil())
+ Expect(data.Spec.Name).To(Equal(name))
+
+ // inspecting non-existent secret should fail
+ data, err = secrets.Inspect(connText, "notasecret", nil)
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+ })
+
+ It("list secret", func() {
+ r := strings.NewReader("mysecret")
+ name := "mysecret"
+ opts := &secrets.CreateOptions{
+ Name: &name,
+ }
+ _, err := secrets.Create(connText, r, opts)
+ Expect(err).To(BeNil())
+
+ data, err := secrets.List(connText, nil)
+ Expect(err).To(BeNil())
+ Expect(data[0].Spec.Name).To(Equal(name))
+ })
+
+ It("list multiple secret", func() {
+ r := strings.NewReader("mysecret")
+ name := "mysecret"
+ opts := &secrets.CreateOptions{
+ Name: &name,
+ }
+ _, err := secrets.Create(connText, r, opts)
+ Expect(err).To(BeNil())
+
+ r2 := strings.NewReader("mysecret2")
+ name2 := "mysecret2"
+ opts2 := &secrets.CreateOptions{
+ Name: &name2,
+ }
+ _, err = secrets.Create(connText, r2, opts2)
+ Expect(err).To(BeNil())
+
+ data, err := secrets.List(connText, nil)
+ Expect(err).To(BeNil())
+ Expect(len(data)).To(Equal(2))
+ })
+
+ It("list no secrets", func() {
+ data, err := secrets.List(connText, nil)
+ Expect(err).To(BeNil())
+ Expect(len(data)).To(Equal(0))
+ })
+
+ It("remove secret", func() {
+ r := strings.NewReader("mysecret")
+ name := "mysecret"
+ opts := &secrets.CreateOptions{
+ Name: &name,
+ }
+ _, err := secrets.Create(connText, r, opts)
+ Expect(err).To(BeNil())
+
+ err = secrets.Remove(connText, name)
+ Expect(err).To(BeNil())
+
+ // removing non-existent secret should fail
+ err = secrets.Remove(connText, "nosecret")
+ Expect(err).To(Not(BeNil()))
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+ })
+
+})
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 2c97d7baf..d43b422a3 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -82,6 +82,10 @@ type ContainerEngine interface {
PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error)
PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error)
SetupRootless(ctx context.Context, cmd *cobra.Command) error
+ SecretCreate(ctx context.Context, name string, reader io.Reader, options SecretCreateOptions) (*SecretCreateReport, error)
+ SecretInspect(ctx context.Context, nameOrIDs []string) ([]*SecretInfoReport, []error, error)
+ SecretList(ctx context.Context) ([]*SecretInfoReport, error)
+ SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error)
Shutdown(ctx context.Context)
SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error)
Unshare(ctx context.Context, args []string) error
diff --git a/pkg/domain/entities/secrets.go b/pkg/domain/entities/secrets.go
new file mode 100644
index 000000000..3cad4c099
--- /dev/null
+++ b/pkg/domain/entities/secrets.go
@@ -0,0 +1,104 @@
+package entities
+
+import (
+ "time"
+
+ "github.com/containers/podman/v2/pkg/errorhandling"
+)
+
+type SecretCreateReport struct {
+ ID string
+}
+
+type SecretCreateOptions struct {
+ Driver string
+}
+
+type SecretListRequest struct {
+ Filters map[string]string
+}
+
+type SecretListReport struct {
+ ID string
+ Name string
+ Driver string
+ CreatedAt string
+ UpdatedAt string
+}
+
+type SecretRmOptions struct {
+ All bool
+}
+
+type SecretRmReport struct {
+ ID string
+ Err error
+}
+
+type SecretInfoReport struct {
+ ID string
+ CreatedAt time.Time
+ UpdatedAt time.Time
+ Spec SecretSpec
+}
+
+type SecretSpec struct {
+ Name string
+ Driver SecretDriverSpec
+}
+
+type SecretDriverSpec struct {
+ Name string
+ Options map[string]string
+}
+
+// swagger:model SecretCreate
+type SecretCreateRequest struct {
+ // User-defined name of the secret.
+ Name string
+ // Base64-url-safe-encoded (RFC 4648) data to store as secret.
+ Data string
+ // Driver represents a driver (default "file")
+ Driver SecretDriverSpec
+}
+
+// Secret create response
+// swagger:response SecretCreateResponse
+type SwagSecretCreateResponse struct {
+ // in:body
+ Body struct {
+ SecretCreateReport
+ }
+}
+
+// Secret list response
+// swagger:response SecretListResponse
+type SwagSecretListResponse struct {
+ // in:body
+ Body []*SecretInfoReport
+}
+
+// Secret inspect response
+// swagger:response SecretInspectResponse
+type SwagSecretInspectResponse struct {
+ // in:body
+ Body SecretInfoReport
+}
+
+// No such secret
+// swagger:response NoSuchSecret
+type SwagErrNoSuchSecret struct {
+ // in:body
+ Body struct {
+ errorhandling.ErrorModel
+ }
+}
+
+// Secret in use
+// swagger:response SecretInUse
+type SwagErrSecretInUse struct {
+ // in:body
+ Body struct {
+ errorhandling.ErrorModel
+ }
+}
diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go
new file mode 100644
index 000000000..b1fe60e01
--- /dev/null
+++ b/pkg/domain/infra/abi/secrets.go
@@ -0,0 +1,138 @@
+package abi
+
+import (
+ "context"
+ "io"
+ "io/ioutil"
+ "path/filepath"
+
+ "github.com/containers/common/pkg/secrets"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader io.Reader, options entities.SecretCreateOptions) (*entities.SecretCreateReport, error) {
+ data, _ := ioutil.ReadAll(reader)
+ secretsPath := ic.Libpod.GetSecretsStorageDir()
+ manager, err := secrets.NewManager(secretsPath)
+ if err != nil {
+ return nil, err
+ }
+ driverOptions := make(map[string]string)
+
+ if options.Driver == "" {
+ options.Driver = "file"
+ }
+ if options.Driver == "file" {
+ driverOptions["path"] = filepath.Join(secretsPath, "filedriver")
+ }
+ secretID, err := manager.Store(name, data, options.Driver, driverOptions)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.SecretCreateReport{
+ ID: secretID,
+ }, nil
+}
+
+func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*entities.SecretInfoReport, []error, error) {
+ secretsPath := ic.Libpod.GetSecretsStorageDir()
+ manager, err := secrets.NewManager(secretsPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ errs := make([]error, 0, len(nameOrIDs))
+ reports := make([]*entities.SecretInfoReport, 0, len(nameOrIDs))
+ for _, nameOrID := range nameOrIDs {
+ secret, err := manager.Lookup(nameOrID)
+ if err != nil {
+ if errors.Cause(err).Error() == "no such secret" {
+ errs = append(errs, err)
+ continue
+ } else {
+ return nil, nil, errors.Wrapf(err, "error inspecting secret %s", nameOrID)
+ }
+ }
+ report := &entities.SecretInfoReport{
+ ID: secret.ID,
+ CreatedAt: secret.CreatedAt,
+ UpdatedAt: secret.CreatedAt,
+ Spec: entities.SecretSpec{
+ Name: secret.Name,
+ Driver: entities.SecretDriverSpec{
+ Name: secret.Driver,
+ },
+ },
+ }
+ reports = append(reports, report)
+
+ }
+
+ return reports, errs, nil
+}
+
+func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretInfoReport, error) {
+ secretsPath := ic.Libpod.GetSecretsStorageDir()
+ manager, err := secrets.NewManager(secretsPath)
+ if err != nil {
+ return nil, err
+ }
+ secretList, err := manager.List()
+ if err != nil {
+ return nil, err
+ }
+ report := make([]*entities.SecretInfoReport, 0, len(secretList))
+ for _, secret := range secretList {
+ reportItem := entities.SecretInfoReport{
+ ID: secret.ID,
+ CreatedAt: secret.CreatedAt,
+ UpdatedAt: secret.CreatedAt,
+ Spec: entities.SecretSpec{
+ Name: secret.Name,
+ Driver: entities.SecretDriverSpec{
+ Name: secret.Driver,
+ Options: secret.DriverOptions,
+ },
+ },
+ }
+ report = append(report, &reportItem)
+ }
+ return report, nil
+}
+
+func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, options entities.SecretRmOptions) ([]*entities.SecretRmReport, error) {
+ var (
+ err error
+ toRemove []string
+ reports = []*entities.SecretRmReport{}
+ )
+ secretsPath := ic.Libpod.GetSecretsStorageDir()
+ manager, err := secrets.NewManager(secretsPath)
+ if err != nil {
+ return nil, err
+ }
+ toRemove = nameOrIDs
+ if options.All {
+ allSecrs, err := manager.List()
+ if err != nil {
+ return nil, err
+ }
+ for _, secr := range allSecrs {
+ toRemove = append(toRemove, secr.ID)
+ }
+ }
+ for _, nameOrID := range toRemove {
+ deletedID, err := manager.Delete(nameOrID)
+ if err == nil || errors.Cause(err).Error() == "no such secret" {
+ reports = append(reports, &entities.SecretRmReport{
+ Err: err,
+ ID: deletedID,
+ })
+ continue
+ } else {
+ return nil, err
+ }
+ }
+
+ return reports, nil
+}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index f10c8c175..daad911cd 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -350,17 +350,6 @@ func (ir *ImageEngine) Build(_ context.Context, containerFiles []string, opts en
if err != nil {
return nil, err
}
- // For remote clients, if the option for writing to a file was
- // selected, we need to write to the *client's* filesystem.
- if len(opts.IIDFile) > 0 {
- f, err := os.Create(opts.IIDFile)
- if err != nil {
- return nil, err
- }
- if _, err := f.WriteString(report.ID); err != nil {
- return nil, err
- }
- }
return report, nil
}
diff --git a/pkg/domain/infra/tunnel/secrets.go b/pkg/domain/infra/tunnel/secrets.go
new file mode 100644
index 000000000..f7c0f7d13
--- /dev/null
+++ b/pkg/domain/infra/tunnel/secrets.go
@@ -0,0 +1,82 @@
+package tunnel
+
+import (
+ "context"
+ "io"
+
+ "github.com/containers/podman/v2/pkg/bindings/secrets"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/errorhandling"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader io.Reader, options entities.SecretCreateOptions) (*entities.SecretCreateReport, error) {
+ opts := new(secrets.CreateOptions).WithDriver(options.Driver).WithName(name)
+ created, _ := secrets.Create(ic.ClientCtx, reader, opts)
+ return created, nil
+}
+
+func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*entities.SecretInfoReport, []error, error) {
+ allInspect := make([]*entities.SecretInfoReport, 0, len(nameOrIDs))
+ errs := make([]error, 0, len(nameOrIDs))
+ for _, name := range nameOrIDs {
+ inspected, err := secrets.Inspect(ic.ClientCtx, name, nil)
+ if err != nil {
+ errModel, ok := err.(errorhandling.ErrorModel)
+ if !ok {
+ return nil, nil, err
+ }
+ if errModel.ResponseCode == 404 {
+ errs = append(errs, errors.Errorf("no such secret %q", name))
+ continue
+ }
+ return nil, nil, err
+ }
+ allInspect = append(allInspect, inspected)
+ }
+ return allInspect, errs, nil
+}
+
+func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretInfoReport, error) {
+ secrs, _ := secrets.List(ic.ClientCtx, nil)
+ return secrs, nil
+}
+
+func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, options entities.SecretRmOptions) ([]*entities.SecretRmReport, error) {
+ allRm := make([]*entities.SecretRmReport, 0, len(nameOrIDs))
+ if options.All {
+ allSecrets, err := secrets.List(ic.ClientCtx, nil)
+ if err != nil {
+ return nil, err
+ }
+ for _, secret := range allSecrets {
+ allRm = append(allRm, &entities.SecretRmReport{
+ Err: secrets.Remove(ic.ClientCtx, secret.ID),
+ ID: secret.ID,
+ })
+ }
+ return allRm, nil
+ }
+ for _, name := range nameOrIDs {
+ secret, err := secrets.Inspect(ic.ClientCtx, name, nil)
+ if err != nil {
+ errModel, ok := err.(errorhandling.ErrorModel)
+ if !ok {
+ return nil, err
+ }
+ if errModel.ResponseCode == 404 {
+ allRm = append(allRm, &entities.SecretRmReport{
+ Err: errors.Errorf("no secret with name or id %q: no such secret ", name),
+ ID: "",
+ })
+ continue
+ }
+ }
+ allRm = append(allRm, &entities.SecretRmReport{
+ Err: secrets.Remove(ic.ClientCtx, name),
+ ID: secret.ID,
+ })
+
+ }
+ return allRm, nil
+}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 1bc050b00..74291325c 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -359,6 +359,10 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
options = append(options, libpod.WithHealthCheck(s.ContainerHealthCheckConfig.HealthConfig))
logrus.Debugf("New container has a health check")
}
+
+ if len(s.Secrets) != 0 {
+ options = append(options, libpod.WithSecrets(s.Secrets))
+ }
return options, nil
}
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index a6cc0a730..732579bf0 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -237,6 +237,9 @@ type ContainerStorageConfig struct {
// If not set, the default of rslave will be used.
// Optional.
RootfsPropagation string `json:"rootfs_propagation,omitempty"`
+ // Secrets are the secrets that will be added to the container
+ // Optional.
+ Secrets []string `json:"secrets,omitempty"`
}
// ContainerSecurityConfig is a container's security features, including