mirror of
https://github.com/containers/podman-compose.git
synced 2025-06-07 21:46:41 +02:00
Merge pull request #971 from mokibit/type-env-secret-support
Add support for environment variable secrets
This commit is contained in:
commit
84f7fdd7da
@ -563,10 +563,11 @@ def get_secret_args(compose, cnt, secret, podman_is_building=False):
|
|||||||
dest_file = ""
|
dest_file = ""
|
||||||
secret_opts = ""
|
secret_opts = ""
|
||||||
|
|
||||||
target = None if is_str(secret) else secret.get("target", None)
|
secret_target = None if is_str(secret) else secret.get("target", None)
|
||||||
uid = None if is_str(secret) else secret.get("uid", None)
|
secret_uid = None if is_str(secret) else secret.get("uid", None)
|
||||||
gid = None if is_str(secret) else secret.get("gid", None)
|
secret_gid = None if is_str(secret) else secret.get("gid", None)
|
||||||
mode = None if is_str(secret) else secret.get("mode", None)
|
secret_mode = None if is_str(secret) else secret.get("mode", None)
|
||||||
|
secret_type = None if is_str(secret) else secret.get("type", None)
|
||||||
|
|
||||||
if source_file:
|
if source_file:
|
||||||
# assemble path for source file first, because we need it for all cases
|
# assemble path for source file first, because we need it for all cases
|
||||||
@ -575,29 +576,29 @@ def get_secret_args(compose, cnt, secret, podman_is_building=False):
|
|||||||
|
|
||||||
if podman_is_building:
|
if podman_is_building:
|
||||||
# pass file secrets to "podman build" with param --secret
|
# pass file secrets to "podman build" with param --secret
|
||||||
if not target:
|
if not secret_target:
|
||||||
secret_id = secret_name
|
secret_id = secret_name
|
||||||
elif "/" in target:
|
elif "/" in secret_target:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f'ERROR: Build secret "{secret_name}" has invalid target "{target}". '
|
f'ERROR: Build secret "{secret_name}" has invalid target "{secret_target}". '
|
||||||
+ "(Expected plain filename without directory as target.)"
|
+ "(Expected plain filename without directory as target.)"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
secret_id = target
|
secret_id = secret_target
|
||||||
volume_ref = ["--secret", f"id={secret_id},src={source_file}"]
|
volume_ref = ["--secret", f"id={secret_id},src={source_file}"]
|
||||||
else:
|
else:
|
||||||
# pass file secrets to "podman run" as volumes
|
# pass file secrets to "podman run" as volumes
|
||||||
if not target:
|
if not secret_target:
|
||||||
dest_file = f"/run/secrets/{secret_name}"
|
dest_file = "/run/secrets/{}".format(secret_name)
|
||||||
elif not target.startswith("/"):
|
elif not secret_target.startswith("/"):
|
||||||
sec = target if target else secret_name
|
sec = secret_target if secret_target else secret_name
|
||||||
dest_file = f"/run/secrets/{sec}"
|
dest_file = f"/run/secrets/{sec}"
|
||||||
else:
|
else:
|
||||||
dest_file = target
|
dest_file = secret_target
|
||||||
volume_ref = ["--volume", f"{source_file}:{dest_file}:ro,rprivate,rbind"]
|
volume_ref = ["--volume", f"{source_file}:{dest_file}:ro,rprivate,rbind"]
|
||||||
|
|
||||||
if uid or gid or mode:
|
if secret_uid or secret_gid or secret_mode:
|
||||||
sec = target if target else secret_name
|
sec = secret_target if secret_target else secret_name
|
||||||
log.warning(
|
log.warning(
|
||||||
"WARNING: Service %s uses secret %s with uid, gid, or mode."
|
"WARNING: Service %s uses secret %s with uid, gid, or mode."
|
||||||
+ " These fields are not supported by this implementation of the Compose file",
|
+ " These fields are not supported by this implementation of the Compose file",
|
||||||
@ -613,9 +614,11 @@ def get_secret_args(compose, cnt, secret, podman_is_building=False):
|
|||||||
# podman-create commands, albeit we can only support a 1:1 mapping
|
# podman-create commands, albeit we can only support a 1:1 mapping
|
||||||
# at the moment
|
# at the moment
|
||||||
if declared_secret.get("external", False) or declared_secret.get("name", None):
|
if declared_secret.get("external", False) or declared_secret.get("name", None):
|
||||||
secret_opts += f",uid={uid}" if uid else ""
|
secret_opts += f",uid={secret_uid}" if secret_uid else ""
|
||||||
secret_opts += f",gid={gid}" if gid else ""
|
secret_opts += f",gid={secret_gid}" if secret_gid else ""
|
||||||
secret_opts += f",mode={mode}" if mode else ""
|
secret_opts += f",mode={secret_mode}" if secret_mode else ""
|
||||||
|
secret_opts += f",type={secret_type}" if secret_type else ""
|
||||||
|
secret_opts += f",target={secret_target}" if secret_target and secret_type == "env" else ""
|
||||||
# The target option is only valid for type=env,
|
# The target option is only valid for type=env,
|
||||||
# which in an ideal world would work
|
# which in an ideal world would work
|
||||||
# for type=mount as well.
|
# for type=mount as well.
|
||||||
@ -628,14 +631,14 @@ def get_secret_args(compose, cnt, secret, podman_is_building=False):
|
|||||||
)
|
)
|
||||||
if ext_name and ext_name != secret_name:
|
if ext_name and ext_name != secret_name:
|
||||||
raise ValueError(err_str.format(secret_name, ext_name))
|
raise ValueError(err_str.format(secret_name, ext_name))
|
||||||
if target and target != secret_name:
|
if secret_target and secret_target != secret_name and secret_type != 'env':
|
||||||
raise ValueError(err_str.format(target, secret_name))
|
raise ValueError(err_str.format(secret_target, secret_name))
|
||||||
if target:
|
if secret_target and secret_type != 'env':
|
||||||
log.warning(
|
log.warning(
|
||||||
'WARNING: Service "%s" uses target: "%s" for secret: "%s".'
|
'WARNING: Service "%s" uses target: "%s" for secret: "%s".'
|
||||||
+ " That is un-supported and a no-op and is ignored.",
|
+ " That is un-supported and a no-op and is ignored.",
|
||||||
cnt["_service"],
|
cnt["_service"],
|
||||||
target,
|
secret_target,
|
||||||
secret_name,
|
secret_name,
|
||||||
)
|
)
|
||||||
return ["--secret", "{}{}".format(secret_name, secret_opts)]
|
return ["--secret", "{}{}".format(secret_name, secret_opts)]
|
||||||
|
91
pytests/test_container_to_args_secrets.py
Normal file
91
pytests/test_container_to_args_secrets.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from podman_compose import container_to_args
|
||||||
|
|
||||||
|
from .test_container_to_args import create_compose_mock
|
||||||
|
from .test_container_to_args import get_minimal_container
|
||||||
|
|
||||||
|
|
||||||
|
class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase):
|
||||||
|
async def test_pass_secret_as_env_variable(self):
|
||||||
|
c = create_compose_mock()
|
||||||
|
c.declared_secrets = {
|
||||||
|
"my_secret": {"external": "true"} # must have external or name value
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = get_minimal_container()
|
||||||
|
cnt["secrets"] = [
|
||||||
|
{
|
||||||
|
"source": "my_secret",
|
||||||
|
"target": "ENV_SECRET",
|
||||||
|
"type": "env",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
args = await container_to_args(c, cnt)
|
||||||
|
self.assertEqual(
|
||||||
|
args,
|
||||||
|
[
|
||||||
|
"--name=project_name_service_name1",
|
||||||
|
"-d",
|
||||||
|
"--network=bridge",
|
||||||
|
"--network-alias=service_name",
|
||||||
|
"--secret",
|
||||||
|
"my_secret,type=env,target=ENV_SECRET",
|
||||||
|
"busybox",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_secret_as_env_external_true_has_no_name(self):
|
||||||
|
c = create_compose_mock()
|
||||||
|
c.declared_secrets = {
|
||||||
|
"my_secret": {
|
||||||
|
"name": "my_secret", # must have external or name value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = get_minimal_container()
|
||||||
|
cnt["_service"] = "test-service"
|
||||||
|
cnt["secrets"] = [
|
||||||
|
{
|
||||||
|
"source": "my_secret",
|
||||||
|
"target": "ENV_SECRET",
|
||||||
|
"type": "env",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
args = await container_to_args(c, cnt)
|
||||||
|
self.assertEqual(
|
||||||
|
args,
|
||||||
|
[
|
||||||
|
"--name=project_name_service_name1",
|
||||||
|
"-d",
|
||||||
|
"--network=bridge",
|
||||||
|
"--network-alias=service_name",
|
||||||
|
"--secret",
|
||||||
|
"my_secret,type=env,target=ENV_SECRET",
|
||||||
|
"busybox",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_pass_secret_as_env_variable_no_external(self):
|
||||||
|
c = create_compose_mock()
|
||||||
|
c.declared_secrets = {
|
||||||
|
"my_secret": {} # must have external or name value
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = get_minimal_container()
|
||||||
|
cnt["_service"] = "test-service"
|
||||||
|
cnt["secrets"] = [
|
||||||
|
{
|
||||||
|
"source": "my_secret",
|
||||||
|
"target": "ENV_SECRET",
|
||||||
|
"type": "env",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError) as context:
|
||||||
|
await container_to_args(c, cnt)
|
||||||
|
self.assertIn('ERROR: unparsable secret: ', str(context.exception))
|
@ -31,6 +31,9 @@ services:
|
|||||||
uid: '103'
|
uid: '103'
|
||||||
gid: '103'
|
gid: '103'
|
||||||
mode: 400
|
mode: 400
|
||||||
|
- source: my_secret
|
||||||
|
target: ENV_SECRET
|
||||||
|
type: env
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
my_secret:
|
my_secret:
|
||||||
@ -43,4 +46,3 @@ secrets:
|
|||||||
name: my_secret_3
|
name: my_secret_3
|
||||||
file_secret:
|
file_secret:
|
||||||
file: ./my_secret
|
file: ./my_secret
|
||||||
|
|
||||||
|
@ -4,3 +4,4 @@ ls -la /run/secrets/*
|
|||||||
ls -la /etc/custom_location
|
ls -la /etc/custom_location
|
||||||
cat /run/secrets/*
|
cat /run/secrets/*
|
||||||
cat /etc/custom_location
|
cat /etc/custom_location
|
||||||
|
env | grep SECRET
|
||||||
|
Loading…
x
Reference in New Issue
Block a user