Add SecretConsumerMetadatum model and its tests

This patch is the first of a series to implement the Secret Consumers spec:
https://specs.openstack.org/openstack/barbican-specs/specs/train/secret-consumers.html

Change-Id: I81155cf7edcc55b80ea4364732f09da798172b74
Signed-off-by: Moises Guimaraes de Medeiros <moguimar@redhat.com>
This commit is contained in:
Moises Guimaraes de Medeiros 2019-08-02 14:52:18 +02:00
parent 2a6fc155d2
commit 63e6979023
4 changed files with 214 additions and 0 deletions

View File

@ -39,6 +39,7 @@ def cleanup_unassociated_projects():
session = repo.get_session()
project_children_tables = [models.Order,
models.KEKDatum,
models.SecretConsumerMetadatum,
models.Secret,
models.ContainerConsumerMetadatum,
models.Container,
@ -158,6 +159,8 @@ def cleanup_all(threshold_date=None):
total += cleanup_softdeletes(models.ContainerSecret,
threshold_date=threshold_date)
total += cleanup_softdeletes(models.SecretConsumerMetadatum,
threshold_date=threshold_date)
total += cleanup_parent_with_no_child(models.Secret, models.Order,
threshold_date=threshold_date)

View File

@ -0,0 +1,61 @@
# Copyright 2019 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Add Secret Consumers table
Revision ID: 0f8c192a061f
Revises: 39cf2e645cba
Create Date: 2019-08-19 12:03:08.567230
"""
# revision identifiers, used by Alembic.
revision = "0f8c192a061f"
down_revision = "39cf2e645cba"
from alembic import op
import sqlalchemy as sa
def upgrade():
ctx = op.get_context()
con = op.get_bind()
table_exists = ctx.dialect.has_table(con.engine,
"secret_consumer_metadata")
if not table_exists:
op.create_table(
"secret_consumer_metadata",
# ModelBase
sa.Column("id", sa.String(length=36), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.Column("deleted_at", sa.DateTime(), nullable=True),
sa.Column("deleted", sa.Boolean(), nullable=False),
sa.Column("status", sa.String(length=20), nullable=False),
# SecretConsumerMetadatum
sa.Column("secret_id", sa.String(36), nullable=False),
sa.Column("project_id", sa.String(36), nullable=False),
sa.Column("service", sa.String(255), nullable=False),
sa.Column("resource_type", sa.String(255), nullable=False),
sa.Column("resource_id", sa.String(36), nullable=False),
# Constraints and Indexes
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["secret_id"], ["secrets.id"]),
sa.UniqueConstraint(
"secret_id", "resource_id", name="_secret_consumer_resource_uc"
),
sa.Index("ix_secret_consumer_metadata_secret_id", "secret_id"),
sa.Index("ix_secret_consumer_metadata_resource_id", "resource_id"),
)

View File

@ -316,6 +316,11 @@ class Secret(BASE, SoftDeleteMixIn, ModelBase):
backref="secret",
cascade="all, delete-orphan")
consumers = orm.relationship(
"SecretConsumerMetadatum",
backref="secret",
cascade="all, delete-orphan")
def __init__(self, parsed_request=None, check_exc=True):
"""Creates secret from a dict."""
super(Secret, self).__init__()
@ -1493,3 +1498,63 @@ class ProjectSecretStore(BASE, ModelBase):
"""Sub-class hook method: return dict of fields."""
return {'secret_store_id': self.secret_store_id,
'project_id': self.project_id}
class SecretConsumerMetadatum(BASE, SoftDeleteMixIn, ModelBase):
"""Stores Consumer Registrations for Secrets in the datastore.
Services can register interest in Secrets. Services will provide a
resource type and a resource id for the object that is using the Secret.
"""
__tablename__ = "secret_consumer_metadata"
secret_id = sa.Column(
sa.String(36), sa.ForeignKey("secrets.id"), index=True, nullable=False
)
project_id = sa.Column(
sa.String(36), sa.ForeignKey("projects.id"), index=True, nullable=True
)
service = sa.Column(sa.String(255), nullable=False)
resource_type = sa.Column(sa.String(255), nullable=False)
resource_id = sa.Column(sa.String(36), index=True, nullable=False)
__table_args__ = (
sa.UniqueConstraint(
"secret_id", "resource_id", name="_secret_consumer_resource_uc"
),
)
def __init__(self, secret_id=None, project_id=None, service=None,
resource_type=None, resource_id=None, check_exc=True):
"""Registers a Consumer to a Secret."""
super(SecretConsumerMetadatum, self).__init__()
msg = u._("Must supply non-None {0} argument "
"for SecretConsumerMetadatum entry.")
if secret_id is None and check_exc:
raise exception.MissingArgumentError(msg.format("secret_id"))
if project_id is None and check_exc:
raise exception.MissingArgumentError(msg.format("project_id"))
if service is None and check_exc:
raise exception.MissingArgumentError(msg.format("service"))
if resource_type is None and check_exc:
raise exception.MissingArgumentError(msg.format("resource_type"))
if resource_id is None and check_exc:
raise exception.MissingArgumentError(msg.format("resource_id"))
self.secret_id = secret_id
self.project_id = project_id
self.service = service
self.resource_type = resource_type
self.resource_id = resource_id
self.status = States.ACTIVE
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
return {
"service": self.service,
"resource_type": self.resource_type,
"resource_id": self.resource_id,
}

View File

@ -748,5 +748,90 @@ class WhenCreatingNewProjectSecretStore(utils.BaseTestCase):
project_ss.to_dict_fields()['status'])
class WhenCreatingNewSecretConsumer(utils.BaseTestCase):
def setUp(self):
super(WhenCreatingNewSecretConsumer, self).setUp()
self.secret_id = "12345secret"
self.project_id = "12345project"
self.service = "12345service"
self.resource_type = "12345resource_type"
self.resource_id = "12345resource_id"
def test_new_secret_consumer(self):
consumer = models.SecretConsumerMetadatum(
self.secret_id,
self.project_id,
self.service,
self.resource_type,
self.resource_id
)
self.assertEqual(self.secret_id, consumer.secret_id)
self.assertEqual(self.project_id, consumer.project_id)
self.assertEqual(self.service, consumer.service)
self.assertEqual(self.resource_type, consumer.resource_type)
self.assertEqual(self.resource_id, consumer.resource_id)
self.assertEqual(models.States.ACTIVE, consumer.status)
def test_to_dict_fields(self):
consumer = models.SecretConsumerMetadatum(
self.secret_id,
self.project_id,
self.service,
self.resource_type,
self.resource_id
)
fields = consumer.to_dict_fields()
self.assertEqual(self.service, fields["service"])
self.assertEqual(self.resource_type, fields["resource_type"])
self.assertEqual(self.resource_id, fields["resource_id"])
def test_should_raise_exception_when_missing_arguments(self):
self.assertRaises(
exception.MissingArgumentError,
models.SecretConsumerMetadatum,
None,
self.project_id,
self.service,
self.resource_type,
self.resource_id,
)
self.assertRaises(
exception.MissingArgumentError,
models.SecretConsumerMetadatum,
self.secret_id,
None,
self.service,
self.resource_type,
self.resource_id,
)
self.assertRaises(
exception.MissingArgumentError,
models.SecretConsumerMetadatum,
self.secret_id,
self.project_id,
None,
self.resource_type,
self.resource_id,
)
self.assertRaises(
exception.MissingArgumentError,
models.SecretConsumerMetadatum,
self.secret_id,
self.project_id,
self.service,
None,
self.resource_id,
)
self.assertRaises(
exception.MissingArgumentError,
models.SecretConsumerMetadatum,
self.secret_id,
self.project_id,
self.service,
self.resource_type,
None,
)
if __name__ == '__main__':
unittest.main()