-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add script to delete indexes from Redis (#2120)
Add a script to check what indexes are in the EntryIndex table in a MySQL database and deletes those indexes from the Redis database. This is meant to be done after Rekor is migrated to using --search_index.storage_provider=mysql and the MySQL table has been backfilled with all the indexes. Deleting the data allows us to downsize the Redis instance. Signed-off-by: Colleen Murphy <[email protected]>
- Loading branch information
Showing
4 changed files
with
364 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright 2024 The Sigstore Authors. | ||
// | ||
// 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. | ||
|
||
/* | ||
cleanup-index checks what index entries are in the MySQL table and deletes those entries from the Redis databse. | ||
It does not go the other way | ||
To run: | ||
go run cmd/cleanup-index/main.go --mysql-dsn <mysql connection> --redis-hostname <redis-hostname> --redis-port <redis-port> [--dry-run] | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"flag" | ||
"fmt" | ||
"log" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
|
||
_ "github.com/go-sql-driver/mysql" | ||
"github.com/jmoiron/sqlx" | ||
"github.com/redis/go-redis/v9" | ||
"sigs.k8s.io/release-utils/version" | ||
|
||
// these imports are to call the packages' init methods | ||
_ "github.com/sigstore/rekor/pkg/types/alpine/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/cose/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/helm/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2" | ||
_ "github.com/sigstore/rekor/pkg/types/jar/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/rfc3161/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/rpm/v0.0.1" | ||
_ "github.com/sigstore/rekor/pkg/types/tuf/v0.0.1" | ||
) | ||
|
||
const ( | ||
mysqlSelectStmt = "SELECT DISTINCT EntryKey FROM EntryIndex" | ||
) | ||
|
||
var ( | ||
redisHostname = flag.String("redis-hostname", "", "Hostname for Redis application") | ||
redisPort = flag.String("redis-port", "", "Port to Redis application") | ||
redisPassword = flag.String("redis-password", "", "Password for Redis authentication") | ||
redisEnableTLS = flag.Bool("redis-enable-tls", false, "Enable TLS for Redis client") | ||
redisInsecureSkipVerify = flag.Bool("redis-insecure-skip-verify", false, "Whether to skip TLS verification for Redis client or not") | ||
mysqlDSN = flag.String("mysql-dsn", "", "MySQL Data Source Name") | ||
versionFlag = flag.Bool("version", false, "Print the current version of Backfill MySQL") | ||
dryRun = flag.Bool("dry-run", false, "Dry run - don't actually insert into MySQL") | ||
) | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
versionInfo := version.GetVersionInfo() | ||
if *versionFlag { | ||
fmt.Println(versionInfo.String()) | ||
os.Exit(0) | ||
} | ||
|
||
if *mysqlDSN == "" { | ||
log.Fatal("mysql-dsn must be set") | ||
} | ||
if *redisHostname == "" { | ||
log.Fatal("redis-hostname must be set") | ||
} | ||
if *redisPort == "" { | ||
log.Fatal("redis-port must be set") | ||
} | ||
|
||
log.Printf("running cleanup index Version: %s GitCommit: %s BuildDate: %s", versionInfo.GitVersion, versionInfo.GitCommit, versionInfo.BuildDate) | ||
|
||
redisClient := getRedisClient() | ||
|
||
mysqlClient, err := getMySQLClient() | ||
if err != nil { | ||
log.Fatalf("creating mysql client: %v", err) | ||
} | ||
|
||
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) | ||
|
||
keys, err := getKeysToDelete(ctx, mysqlClient) | ||
if err != nil { | ||
log.Fatalf("getting keys from mysql: %v", err) | ||
} | ||
err = removeFromRedis(ctx, redisClient, keys) | ||
if err != nil { | ||
log.Fatalf("deleting keys from redis: %v", err) | ||
} | ||
} | ||
|
||
// getRedisClient creates a Redis client. | ||
func getRedisClient() *redis.Client { | ||
opts := &redis.Options{ | ||
Addr: fmt.Sprintf("%s:%s", *redisHostname, *redisPort), | ||
Password: *redisPassword, | ||
Network: "tcp", | ||
DB: 0, // default DB | ||
} | ||
// #nosec G402 | ||
if *redisEnableTLS { | ||
opts.TLSConfig = &tls.Config{ | ||
InsecureSkipVerify: *redisInsecureSkipVerify, //nolint: gosec | ||
} | ||
} | ||
return redis.NewClient(opts) | ||
} | ||
|
||
// getMySQLClient creates a MySQL client. | ||
func getMySQLClient() (*sqlx.DB, error) { | ||
dbClient, err := sqlx.Open("mysql", *mysqlDSN) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err = dbClient.Ping(); err != nil { | ||
return nil, err | ||
} | ||
return dbClient, nil | ||
} | ||
|
||
// getKeysToDelete looks up entries in the EntryIndex table in MySQL. | ||
func getKeysToDelete(ctx context.Context, dbClient *sqlx.DB) ([]string, error) { | ||
keys := []string{} | ||
err := dbClient.SelectContext(ctx, &keys, mysqlSelectStmt) | ||
return keys, err | ||
} | ||
|
||
// removeFromRedis delete the given keys from Redis. | ||
func removeFromRedis(ctx context.Context, redisClient *redis.Client, keys []string) error { | ||
fmt.Printf("attempting to remove %d keys from redis\n", len(keys)) | ||
if *dryRun { | ||
return nil | ||
} | ||
result, err := redisClient.Del(ctx, keys...).Result() | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Printf("removed %d keys from redis\n", result) | ||
if result != int64(len(keys)) { | ||
fmt.Println("some keys present in mysql may already have been removed from redis") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
#!/usr/bin/env bash | ||
# | ||
# Copyright 2024 The Sigstore Authors. | ||
# | ||
# 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. | ||
|
||
REKOR_ADDRESS=http://localhost:3000 | ||
REDIS_HOST=localhost | ||
REDIS_PORT=6379 | ||
REDIS_PASSWORD=test | ||
MYSQL_HOST=127.0.0.1 | ||
MYSQL_PORT=3306 | ||
MYSQL_USER=test | ||
MYSQL_PASSWORD=zaphod | ||
MYSQL_DB=test | ||
|
||
testdir=$(mktemp -d) | ||
|
||
source $(dirname "$0")/index-test-utils.sh | ||
|
||
trap cleanup EXIT | ||
|
||
make_entries() { | ||
set -e | ||
# make 10 unique artifacts and sign each once | ||
for i in $(seq 0 9) ; do | ||
minisign -GW -p $testdir/mini${i}.pub -s $testdir/mini${i}.key | ||
echo test${i} > $testdir/blob${i} | ||
minisign -S -s $testdir/mini${i}.key -m $testdir/blob${i} | ||
rekor-cli --rekor_server $REKOR_ADDRESS upload \ | ||
--artifact $testdir/blob${i} \ | ||
--pki-format=minisign \ | ||
--public-key $testdir/mini${i}.pub \ | ||
--signature $testdir/blob${i}.minisig \ | ||
--format json | ||
done | ||
# double-sign a few artifacts | ||
for i in $(seq 7 9) ; do | ||
set +e | ||
let key_index=$i-5 | ||
set -e | ||
minisign -S -s $testdir/mini${key_index}.key -m $testdir/blob${i} | ||
rekor-cli --rekor_server $REKOR_ADDRESS upload \ | ||
--artifact $testdir/blob${i} \ | ||
--pki-format=minisign \ | ||
--public-key $testdir/mini${key_index}.pub \ | ||
--signature $testdir/blob${i}.minisig \ | ||
--format json | ||
done | ||
set +e | ||
} | ||
|
||
docker_up | ||
|
||
checkpoints=$(redis_cli --scan) | ||
|
||
make_entries | ||
|
||
set -e | ||
loginfo=$(rekor-cli --rekor_server $REKOR_ADDRESS loginfo --format=json) | ||
let end_index=$(echo $loginfo | jq .ActiveTreeSize)-1 | ||
set +e | ||
|
||
# check that the entries are in redis | ||
if [ $(redis_cli --scan | grep -v '/' | wc -l) -ne 20 ] ; then | ||
echo "Setup failed: redis had an unexpected number of index keys." | ||
exit 1 | ||
fi | ||
|
||
# backfill to mysql - this isn't useful in a real scenario because | ||
# search_index.storage_provider still points to redis, but it's useful | ||
# to test that the key cleanup is working | ||
go run cmd/backfill-index/main.go --rekor-address $REKOR_ADDRESS \ | ||
--mysql-dsn "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/${MYSQL_DB}" \ | ||
--concurrency 5 --start 0 --end $end_index | ||
|
||
# run the cleanup script | ||
go run cmd/cleanup-index/main.go --mysql-dsn "${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/${MYSQL_DB}" --redis-hostname $REDIS_HOST --redis-port $REDIS_PORT --redis-password $REDIS_PASSWORD | ||
|
||
# there should be no more index entries in redis | ||
if [ $(redis_cli --scan | grep -v '/' | wc -l) -ne 0 ] ; then | ||
echo "Found index keys remaining in redis." | ||
exit 1 | ||
fi | ||
|
||
# the checkpoints should have been left alone | ||
for cp in $checkpoints ; do | ||
if [ $(redis_cli EXISTS $cp) -ne 1 ] ; then | ||
echo "Missing checkpoint $cp" | ||
exit 1 | ||
fi | ||
done |
Oops, something went wrong.