Repository: Netflix/lemur

Issue:
Replacing multiple certs causes integrity error.
When creating a new cert, if you tell Lemur it will be replacing multiple certs, you get the following error:

> (psycopg2.IntegrityError) duplicate key value violates unique constraint "certificate_replacement_associations_ix" DETAIL: Key (certificate_id, certificate_id)=(XXXXX, XXXXX) already exists. [SQL: 'INSERT INTO certificate_replacement_associations (replaced_certificate_id, certificate_id) VALUES (%(replaced_certificate_id)s, %(certificate_id)s)'] [parameters: ({'replaced_certificate_id': YYYYY, 'certificate_id': XXXXX}, {'replaced_certificate_id': ZZZ, 'certificate_id': XXXXX})]

Either the UI should restrict the replace to a single cert or the DB model should support multiple.


Discussion:
This is caused by the following unique constraint:
https://github.com/Netflix/lemur/blob/master/lemur/migrations/versions/412b22cb656a_.py#L37

This shouldn't ever be triggered as the certificate creates a new id upon creation and is then associated. One possible explanation might be the way SQLAlchemy is evaluating the IDs, we might need to commit the certificate before attempting to associate the replaced certificates. 

Resolving patch:
diff --git a/lemur/migrations/versions/5e680529b666_.py b/lemur/migrations/versions/5e680529b666_.py
--- a/lemur/migrations/versions/5e680529b666_.py
+++ b/lemur/migrations/versions/5e680529b666_.py
@@ -15,16 +15,12 @@
 
 
 def upgrade():
-    ### commands auto generated by Alembic - please adjust! ###
     op.add_column('endpoints', sa.Column('sensitive', sa.Boolean(), nullable=True))
     op.add_column('endpoints', sa.Column('source_id', sa.Integer(), nullable=True))
     op.create_foreign_key(None, 'endpoints', 'sources', ['source_id'], ['id'])
-    ### end Alembic commands ###
 
 
 def downgrade():
-    ### commands auto generated by Alembic - please adjust! ###
     op.drop_constraint(None, 'endpoints', type_='foreignkey')
     op.drop_column('endpoints', 'source_id')
     op.drop_column('endpoints', 'sensitive')
-    ### end Alembic commands ###
\ No newline at end of file
diff --git a/lemur/migrations/versions/8ae67285ff14_.py b/lemur/migrations/versions/8ae67285ff14_.py
new file mode 100644
--- /dev/null
+++ b/lemur/migrations/versions/8ae67285ff14_.py
@@ -0,0 +1,24 @@
+"""Modifies the unique index for the certificate replacements
+
+Revision ID: 8ae67285ff14
+Revises: 5e680529b666
+Create Date: 2017-05-10 11:56:13.999332
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '8ae67285ff14'
+down_revision = '5e680529b666'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.drop_index('certificate_replacement_associations_ix')
+    op.create_index('certificate_replacement_associations_ix', 'certificate_replacement_associations', ['replaced_certificate_id', 'certificate_id'], unique=True)
+
+
+def downgrade():
+    op.drop_index('certificate_replacement_associations_ix')
+    op.create_index('certificate_replacement_associations_ix', 'certificate_replacement_associations', ['certificate_id', 'certificate_id'], unique=True)
diff --git a/lemur/models.py b/lemur/models.py
--- a/lemur/models.py
+++ b/lemur/models.py
@@ -53,7 +53,7 @@
                                                        ForeignKey('certificates.id', ondelete='cascade'))
                                                 )
 
-Index('certificate_replacement_associations_ix', certificate_replacement_associations.c.certificate_id, certificate_replacement_associations.c.certificate_id)
+Index('certificate_replacement_associations_ix', certificate_replacement_associations.c.replaced_certificate_id, certificate_replacement_associations.c.certificate_id)
 
 roles_authorities = db.Table('roles_authorities',
                              Column('authority_id', Integer, ForeignKey('authorities.id')),


Test patch: