From 59efc83ff74ec8115303a94ad00d2b768c17c087 Mon Sep 17 00:00:00 2001 From: Shubham Date: Sat, 6 Apr 2024 22:42:12 +0530 Subject: [PATCH] Allow supplying MONGO_SERVER_URL via chains-config Currently, when using the Mongo docstore for docdb storage backend, the only way to supply MONGO_SERVER_URL environment variable (which contains the credentials to connect to MongoDB) is by adding an environment variable to the Chains controller pod. It's a farily common practice to update the MONGO_SERVER_URL at regular intervals when the credentials are rotated. To facilitate this, this commit adds 2 fields to Chains' configuration: 1. storage.docdb.mongo-server-url 2. storage.docdb.mongo-server-url-dir `storage.docdb.mongo-server-url` simply allows supplying the value of MONGO_SERVER_URL as a field. When this field is updated, the chains controller pod does not restart, unlike when the MONGO_SERVER_URL environment variable is updated. `storage.docdb.mongo-server-url-dir` allows reading MONGO_SERVER_URL from a file in the specified directory. This allows mounting the value of MONGO_SERER_URL from a secret or other mechanisms. When the value of MONGO_SERVER_URL is updated in the path, the new value is automatically picked up and applied. --- docs/config.md | 29 ++-- pkg/chains/signing.go | 13 +- pkg/chains/storage/docdb/docdb.go | 188 ++++++++++++++++++++++- pkg/chains/storage/docdb/docdb_test.go | 159 +++++++++++++++++++ pkg/chains/storage/storage.go | 46 ++++++ pkg/config/config.go | 23 ++- pkg/reconciler/pipelinerun/controller.go | 14 ++ pkg/reconciler/taskrun/controller.go | 13 ++ 8 files changed, 465 insertions(+), 20 deletions(-) diff --git a/docs/config.md b/docs/config.md index 03b0099df9..efcc30a71e 100644 --- a/docs/config.md +++ b/docs/config.md @@ -64,14 +64,16 @@ Supported keys include: ### Storage Configuration -| Key | Description | Supported Values | Default | -| :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------ | -| `storage.gcs.bucket` | The GCS bucket for storage | | | -| `storage.oci.repository` | The OCI repo to store OCI signatures and attestation in | If left undefined _and_ one of `artifacts.{oci,taskrun}.storage` includes `oci` storage, attestations will be stored alongside the stored OCI artifact itself. ([example on GCP](../images/attestations-in-artifact-registry.png)) Defining this value results in the OCI bundle stored in the designated location _instead of_ alongside the image. See [cosign documentation](https://github.com/sigstore/cosign#specifying-registry) for additional information. | | -| `storage.docdb.url` | The go-cloud URI reference to a docstore collection | `firestore://projects/[PROJECT]/databases/(default)/documents/[COLLECTION]?name_field=name` | | -| `storage.grafeas.projectid` | The project of where grafeas server is located for storing occurrences | | | -| `storage.grafeas.noteid` (optional) | This field will be used as the prefix part of the note name that will be created. The value of this field must be a string without spaces. (See more details [below](#grafeas).) | | | -| `storage.grafeas.notehint` (optional) | This field is used to set the [human_readable_name](https://github.com/grafeas/grafeas/blob/cd23d4dc1bef740d6d6d90d5007db5c9a2431c41/proto/v1/attestation.proto#L49) field in the Grafeas ATTESTATION note. If it is not provided, the default `This attestation note was generated by Tekton Chains` will be used. | | | +| Key | Description | Supported Values | Default | +|:------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------| +| `storage.gcs.bucket` | The GCS bucket for storage | | | +| `storage.oci.repository` | The OCI repo to store OCI signatures and attestation in | If left undefined _and_ one of `artifacts.{oci,taskrun}.storage` includes `oci` storage, attestations will be stored alongside the stored OCI artifact itself. ([example on GCP](../images/attestations-in-artifact-registry.png)) Defining this value results in the OCI bundle stored in the designated location _instead of_ alongside the image. See [cosign documentation](https://github.com/sigstore/cosign#specifying-registry) for additional information. | | +| `storage.docdb.url` | The go-cloud URI reference to a docstore collection | `firestore://projects/[PROJECT]/databases/(default)/documents/[COLLECTION]?name_field=name` | | +| `storage.docdb.mongo-server-url` (optional) | The value of MONGO_SERVER_URL env var with the MongoDB connection URI | Example: `mongodb://[USER]:[PASSWORD]@[HOST]:[PORT]/[DATABASE]` | | +| `storage.docdb.mongo-server-url-dir` (optional) | The path of the directory that contains the file named MONGO_SERVER_URL that stores the value of MONGO_SERVER_URL env var | If the file `/mnt/mongo-creds-secret/MONGO_SERVER_URL` has the value of MONGO_SERVER_URL, then set `storage.docdb.mongo-server-url-dir: /mnt/mongo-creds-secret` | | +| `storage.grafeas.projectid` | The project of where grafeas server is located for storing occurrences | | | +| `storage.grafeas.noteid` (optional) | This field will be used as the prefix part of the note name that will be created. The value of this field must be a string without spaces. (See more details [below](#grafeas).) | | | +| `storage.grafeas.notehint` (optional) | This field is used to set the [human_readable_name](https://github.com/grafeas/grafeas/blob/cd23d4dc1bef740d6d6d90d5007db5c9a2431c41/proto/v1/attestation.proto#L49) field in the Grafeas ATTESTATION note. If it is not provided, the default `This attestation note was generated by Tekton Chains` will be used. | | | #### docstore @@ -83,7 +85,16 @@ You can read about the go-cloud docstore URI format [here](https://gocloud.dev/h #### MongoDB -With MongoDB you will need to add a `MONGO_SERVER_URL` env var with the MongoDB connection URI to the `tekton-chains-controller`, the go-cloud URI is just to point at the db and collection +With MongoDB you will need to supply the value of `MONGO_SERVER_URL` env var with the MongoDB connection URI to the Tekton Chains, the go-cloud URI is just to point at the db and collection. +This can be achieved in a few ways: + +- Setting the `MONGO_SERVER_URL` env var in the `tekton-chains-controller` deployment. +- Setting the value of `storage.docdb.mongo-server-url` field. + - This field takes precedence over the `MONGO_SERVER_URL` env var. +- Setting the value of `storage.docdb.mongo-server-url-dir` field. + - This field takes precedence over `storage.docdb.mongo-server-url` and `MONGO_SERVER_URL` env var. + - The value should point to a directory that has a file named `MONGO_SERVER_URL` that contains the env var. Each time the file is updated, the new value will be read. + - One common use case is to store the value of `MONGO_SERVER_URL` in a secret with the key `MONGO_SERVER_URL` and mount the secret at the path specified in this field. When the secret is updated, the new value will be fetched by Tekton Chains. #### Grafeas diff --git a/pkg/chains/signing.go b/pkg/chains/signing.go index 453bad9820..ebbd00462e 100644 --- a/pkg/chains/signing.go +++ b/pkg/chains/signing.go @@ -29,6 +29,7 @@ import ( "github.com/tektoncd/chains/pkg/chains/storage" "github.com/tektoncd/chains/pkg/config" versioned "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" + "golang.org/x/exp/maps" "k8s.io/apimachinery/pkg/util/sets" "knative.dev/pkg/logging" ) @@ -184,7 +185,17 @@ func (o *ObjectSigner) Sign(ctx context.Context, tektonObj objects.TektonObject) // Now store those! for _, backend := range sets.List[string](signableType.StorageBackend(cfg)) { - b := o.Backends[backend] + logger.Infof("signable storage backends: %v", signableType.StorageBackend(cfg)) + logger.Infof("o.Backends(): %v", o.Backends) + + b, ok := o.Backends[backend] + if !ok { + backendErr := fmt.Errorf("could not find backend '%s' in configured backends (%v) while trying sign: %s/%s", backend, maps.Keys(o.Backends), tektonObj.GetKindName(), tektonObj.GetName()) + logger.Error(backendErr) + merr = multierror.Append(merr, backendErr) + continue + } + storageOpts := config.StorageOpts{ ShortKey: signableType.ShortKey(obj), FullKey: signableType.FullKey(obj), diff --git a/pkg/chains/storage/docdb/docdb.go b/pkg/chains/storage/docdb/docdb.go index d9b82c979d..f06f851931 100644 --- a/pkg/chains/storage/docdb/docdb.go +++ b/pkg/chains/storage/docdb/docdb.go @@ -17,19 +17,31 @@ import ( "context" "encoding/base64" "encoding/json" + "fmt" + "net/url" + "os" + "path" + "slices" + "strings" + "github.com/fsnotify/fsnotify" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/config" "gocloud.dev/docstore" _ "gocloud.dev/docstore/awsdynamodb" _ "gocloud.dev/docstore/gcpfirestore" + "gocloud.dev/docstore/mongodocstore" _ "gocloud.dev/docstore/mongodocstore" + "knative.dev/pkg/logging" ) const ( StorageTypeDocDB = "docdb" ) +// ErrNothingToWatch is an error that's returned when the backend doesn't have anything to "watch" +var ErrNothingToWatch = fmt.Errorf("backend has nothing to watch") + // Backend is a storage backend that stores signed payloads in the TaskRun metadata as an annotation. // It is stored as base64 encoded JSON. type Backend struct { @@ -47,8 +59,21 @@ type SignedDocument struct { // NewStorageBackend returns a new Tekton StorageBackend that stores signatures on a TaskRun func NewStorageBackend(ctx context.Context, cfg config.Config) (*Backend, error) { - url := cfg.Storage.DocDB.URL - coll, err := docstore.OpenCollection(ctx, url) + docdbURL := cfg.Storage.DocDB.URL + + u, err := url.Parse(docdbURL) + if err != nil { + return nil, err + } + + if u.Scheme == mongodocstore.Scheme { + // MONGO_SERVER_URL can be passed in as an environment variable or via config fields + if err := populateMongoServerURL(ctx, cfg); err != nil { + return nil, err + } + } + + coll, err := docstore.OpenCollection(ctx, docdbURL) if err != nil { return nil, err } @@ -58,6 +83,98 @@ func NewStorageBackend(ctx context.Context, cfg config.Config) (*Backend, error) }, nil } +// WatchBackend returns a channel that receives a new Backend each time it needs to be updated +func WatchBackend(ctx context.Context, cfg config.Config, watcherStop chan bool) (chan *Backend, error) { + logger := logging.FromContext(ctx) + docDBURL := cfg.Storage.DocDB.URL + + u, err := url.Parse(docDBURL) + if err != nil { + return nil, err + } + + // Set up the watcher only for mongo backends + if u.Scheme != "mongo" { + return nil, ErrNothingToWatch + } + + // Set up watcher only when `storage.docdb.mongo-server-url-dir` is set + if cfg.Storage.DocDB.MongoServerURLDir == "" { + return nil, ErrNothingToWatch + } + + logger.Infof("setting up fsnotify watcher for directory: %s", cfg.Storage.DocDB.MongoServerURLDir) + + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + pathsToWatch := []string{ + // mongo-server-url-dir/MONGO_SERVER_URL is where the MONGO_SERVER_URL environment + // variable is expected to be mounted, either manually or via a Kubernetes secret, etc. + path.Join(cfg.Storage.DocDB.MongoServerURLDir, "MONGO_SERVER_URL"), + // When a Kubernetes secret is mounted on a path, the `data` in that secret is mounted + // under path/..data that is then `symlink`ed to the key of the data. In this instance, + // the mounted path is going to look like: + // file 1 - ..2024_05_03_11_23_23.1253599725 + // file 2 - ..data -> ..2024_05_03_11_23_23.1253599725 + // file 3 - MONGO_SERVER_URL -> ..data/MONGO_SERVER_URL + // So each time the secret is updated, the file `MONGO_SERVER_URL` is not updated, + // instead the underlying symlink at `..data` is updated and that's what we want to + // capture via the fsnotify event watcher + path.Join(cfg.Storage.DocDB.MongoServerURLDir, "..data"), + } + + backendChan := make(chan *Backend) + // Start listening for events. + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + logger.Infof("received event: %s, path: %s", event.Op.String(), event.Name) + // Only respond to create/write/remove events in the directory + if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) || event.Has(fsnotify.Remove) { + // event.Name captures the path that is updated + if slices.Contains(pathsToWatch, event.Name) { + logger.Infof("directory %s has been updated, reconfiguring backend...", cfg.Storage.DocDB.MongoServerURLDir) + + // Now that MONGO_SERVER_URL has been updated, we should update docdb backend again + newDocDBBackend, err := NewStorageBackend(ctx, cfg) + if err != nil { + logger.Error(err) + backendChan <- nil + } else { + // Storing the backend in the signer so everyone has access to the up-to-date backend + backendChan <- newDocDBBackend + } + } + } + + case err, ok := <-watcher.Errors: + if !ok { + return + } + logger.Error(err) + + case <-watcherStop: + logger.Info("stopping fsnotify context...") + return + } + } + }() + + // Add a path. + err = watcher.Add(cfg.Storage.DocDB.MongoServerURLDir) + if err != nil { + return nil, err + } + return backendChan, nil +} + // StorePayload implements the Payloader interface. func (b *Backend) StorePayload(ctx context.Context, _ objects.TektonObject, rawPayload []byte, signature string, opts config.StorageOpts) error { var obj interface{} @@ -125,3 +242,70 @@ func (b *Backend) retrieveDocuments(ctx context.Context, opts config.StorageOpts } return []SignedDocument{d}, nil } + +func populateMongoServerURL(ctx context.Context, cfg config.Config) error { + // First preference is given to the key `storage.docdb.mongo-server-url-dir`. + // If that doesn't exist, then we move on to `storage.docdb.mongo-server-url`. + // If that doesn't exist, then we check if `MONGO_SERVER_URL` env var is set. + logger := logging.FromContext(ctx) + mongoEnv := "MONGO_SERVER_URL" + + if cfg.Storage.DocDB.MongoServerURLDir != "" { + logger.Infof("setting %s from storage.docdb.mongo-server-url-dir: %s", mongoEnv, cfg.Storage.DocDB.MongoServerURLDir) + if err := setMongoServerURLFromDir(ctx, cfg.Storage.DocDB.MongoServerURLDir); err != nil { + return err + } + } else if cfg.Storage.DocDB.MongoServerURL != "" { + logger.Infof("setting %s from storage.docdb.mongo-server-url", mongoEnv) + if err := os.Setenv(mongoEnv, cfg.Storage.DocDB.MongoServerURL); err != nil { + return err + } + } + + if _, envExists := os.LookupEnv(mongoEnv); !envExists { + return fmt.Errorf("mongo docstore configured but %s environment variable not set, "+ + "supply one of storage.docdb.mongo-server-url-dir, storage.docdb.mongo-server-url or set %s", mongoEnv, mongoEnv) + } + + return nil +} + +func setMongoServerURLFromDir(ctx context.Context, dir string) error { + logger := logging.FromContext(ctx) + mongoEnv := "MONGO_SERVER_URL" + + stat, err := os.Stat(dir) + if err != nil { + if os.IsNotExist(err) { + // If directory does not exist, then create it. This is needed for + // the fsnotify watcher. + // fsnotify does not receive events if the path that it's watching + // is created later. + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + return nil + } else { + return err + } + } + // If the path exists but is not a directory, then throw an error + if !stat.IsDir() { + return fmt.Errorf("path specified at storage.docdb.mongo-server-url-dir: %s is not a directory", dir) + } + + filePath := path.Join(dir, mongoEnv) + fileData, err := os.ReadFile(filePath) + if err != nil { + return err + } + // A trailing newline is fairly common in mounted files, let's remove it. + fileDataNormalized := strings.TrimSuffix(string(fileData), "\n") + + logger.Infof("setting %s from path: %s", mongoEnv, filePath) + if err = os.Setenv(mongoEnv, fileDataNormalized); err != nil { + return err + } + + return nil +} diff --git a/pkg/chains/storage/docdb/docdb_test.go b/pkg/chains/storage/docdb/docdb_test.go index 9ac4f6ae2f..0b9caf2bf1 100644 --- a/pkg/chains/storage/docdb/docdb_test.go +++ b/pkg/chains/storage/docdb/docdb_test.go @@ -15,6 +15,8 @@ package docdb import ( "encoding/json" + "os" + "path/filepath" "testing" "github.com/tektoncd/chains/pkg/chains/objects" @@ -117,3 +119,160 @@ func TestBackend_StorePayload(t *testing.T) { }) } } + +func TestPopulateMongoServerURL(t *testing.T) { + mongoDir := t.TempDir() + mongoEnvFromFile := "mongoEnvFromFile" + if err := os.WriteFile(filepath.Join(mongoDir, "MONGO_SERVER_URL"), []byte(mongoEnvFromFile), 0644); err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + cfg config.Config + setMongoEnv string + expectedMongoEnv string + wantErr bool + }{ + { + name: "fail when MONGO_SERVER_URL is not set but storage.docdb.url is set", + cfg: config.Config{ + Storage: config.StorageConfigs{ + DocDB: config.DocDBStorageConfig{ + URL: "mongo://chainsdb/chainscollection?id_field=name", + }, + }, + }, + wantErr: true, + }, + { + name: "pass when MONGO_SERVER_URL is set and storage.docdb.url is set", + cfg: config.Config{ + Storage: config.StorageConfigs{ + DocDB: config.DocDBStorageConfig{ + URL: "mongo://chainsdb/chainscollection?id_field=name", + }, + }, + }, + setMongoEnv: "testEnv", + expectedMongoEnv: "testEnv", + wantErr: false, + }, + { + name: "storage.docdb.mongo-server-url has more precedence than MONGO_SERVER_URL", + cfg: config.Config{ + Storage: config.StorageConfigs{ + DocDB: config.DocDBStorageConfig{ + URL: "mongo://chainsdb/chainscollection?id_field=name", + MongoServerURL: "envFromConfig", + }, + }, + }, + setMongoEnv: "testEnv", + expectedMongoEnv: "envFromConfig", + wantErr: false, + }, + { + name: "storage.docdb.mongo-server-url works solo", + cfg: config.Config{ + Storage: config.StorageConfigs{ + DocDB: config.DocDBStorageConfig{ + URL: "mongo://chainsdb/chainscollection?id_field=name", + MongoServerURL: "envFromConfigSolo", + }, + }, + }, + setMongoEnv: "", + expectedMongoEnv: "envFromConfigSolo", + wantErr: false, + }, + { + name: "storage.docdb.mongo-server-url-dir has precedence over storage.docdb.mongo-server-url and MONGO_SERVER_URL", + cfg: config.Config{ + Storage: config.StorageConfigs{ + DocDB: config.DocDBStorageConfig{ + URL: "mongo://chainsdb/chainscollection?id_field=name", + MongoServerURLDir: mongoDir, + MongoServerURL: "envFromConfig", + }, + }, + }, + setMongoEnv: "mongoEnvVar", + expectedMongoEnv: mongoEnvFromFile, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer os.Unsetenv("MONGO_SERVER_URL") + ctx, _ := rtesting.SetupFakeContext(t) + + if tt.setMongoEnv != "" { + if err := os.Setenv("MONGO_SERVER_URL", tt.setMongoEnv); err != nil { + t.Error(err) + } + } + + if err := populateMongoServerURL(ctx, tt.cfg); (err != nil) != tt.wantErr { + t.Errorf("did not expect an error, but got: %v", err) + } + + currentMongoEnv := os.Getenv("MONGO_SERVER_URL") + if os.Getenv("MONGO_SERVER_URL") != tt.expectedMongoEnv { + t.Errorf("expected MONGO_SERVER_URL to be: %s, but got: %s", tt.expectedMongoEnv, currentMongoEnv) + } + }) + } +} +func TestSetMongoServerURLFromDir(t *testing.T) { + mongoDir := t.TempDir() + mongoEnvFromFile := "mongoEnvFromFile" + if err := os.WriteFile(filepath.Join(mongoDir, "MONGO_SERVER_URL"), []byte(mongoEnvFromFile), 0644); err != nil { + t.Fatal(err) + } + + if err := os.WriteFile(filepath.Join(mongoDir, "just-a-file"), []byte("just-a-file"), 0644); err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + directory string + expectedMongoEnv string + wantErr bool + }{ + { + name: "error if path is not a directory", + directory: filepath.Join(filepath.Join(mongoDir, "just-a-file")), + wantErr: true, + }, + { + name: "verify if MONGO_SERVER_URL is being set from path", + directory: mongoDir, + expectedMongoEnv: mongoEnvFromFile, + wantErr: false, + }, + { + name: "no error if path does not exist (it will be created)", + directory: filepath.Join(mongoDir, "does-not-exist"), + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer os.Unsetenv("MONGO_SERVER_URL") + ctx, _ := rtesting.SetupFakeContext(t) + + if err := setMongoServerURLFromDir(ctx, tt.directory); (err != nil) != tt.wantErr { + t.Errorf("did not expect an error, but got: %v", err) + } + + currentEnv := os.Getenv("MONGO_SERVER_URL") + if currentEnv != tt.expectedMongoEnv { + t.Errorf("expected MONGO_SERVER_URL: %s, got %s", tt.expectedMongoEnv, currentEnv) + } + }) + } +} diff --git a/pkg/chains/storage/storage.go b/pkg/chains/storage/storage.go index be3561c965..229803f8e0 100644 --- a/pkg/chains/storage/storage.go +++ b/pkg/chains/storage/storage.go @@ -15,6 +15,7 @@ package storage import ( "context" + "errors" "github.com/tektoncd/chains/pkg/chains/objects" "github.com/tektoncd/chains/pkg/chains/storage/docdb" @@ -25,8 +26,10 @@ import ( "github.com/tektoncd/chains/pkg/chains/storage/tekton" "github.com/tektoncd/chains/pkg/config" "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" + "golang.org/x/exp/maps" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/kubernetes" + "knative.dev/pkg/logging" ) // Backend is an interface to store a chains Payload @@ -42,6 +45,8 @@ type Backend interface { // InitializeBackends creates and initializes every configured storage backend. func InitializeBackends(ctx context.Context, ps versioned.Interface, kc kubernetes.Interface, cfg config.Config) (map[string]Backend, error) { + logger := logging.FromContext(ctx) + // Add an entry here for every configured backend configuredBackends := []string{} if cfg.Artifacts.TaskRuns.Enabled() { @@ -53,6 +58,7 @@ func InitializeBackends(ctx context.Context, ps versioned.Interface, kc kubernet if cfg.Artifacts.PipelineRuns.Enabled() { configuredBackends = append(configuredBackends, sets.List[string](cfg.Artifacts.PipelineRuns.StorageBackend)...) } + logger.Infof("configured backends from config: %v", configuredBackends) // Now only initialize and return the configured ones. backends := map[string]Backend{} @@ -90,5 +96,45 @@ func InitializeBackends(ctx context.Context, ps versioned.Interface, kc kubernet } } + + logger.Infof("successfully initialized backends: %v", maps.Keys(backends)) return backends, nil } + +// WatchBackends watches backends for any update and keeps them up to date. +func WatchBackends(ctx context.Context, watcherStop chan bool, backends map[string]Backend, cfg config.Config) error { + logger := logging.FromContext(ctx) + for backend := range backends { + switch backend { + case docdb.StorageTypeDocDB: + backendChan, err := docdb.WatchBackend(ctx, cfg, watcherStop) + if err != nil { + if errors.Is(err, docdb.ErrNothingToWatch) { + logger.Info(err) + continue + } + return err + } + go func() { + for { + select { + case newBackend := <-backendChan: + if newBackend == nil { + logger.Errorf("removing backend %s from backends", docdb.StorageTypeDocDB) + delete(backends, docdb.StorageTypeDocDB) + continue + } + logger.Infof("adding to backends: %s", docdb.StorageTypeDocDB) + backends[docdb.StorageTypeDocDB] = newBackend + case <-watcherStop: + logger.Info("stop watching backends...") + return + } + } + }() + default: + logger.Debugf("no backends to watch...") + } + } + return nil +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 1d3cb3fbc9..a5eef510df 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -122,7 +122,9 @@ type TektonStorageConfig struct { } type DocDBStorageConfig struct { - URL string + URL string + MongoServerURL string + MongoServerURLDir string } type GrafeasConfig struct { @@ -165,13 +167,16 @@ const ( ociStorageKey = "artifacts.oci.storage" ociSignerKey = "artifacts.oci.signer" - gcsBucketKey = "storage.gcs.bucket" - ociRepositoryKey = "storage.oci.repository" - ociRepositoryInsecureKey = "storage.oci.repository.insecure" - docDBUrlKey = "storage.docdb.url" - grafeasProjectIDKey = "storage.grafeas.projectid" - grafeasNoteIDKey = "storage.grafeas.noteid" - grafeasNoteHint = "storage.grafeas.notehint" + gcsBucketKey = "storage.gcs.bucket" + ociRepositoryKey = "storage.oci.repository" + ociRepositoryInsecureKey = "storage.oci.repository.insecure" + docDBUrlKey = "storage.docdb.url" + docDBMongoServerURLKey = "storage.docdb.mongo-server-url" + docDBMongoServerURLDirKey = "storage.docdb.mongo-server-url-dir" + + grafeasProjectIDKey = "storage.grafeas.projectid" + grafeasNoteIDKey = "storage.grafeas.noteid" + grafeasNoteHint = "storage.grafeas.notehint" // PubSub - General pubsubProvider = "storage.pubsub.provider" @@ -293,6 +298,8 @@ func NewConfigFromMap(data map[string]string) (*Config, error) { asString(ociRepositoryKey, &cfg.Storage.OCI.Repository), asBool(ociRepositoryInsecureKey, &cfg.Storage.OCI.Insecure), asString(docDBUrlKey, &cfg.Storage.DocDB.URL), + asString(docDBMongoServerURLKey, &cfg.Storage.DocDB.MongoServerURL), + asString(docDBMongoServerURLDirKey, &cfg.Storage.DocDB.MongoServerURLDir), asString(grafeasProjectIDKey, &cfg.Storage.Grafeas.ProjectID), asString(grafeasNoteIDKey, &cfg.Storage.Grafeas.NoteID), asString(grafeasNoteHint, &cfg.Storage.Grafeas.NoteHint), diff --git a/pkg/reconciler/pipelinerun/controller.go b/pkg/reconciler/pipelinerun/controller.go index 21beabbc47..e382d2f26b 100644 --- a/pkg/reconciler/pipelinerun/controller.go +++ b/pkg/reconciler/pipelinerun/controller.go @@ -53,8 +53,18 @@ func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl Pipelineclientset: pipelineClient, TaskRunLister: taskRunInformer.Lister(), } + impl := pipelinerunreconciler.NewImpl(ctx, c, func(impl *controller.Impl) controller.Options { + watcherStop := make(chan bool) + cfgStore := config.NewConfigStore(logger, func(name string, value interface{}) { + select { + case watcherStop <- true: + logger.Info("sent close event to fsnotify...") + default: + logger.Info("could not send close event to fsnotify...") + } + // get updated config cfg := *value.(*config.Config) @@ -64,6 +74,10 @@ func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl logger.Error(err) } psSigner.Backends = backends + + if err := storage.WatchBackends(ctx, watcherStop, psSigner.Backends, cfg); err != nil { + logger.Error(err) + } }) // setup watches for the config names provided by client diff --git a/pkg/reconciler/taskrun/controller.go b/pkg/reconciler/taskrun/controller.go index 8695dcfdb3..1991f334db 100644 --- a/pkg/reconciler/taskrun/controller.go +++ b/pkg/reconciler/taskrun/controller.go @@ -49,7 +49,16 @@ func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl Pipelineclientset: pipelineClient, } impl := taskrunreconciler.NewImpl(ctx, c, func(impl *controller.Impl) controller.Options { + watcherStop := make(chan bool) + cfgStore := config.NewConfigStore(logger, func(name string, value interface{}) { + select { + case watcherStop <- true: + logger.Info("sent close event to fsnotify") + default: + logger.Info("could not send close event to fsnotify") + } + // get updated config cfg := *value.(*config.Config) @@ -59,6 +68,10 @@ func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl logger.Error(err) } tsSigner.Backends = backends + + if err := storage.WatchBackends(ctx, watcherStop, tsSigner.Backends, cfg); err != nil { + logger.Error(err) + } }) // setup watches for the config names provided by client