From: dklawren Date: Mon, 9 Dec 2019 16:28:14 +0000 (-0500) Subject: Bug 1588221 - Update current s3 storage controller to only upload attachments over... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=68b7a0b5a4793da621fb040d3406f465e94eeb65;p=thirdparty%2Fbugzilla.git Bug 1588221 - Update current s3 storage controller to only upload attachments over a certain size --- diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm index e7b310834..1c15f44d3 100644 --- a/Bugzilla/Attachment.pm +++ b/Bugzilla/Attachment.pm @@ -316,7 +316,7 @@ the content of the attachment sub data { my $self = shift; - return $self->{data} //= current_storage()->retrieve($self->id); + return $self->{data} //= $self->current_storage->get_data(); } =over @@ -735,7 +735,8 @@ sub create { close($data); $data = $tmp; } - current_storage()->store($attachment->id, $data); + + $attachment->current_storage->set_data($data)->set_class(); # Return the new attachment object return $attachment; @@ -827,7 +828,7 @@ sub remove_from_db { WHERE attach_id = ?', undef, ('text/plain', 0, 1, 0, $self->id) ); $dbh->bz_commit_transaction(); - current_storage()->remove($self->id); + $self->current_storage->remove_data()->remove_class(); # As we don't call SUPER->remove_from_db we need to manually clear # memcached here. @@ -893,8 +894,31 @@ sub get_content_type { } sub current_storage { - return state $storage - //= get_storage_by_name(Bugzilla->params->{attachment_storage}); + my ($self, $override_class) = @_; + my $dbh = Bugzilla->dbh; + + # Sometimes we might want to copy attachment data from one + # storage class to another. With this param, we can override + # the current class, and then call ->set_data(). + if ($override_class) { + return $self->{current_storage} = $self->get_storage_by_name($override_class, $self); + } + + if (!$self->{current_storage}) { + my ($current_storage) + = $dbh->selectrow_array( + "SELECT storage_class FROM attachment_storage_class WHERE id = ?", + undef, $self->id); + if (!$current_storage) { + $self->{current_storage} + = $self->get_storage_by_name(Bugzilla->params->{attachment_storage}); + } + else { + $self->{current_storage} = $self->get_storage_by_name($current_storage); + } + } + + return $self->{current_storage}; } sub get_storage_names { @@ -907,20 +931,20 @@ sub get_storage_names { } sub get_storage_by_name { - my ($name) = @_; + my ($self, $name) = @_; # all options for attachment_storage need to be handled here if ($name eq 'database') { - require Bugzilla::Attachment::Database; - return Bugzilla::Attachment::Database->new(); + require Bugzilla::Attachment::Storage::Database; + return Bugzilla::Attachment::Storage::Database->new({attachment => $self}); } elsif ($name eq 'filesystem') { - require Bugzilla::Attachment::FileSystem; - return Bugzilla::Attachment::FileSystem->new(); + require Bugzilla::Attachment::Storage::FileSystem; + return Bugzilla::Attachment::Storage::FileSystem->new({attachment => $self}); } elsif ($name eq 's3') { - require Bugzilla::Attachment::S3; - return Bugzilla::Attachment::S3->new(); + require Bugzilla::Attachment::Storage::S3; + return Bugzilla::Attachment::Storage::S3->new({attachment => $self}); } else { return undef; diff --git a/Bugzilla/Attachment/Database.pm b/Bugzilla/Attachment/Database.pm deleted file mode 100644 index fd8f5f000..000000000 --- a/Bugzilla/Attachment/Database.pm +++ /dev/null @@ -1,50 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. - -package Bugzilla::Attachment::Database; - -use 5.10.1; -use strict; -use warnings; - -sub new { - return bless({}, shift); -} - -sub store { - my ($self, $attach_id, $data) = @_; - my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare( - "INSERT INTO attach_data (id, thedata) VALUES ($attach_id, ?)"); - $sth->bind_param(1, $data, $dbh->BLOB_TYPE); - $sth->execute(); -} - -sub retrieve { - my ($self, $attach_id) = @_; - my $dbh = Bugzilla->dbh; - my ($data) - = $dbh->selectrow_array("SELECT thedata FROM attach_data WHERE id = ?", - undef, $attach_id); - return $data; -} - -sub remove { - my ($self, $attach_id) = @_; - my $dbh = Bugzilla->dbh; - $dbh->do("DELETE FROM attach_data WHERE id = ?", undef, $attach_id); -} - -sub exists { - my ($self, $attach_id) = @_; - my $dbh = Bugzilla->dbh; - my ($exists) = $dbh->selectrow_array("SELECT 1 FROM attach_data WHERE id = ?", - undef, $attach_id); - return !!$exists; -} - -1; diff --git a/Bugzilla/Attachment/FileSystem.pm b/Bugzilla/Attachment/FileSystem.pm deleted file mode 100644 index f10a994fd..000000000 --- a/Bugzilla/Attachment/FileSystem.pm +++ /dev/null @@ -1,63 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. - -package Bugzilla::Attachment::FileSystem; - -use 5.10.1; -use strict; -use warnings; - -use Bugzilla::Constants qw(bz_locations); - -sub new { - return bless({}, shift); -} - -sub store { - my ($self, $attach_id, $data) = @_; - my $path = _local_path($attach_id); - mkdir($path, 0770) unless -d $path; - open(my $fh, '>', _local_file($attach_id)); - binmode($fh); - print $fh $data; - close($fh); -} - -sub retrieve { - my ($self, $attach_id) = @_; - if (open(my $fh, '<', _local_file($attach_id))) { - local $/; - binmode($fh); - my $data = <$fh>; - close($fh); - return $data; - } - return undef; -} - -sub remove { - my ($self, $attach_id) = @_; - unlink(_local_file($attach_id)); -} - -sub exists { - my ($self, $attach_id) = @_; - return -e _local_file($attach_id); -} - -sub _local_path { - my ($attach_id) = @_; - my $hash = sprintf('group.%03d', $attach_id % 1000); - return bz_locations()->{attachdir} . '/' . $hash; -} - -sub _local_file { - my ($attach_id) = @_; - return _local_path($attach_id) . '/attachment.' . $attach_id; -} - -1; diff --git a/Bugzilla/Attachment/S3.pm b/Bugzilla/Attachment/S3.pm deleted file mode 100644 index 7f4755720..000000000 --- a/Bugzilla/Attachment/S3.pm +++ /dev/null @@ -1,62 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -# This Source Code Form is "Incompatible With Secondary Licenses", as -# defined by the Mozilla Public License, v. 2.0. - -package Bugzilla::Attachment::S3; - -use 5.10.1; -use strict; -use warnings; - -use Bugzilla::Error; -use Bugzilla::S3; - -sub new { - my $s3 = Bugzilla::S3->new({ - aws_access_key_id => Bugzilla->params->{aws_access_key_id}, - aws_secret_access_key => Bugzilla->params->{aws_secret_access_key}, - secure => 1, - }); - return - bless({s3 => $s3, bucket => $s3->bucket(Bugzilla->params->{s3_bucket}),}, - shift); -} - -sub store { - my ($self, $attach_id, $data) = @_; - unless ($self->{bucket}->add_key($attach_id, $data)) { - warn "Failed to add attachment $attach_id to S3: " - . $self->{bucket}->errstr . "\n"; - ThrowCodeError('s3_add_failed', - {attach_id => $attach_id, reason => $self->{bucket}->errstr}); - } -} - -sub retrieve { - my ($self, $attach_id) = @_; - my $response = $self->{bucket}->get_key($attach_id); - if (!$response) { - warn "Failed to retrieve attachment $attach_id from S3: " - . $self->{bucket}->errstr . "\n"; - ThrowCodeError('s3_get_failed', - {attach_id => $attach_id, reason => $self->{bucket}->errstr}); - } - return $response->{value}; -} - -sub remove { - my ($self, $attach_id) = @_; - $self->{bucket}->delete_key($attach_id) - or warn "Failed to remove attachment $attach_id from S3: " - . $self->{bucket}->errstr . "\n"; -} - -sub exists { - my ($self, $attach_id) = @_; - return !!$self->{bucket}->head_key($attach_id); -} - -1; diff --git a/Bugzilla/Attachment/Storage/Base.pm b/Bugzilla/Attachment/Storage/Base.pm new file mode 100644 index 000000000..4725f05c7 --- /dev/null +++ b/Bugzilla/Attachment/Storage/Base.pm @@ -0,0 +1,38 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Attachment::Storage::Base; + +use 5.10.1; +use Moo::Role; + +use Type::Utils qw(class_type); + +requires qw(set_data get_data remove_data data_exists data_type); + +has 'attachment' => ( + is => 'ro', + required => 1, + isa => class_type({class => 'Bugzilla::Attachment'}) +); + +sub set_class { + my ($self) = @_; + Bugzilla->dbh->do( + "REPLACE INTO attachment_storage_class (id, storage_class) VALUES (?, ?)", + undef, $self->attachment->id, $self->data_type); + return $self; +} + +sub remove_class { + my ($self) = @_; + Bugzilla->dbh->do("DELETE FROM attachment_storage_class WHERE id = ?", + undef, $self->attachment->id); + return $self; +} + +1; diff --git a/Bugzilla/Attachment/Storage/Database.pm b/Bugzilla/Attachment/Storage/Database.pm new file mode 100644 index 000000000..9da52ef6c --- /dev/null +++ b/Bugzilla/Attachment/Storage/Database.pm @@ -0,0 +1,54 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Attachment::Storage::Database; + +use 5.10.1; +use Moo; + +with 'Bugzilla::Attachment::Storage::Base'; + +sub data_type { return 'database'; } + +sub set_data { + my ($self, $data) = @_; + my $dbh = Bugzilla->dbh; + my $sth + = $dbh->prepare( + "REPLACE INTO attach_data (id, thedata) VALUES (?, ?)" + ); + $sth->bind_param(1, $self->attachment->id); + $sth->bind_param(2, $data, $dbh->BLOB_TYPE); + $sth->execute(); + return $self; +} + +sub get_data { + my ($self) = @_; + my $dbh = Bugzilla->dbh; + my ($data) + = $dbh->selectrow_array("SELECT thedata FROM attach_data WHERE id = ?", + undef, $self->attachment->id); + return $data; +} + +sub remove_data { + my ($self) = @_; + my $dbh = Bugzilla->dbh; + $dbh->do("DELETE FROM attach_data WHERE id = ?", undef, $self->attachment->id); + return $self; +} + +sub data_exists { + my ($self) = @_; + my $dbh = Bugzilla->dbh; + my ($exists) = $dbh->selectrow_array("SELECT 1 FROM attach_data WHERE id = ?", + undef, $self->attachment->id); + return !!$exists; +} + +1; diff --git a/Bugzilla/Attachment/Storage/FileSystem.pm b/Bugzilla/Attachment/Storage/FileSystem.pm new file mode 100644 index 000000000..d842a2476 --- /dev/null +++ b/Bugzilla/Attachment/Storage/FileSystem.pm @@ -0,0 +1,63 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Attachment::Storage::FileSystem; + +use 5.10.1; +use Moo; + +use Bugzilla::Constants qw(bz_locations); + +with 'Bugzilla::Attachment::Storage::Base'; + +sub data_type { return 'filesystem'; } + +sub set_data { + my ($self, $data) = @_; + my $path = $self->_local_path(); + mkdir($path, 0770) unless -d $path; + open(my $fh, '>', $self->_local_file()); + binmode($fh); + print $fh $data; + close($fh); + return $self; +} + +sub get_data { + my ($self) = @_; + if (open(my $fh, '<', $self->_local_file())) { + local $/; + binmode($fh); + my $data = <$fh>; + close($fh); + return $data; + } +} + +sub remove_data { + my ($self) = @_; + unlink($self->_local_file()); + return $self; +} + +sub data_exists { + my ($self) = @_; + return -e $self->_local_file(); +} + +sub _local_path { + my ($self) = @_; + my $hash = sprintf('group.%03d', $self->attachment->id % 1000); + return bz_locations()->{attachdir} . '/' . $hash; +} + +sub _local_file { + my ($self) = @_; + return $self->_local_path() . '/attachment.' . $self->attachment->id; +} + +1; diff --git a/Bugzilla/Attachment/Storage/S3.pm b/Bugzilla/Attachment/Storage/S3.pm new file mode 100644 index 000000000..aec5f440c --- /dev/null +++ b/Bugzilla/Attachment/Storage/S3.pm @@ -0,0 +1,90 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Attachment::Storage::S3; + +use 5.10.1; +use Moo; + +use Bugzilla::Error; +use Bugzilla::S3; + +with 'Bugzilla::Attachment::Storage::Base'; + +has 's3' => (is => 'ro', lazy => 1); +has 'bucket' => (is => 'ro', lazy => 1); + +sub _build_s3 { + my $self = shift; + $self->{s3} ||= Bugzilla::S3->new({ + aws_access_key_id => Bugzilla->params->{aws_access_key_id}, + aws_secret_access_key => Bugzilla->params->{aws_secret_access_key}, + secure => 1, + }); + return $self->{s3}; +} + +sub _build_bucket { + my $self = shift; + $self->{bucket} ||= $self->s3->bucket(Bugzilla->params->{s3_bucket}); + return $self->bucket; +} + +sub data_type { return 's3'; } + +sub set_data { + my ($self, $data) = @_; + my $attach_id = $self->attachment->id; + + # If the attachment is larger than attachment_s3_minsize, + # we instead store it in the database. + if (Bugzilla->params->{attachment_s3_minsize} + && $self->attachment->datasize < Bugzilla->params->{attachment_s3_minsize}) + { + require Bugzilla::Attachment::Storage::Database; + return Bugzilla::Attachment::Storage::Database->new({attachment => $self->attachment}) + ->set_data($data); + } + + unless ($self->bucket->add_key($attach_id, $data)) { + warn "Failed to add attachment $attach_id to S3: " + . $self->bucket->errstr . "\n"; + ThrowCodeError('s3_add_failed', + {attach_id => $attach_id, reason => $self->bucket->errstr}); + } + + return $self; +} + +sub get_data { + my ($self) = @_; + my $attach_id = $self->attachment->id; + my $response = $self->bucket->get_key($attach_id); + if (!$response) { + warn "Failed to retrieve attachment $attach_id from S3: " + . $self->bucket->errstr . "\n"; + ThrowCodeError('s3_get_failed', + {attach_id => $attach_id, reason => $self->bucket->errstr}); + } + return $response->{value}; +} + +sub remove_data { + my ($self) = @_; + my $attach_id = $self->attachment->id; + $self->bucket->delete_key($attach_id) + or warn "Failed to remove attachment $attach_id from S3: " + . $self->bucket->errstr . "\n"; + return $self; +} + +sub data_exists { + my ($self) = @_; + return !!$self->bucket->head_key($self->attachment->id); +} + +1; diff --git a/Bugzilla/Config/Attachment.pm b/Bugzilla/Config/Attachment.pm index dc1a31d48..7aa0ca794 100644 --- a/Bugzilla/Config/Attachment.pm +++ b/Bugzilla/Config/Attachment.pm @@ -33,6 +33,12 @@ sub get_param_list { default => 'database', checker => \&check_storage }, + { + name => 'attachment_s3_minsize', + type => 't', + default => '20000', + checker => \&check_numeric + }, {name => 's3_bucket', type => 't', default => '',}, {name => 'aws_access_key_id', type => 't', default => '',}, {name => 'aws_secret_access_key', type => 't', default => '',}, diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index 15ce3c337..a4daefd33 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -538,6 +538,7 @@ use constant ABSTRACT_SCHEMA => { attachments_ispatch_idx => ['ispatch'], ], }, + attach_data => { FIELDS => [ id => { @@ -551,6 +552,20 @@ use constant ABSTRACT_SCHEMA => { ], }, + attachment_storage_class => { + FIELDS => [ + id => { + TYPE => 'INT3', + NOTNULL => 1, + PRIMARYKEY => 1, + REFERENCES => + {TABLE => 'attachments', COLUMN => 'attach_id', DELETE => 'CASCADE'} + }, + storage_class => {TYPE => 'varchar(64)', NOTNULL => 1}, + extra_data => {TYPE => 'MEDIUMTEXT'} + ], + }, + duplicates => { FIELDS => [ dupe_of => { diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 9425d95ec..879bdf165 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -805,6 +805,9 @@ sub update_table_definitions { # Bug 1576667 - dkl@mozilla.com _populate_api_keys_creation_ts(); + # Bug 1588221 - dkl@mozilla.com + _populate_attachment_storage_class(); + ################################################################ # New --TABLE-- changes should go *** A B O V E *** this point # ################################################################ @@ -4326,6 +4329,19 @@ sub _populate_api_keys_creation_ts { {TYPE => 'DATETIME', NOTNULL => 1}); } +sub _populate_attachment_storage_class { + my $dbh = Bugzilla->dbh; + + # Return if we have already made these changes + if (!$dbh->selectrow_arrayref('SELECT COUNT(id) FROM attachment_storage_class')) + { + $dbh->do( + "INSERT INTO attachment_storage_class (id, storage_class) SELECT attachments.id, 'database' FROM attachments ORDER BY attachments.id" + ); + } +} + + 1; __END__ diff --git a/scripts/attachment-data.pl b/scripts/attachment-data.pl index 0e89618e6..596d0844b 100755 --- a/scripts/attachment-data.pl +++ b/scripts/attachment-data.pl @@ -62,7 +62,7 @@ elsif ($cmd eq 'import') { next unless $mem->{data_len}; next unless check_attachment($attachment, $mem->{bug_id}, $mem->{data_len}); - Bugzilla::Attachment::current_storage()->store($attachment->id, $mem->{data}); + $attachment->current_storage->set_data($mem->{data})->set_class(); } } elsif ($cmd eq 'check') { @@ -79,7 +79,8 @@ elsif ($cmd eq 'remove') { while (my $mem = $archive->read_member) { warn "checking $mem->{attach_id}\n"; - my $attachment = Bugzilla::Attachment->new($mem->{attach_id}); + my $attachment + = Bugzilla::Attachment->new({id => $mem->{attach_id}, cache => 1}); die "bad attachment\n" unless check_attachment($attachment, $mem->{bug_id}, $mem->{data_len}); $remove_ok{$mem->{attach_id}} = 1; @@ -88,7 +89,8 @@ elsif ($cmd eq 'remove') { chomp $attach_id; if ($remove_ok{$attach_id}) { warn "removing $attach_id\n"; - Bugzilla::Attachment::current_storage()->remove($attach_id); + my $attachment = Bugzilla::Attachment->new({id => $attach_id, cache => 1}); + $attachment->current_storage->remove()->remove_class(); } else { warn "Unable to remove $attach_id, as it did not occur in the archive.\n"; diff --git a/scripts/migrate-attachments.pl b/scripts/migrate-attachments.pl index 7ef6bea78..8dfd40cf6 100755 --- a/scripts/migrate-attachments.pl +++ b/scripts/migrate-attachments.pl @@ -21,14 +21,23 @@ use Getopt::Long qw(GetOptions); my @storage_names = Bugzilla::Attachment->get_storage_names(); my %options; -GetOptions(\%options, 'mirror=s@{2}', 'copy=s@{2}', 'delete=s') or exit(1); -unless ($options{mirror} || $options{copy} || $options{delete}) { +GetOptions(\%options, 'migrate=s@{2}', 'mirror=s@{2}', 'copy=s@{2}', 'delete=s') or exit(1); +unless ($options{migrate} || $options{mirror} || $options{copy} || $options{delete}) { die <dbh; +if ($options{migrate}) { + if ($options{migrate}->[0] eq $options{migrate}->[1]) { + die "Source and destination must be different\n"; + } + my ($source, $dest) = @{$options{migrate}}; + + my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM attachments"); + confirm(sprintf( + 'Migrate %s attachments from %s to %s?', $total, @{$options{migrate}})); + + my $sth + = $dbh->prepare("SELECT attach_id FROM attachments ORDER BY attach_id DESC"); + $sth->execute(); + my ($count, $migrated) = (0, 0); + while (my ($attach_id) = $sth->fetchrow_array()) { + indicate_progress({total => $total, current => ++$count}); + + my $attachment = Bugzilla::Attachment->new({id => $attach_id, cached => 1}); + + # skip deleted attachments + next if $attachment->datasize == 0; + + # migrate the attachment + if (my $data = $attachment->current_storage($source)->get_data()) { + $attachment->current_storage($dest)->set_data($data)->set_class(); + $migrated++; + } + } + print "\n"; + print "Attachments migrated: $migrated\n"; +} + if ($options{mirror}) { if ($options{mirror}->[0] eq $options{mirror}->[1]) { die "Source and destination must be different\n"; } - my ($source, $dest) = map { storage($_) } @{$options{mirror}}; + my ($source, $dest) = @{$options{mirror}}; my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM attachments"); confirm(sprintf( 'Mirror %s attachments from %s to %s?', $total, @{$options{mirror}})); - my $sth = $dbh->prepare( - "SELECT attach_id, attach_size FROM attachments ORDER BY attach_id DESC"); + my $sth + = $dbh->prepare("SELECT attach_id FROM attachments ORDER BY attach_id DESC"); $sth->execute(); my ($count, $deleted, $stored) = (0, 0, 0); - while (my ($attach_id, $attach_size) = $sth->fetchrow_array()) { + while (my ($attach_id) = $sth->fetchrow_array()) { indicate_progress({total => $total, current => ++$count}); + my $attachment = Bugzilla::Attachment->new({id => $attach_id, cached => 1}); + # remove deleted attachments - if ($attach_size == 0 && $dest->exists($attach_id)) { - $dest->remove($attach_id); + if ($attachment->datasize == 0 && $attachment->current_storage($dest)->data_exists()) { + $attachment->current_storage($dest)->remove_data(); $deleted++; } # store attachments that don't already exist - elsif ($attach_size != 0 && !$dest->exists($attach_id)) { - if (my $data = $source->retrieve($attach_id)) { - $dest->store($attach_id, $data); + elsif ($attachment->datasize != 0 && !$attachment->current_storage($dest)->data_exists()) + { + if (my $data = $attachment->current_storage($source)->get_data()) { + $attachment->current_storage($dest)->set_data($data); $stored++; } } @@ -99,7 +143,7 @@ elsif ($options{copy}) { if ($options{copy}->[0] eq $options{copy}->[1]) { die "Source and destination must be different\n"; } - my ($source, $dest) = map { storage($_) } @{$options{copy}}; + my ($source, $dest) = @{$options{copy}}; my ($total) = $dbh->selectrow_array( @@ -109,17 +153,19 @@ elsif ($options{copy}) { my $sth = $dbh->prepare( - "SELECT attach_id, attach_size FROM attachments WHERE attach_size != 0 ORDER BY attach_id DESC" + "SELECT attach_id FROM attachments WHERE attach_size != 0 ORDER BY attach_id DESC" ); $sth->execute(); my ($count, $stored) = (0, 0); - while (my ($attach_id, $attach_size) = $sth->fetchrow_array()) { + while (my ($attach_id) = $sth->fetchrow_array()) { indicate_progress({total => $total, current => ++$count}); + my $attachment = Bugzilla::Attachment->new({id => $attach_id, cached => 1}); + # store attachments that don't already exist - if (!$dest->exists($attach_id)) { - if (my $data = $source->retrieve($attach_id)) { - $dest->store($attach_id, $data); + if (!$attachment->current_storage($dest)->data_exists()) { + if (my $data = $attachment->current_storage($source)->get_data()) { + $attachment->current_storage($dest)->set_data($data); $stored++; } } @@ -129,7 +175,7 @@ elsif ($options{copy}) { } elsif ($options{delete}) { - my $storage = storage($options{delete}); + my $storage = $options{delete}; my ($total) = $dbh->selectrow_array( "SELECT COUNT(*) FROM attachments WHERE attach_size != 0"); @@ -143,8 +189,11 @@ elsif ($options{delete}) { my ($count, $deleted) = (0, 0); while (my ($attach_id) = $sth->fetchrow_array()) { indicate_progress({total => $total, current => ++$count}); - if ($storage->exists($attach_id)) { - $storage->remove($attach_id); + + my $attachment = Bugzilla::Attachment->new({id => $attach_id, cached => 1}); + + if ($attachment->current_storage($storage)->data_exists()) { + $attachment->current_storage($storage)->remove_data(); $deleted++; } } @@ -152,13 +201,6 @@ elsif ($options{delete}) { print "Attachments deleted: $deleted\n"; } -sub storage { - my ($name) = @_; - my $storage = Bugzilla::Attachment::get_storage_by_name($name) - or die "Invalid attachment location: $name\n"; - return $storage; -} - sub confirm { my ($prompt) = @_; print $prompt, "\n\nPress to stop or to continue..\n"; diff --git a/template/en/default/admin/params/attachment.html.tmpl b/template/en/default/admin/params/attachment.html.tmpl index 5f0d5844e..cfabd9d62 100644 --- a/template/en/default/admin/params/attachment.html.tmpl +++ b/template/en/default/admin/params/attachment.html.tmpl @@ -47,5 +47,10 @@ "must use scripts/migrate-attachments to migrate existing " _ "attachments.", + attachment_s3_minsize => + "When attachment_storage is set to S3, only attachments larger than this " _ + "value will be uploaded to S3 for performance reasons. Smaller attachments " _ + "will be stored in the database." + } %]