Repository: deislabs/ratify
Pull request #1356 (2024-04-01 04:38:01)

feat: add GetNamespace utils method for context [multi-tenancy PR 1]
# Description

## What this PR does / why we need it:
This is the first PR for multi-tenancy support.

1.  Add a `GetNamespace` util method. It fetches the namespace embeded in the context. For each request, we'll use `SetContextWithNamespace` to embed the namespace into the context, and consumers like executors, verifiers, policyEnforcers could get the namespace from the context instead of passing it through function param explicitely.
2. Embed the namespace into the context for mutation requests as well.

## Which issue(s) this PR fixes *(optional, using `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when the PR gets merged)*:
Fixes #

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Helm Chart Change (any edit/addition/update that is necessary for changes merged to the `main` branch)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Please also list any relevant details for your test configuration

- [ ] Test A
- [ ] Test B

# Checklist:

- [ ] Does the affected code have corresponding tests?
- [ ] Are the changes documented, not just with inline documentation, but also with conceptual documentation such as an overview of a new feature, or task-based documentation like a tutorial? Consider if this change should be announced on your project blog.
- [ ] Does this introduce breaking changes that would require an announcement or bumping the major version?
- [ ] Do all new files have appropriate license header?

# Post Merge Requirements
- [ ] MAINTAINERS: manually trigger the "Publish Package" workflow after merging any PR that indicates `Helm Chart Change`


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/deislabs/ratify/pull/1356?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 64.95%. Comparing base [(`45c0f81`)](https://app.codecov.io/gh/deislabs/ratify/commit/45c0f81314ea5d26ec69672def593a6a8f464f1c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs) to head [(`9bb1a17`)](https://app.codecov.io/gh/deislabs/ratify/pull/1356?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs).
> Report is 36 commits behind head on main.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1356      +/-   ##
==========================================
+ Coverage   61.60%   64.95%   +3.34%     
==========================================
  Files          98       87      -11     
  Lines        6248     4719    -1529     
==========================================
- Hits         3849     3065     -784     
+ Misses       2066     1366     -700     
+ Partials      333      288      -45     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/deislabs/ratify/pull/1356?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs).


Patch:
diff --git a/httpserver/handlers.go b/httpserver/handlers.go
index 92796ef..57c999b 100644
--- a/httpserver/handlers.go
+++ b/httpserver/handlers.go
@@ -197,13 +197,19 @@ func (server *Server) mutate(ctx context.Context, w http.ResponseWriter, r *http
 				returnItem.Error = err.Error()
 				return
 			}
-			parsedReference, err := pkgUtils.ParseSubjectReference(image)
+			requestKey, err := pkgUtils.ParseRequestKey(image)
+			if err != nil {
+				returnItem.Error = err.Error()
+				return
+			}
+			parsedReference, err := pkgUtils.ParseSubjectReference(requestKey.Subject)
 			if err != nil {
 				err = errors.ErrorCodeReferenceInvalid.WithError(err).WithDetail(fmt.Sprintf("failed to parse image reference %s", image))
 				logger.GetLogger(ctx, server.LogOption).Error(err)
 				returnItem.Error = err.Error()
 				return
 			}
+			ctx = ctxUtils.SetContextWithNamespace(ctx, requestKey.Namespace)
 
 			if parsedReference.Digest == "" {
 				var selectedStore referrerstore.ReferrerStore
diff --git a/internal/context/utils.go b/internal/context/utils.go
index deb764d..39c8ae5 100644
--- a/internal/context/utils.go
+++ b/internal/context/utils.go
@@ -29,6 +29,15 @@ func SetContextWithNamespace(ctx context.Context, namespace string) context.Cont
 	return context.WithValue(ctx, contextKeyNamespace, namespace)
 }
 
+// GetNamespace returns the embedded namespace from the context.
+func GetNamespace(ctx context.Context) string {
+	namespace := ctx.Value(contextKeyNamespace)
+	if namespace == nil {
+		return ""
+	}
+	return namespace.(string)
+}
+
 // CreateCacheKey creates a new cache key prefixed with embedded namespace.
 func CreateCacheKey(ctx context.Context, key string) string {
 	namespace := ctx.Value(contextKeyNamespace)


Test patch:
diff --git a/internal/context/utils_test.go b/internal/context/utils_test.go
index a446a04..1b2d94a 100644
--- a/internal/context/utils_test.go
+++ b/internal/context/utils_test.go
@@ -78,3 +78,40 @@ func TestCreateCacheKey(t *testing.T) {
 		})
 	}
 }
+
+func TestGetNamespace(t *testing.T) {
+	testCases := []struct {
+		name         string
+		namespaceSet bool
+		namespace    string
+	}{
+		{
+			name:         "no namespace",
+			namespaceSet: false,
+		},
+		{
+			name:         "empty namespace",
+			namespaceSet: true,
+			namespace:    "",
+		},
+		{
+			name:         "with namespace",
+			namespaceSet: true,
+			namespace:    testNamespace,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := context.Background()
+			if tc.namespaceSet {
+				ctx = SetContextWithNamespace(ctx, tc.namespace)
+			}
+
+			namespace := GetNamespace(ctx)
+			if namespace != tc.namespace {
+				t.Fatalf("expected namespace %s, got %s", tc.namespace, namespace)
+			}
+		})
+	}
+}