mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-29 07:23:25 +08:00
* feat(providers): support stable credential placeholders Add credentials[].stable_placeholder so a running client keeps using an externally rotated static credential through the reference it already holds. Squashed replay of the earlier four commits onto upstream main 71c3cd957; re-derives the frozen schema fingerprints and adapts the provider environment revision test to the added capability argument. Signed-off-by: Shiju <shiju@nvidia.com> * fix(e2e): apply stable-placeholder supervisor image override Use ManagedGateway's per-process supervisor image override so the fixture CA reaches the supervisor when runtime image environment variables take precedence over gateway TOML. Restore the wrapper's original image after each test, following the provider-readiness fixture. Signed-off-by: Shiju <shiju@nvidia.com> * test(server): update public RPC schema fingerprint after merging main Signed-off-by: Shiju <shiju@nvidia.com> --------- Signed-off-by: Shiju <shiju@nvidia.com>
196 lines
6.1 KiB
Go
196 lines
6.1 KiB
Go
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package v1
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/internal/converter"
|
|
pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
type profileClient struct {
|
|
client pb.OpenShellClient
|
|
}
|
|
|
|
func newProfileClient(conn grpc.ClientConnInterface) *profileClient {
|
|
return &profileClient{client: pb.NewOpenShellClient(conn)}
|
|
}
|
|
|
|
func (p *profileClient) List(workspace string, opts ...ListOptions) (*Pager[*ProviderProfile], error) {
|
|
pageSize, err := listPageSize(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pageToken string
|
|
if len(opts) > 0 {
|
|
pageToken = opts[0].PageToken
|
|
}
|
|
return newPager(pageToken, func(ctx context.Context, pageToken string) (*Page[*ProviderProfile], error) {
|
|
req := &pb.ListProviderProfilesRequest{WorkspaceScope: profileWorkspaceScope(workspace), PageSize: pageSize, PageToken: pageToken}
|
|
resp, err := p.client.ListProviderProfiles(ctx, req)
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
profiles := make([]*ProviderProfile, 0, len(resp.GetProfiles()))
|
|
for _, profile := range resp.GetProfiles() {
|
|
profiles = append(profiles, converter.ProviderProfileFromProto(profile))
|
|
}
|
|
return &Page[*ProviderProfile]{Items: profiles, NextPageToken: resp.GetNextPageToken()}, nil
|
|
}), nil
|
|
}
|
|
|
|
func (p *profileClient) ListAll(ctx context.Context, workspace string, opts ...ListOptions) ([]*ProviderProfile, error) {
|
|
pager, err := p.List(workspace, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pager.All(ctx)
|
|
}
|
|
|
|
func (p *profileClient) Get(ctx context.Context, workspace, id string) (*ProviderProfile, error) {
|
|
resp, err := p.client.GetProviderProfile(ctx, &pb.GetProviderProfileRequest{
|
|
Id: id,
|
|
WorkspaceScope: profileWorkspaceScope(workspace),
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
return converter.ProviderProfileFromProto(resp.GetProfile()), nil
|
|
}
|
|
|
|
func (p *profileClient) Import(ctx context.Context, workspace string, items []ProfileImportItem) (*ImportResult, error) {
|
|
pbItems := make([]*pb.ProviderProfileImportItem, len(items))
|
|
for i := range items {
|
|
pbItems[i] = converter.ProfileImportItemToProto(&items[i])
|
|
}
|
|
if profilesRequireStablePlaceholders(pbItems) {
|
|
if _, err := p.lint(ctx, workspace, pbItems); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
resp, err := p.client.ImportProviderProfiles(ctx, &pb.ImportProviderProfilesRequest{
|
|
Profiles: pbItems,
|
|
WorkspaceScope: profileWorkspaceScope(workspace),
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
|
|
result := &ImportResult{
|
|
Imported: resp.GetImported(),
|
|
}
|
|
|
|
for _, d := range resp.GetDiagnostics() {
|
|
if diag := converter.ProfileDiagnosticFromProto(d); diag != nil {
|
|
result.Diagnostics = append(result.Diagnostics, *diag)
|
|
}
|
|
}
|
|
|
|
for _, pp := range resp.GetProfiles() {
|
|
if profile := converter.ProviderProfileFromProto(pp); profile != nil {
|
|
result.Profiles = append(result.Profiles, *profile)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (p *profileClient) Update(ctx context.Context, workspace, id string, expectedResourceVersion uint64, item ProfileImportItem) (*UpdateResult, error) {
|
|
pbItem := converter.ProfileImportItemToProto(&item)
|
|
if items := []*pb.ProviderProfileImportItem{pbItem}; profilesRequireStablePlaceholders(items) {
|
|
if _, err := p.lint(ctx, workspace, items); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
resp, err := p.client.UpdateProviderProfiles(ctx, &pb.UpdateProviderProfilesRequest{
|
|
Id: id,
|
|
Profile: pbItem,
|
|
ExpectedResourceVersion: expectedResourceVersion,
|
|
WorkspaceScope: profileWorkspaceScope(workspace),
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
|
|
result := &UpdateResult{
|
|
Updated: resp.GetUpdated(),
|
|
Profile: converter.ProviderProfileFromProto(resp.GetProfile()),
|
|
}
|
|
|
|
for _, d := range resp.GetDiagnostics() {
|
|
if diag := converter.ProfileDiagnosticFromProto(d); diag != nil {
|
|
result.Diagnostics = append(result.Diagnostics, *diag)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (p *profileClient) Lint(ctx context.Context, workspace string, items []ProfileImportItem) (*LintResult, error) {
|
|
pbItems := make([]*pb.ProviderProfileImportItem, len(items))
|
|
for i := range items {
|
|
pbItems[i] = converter.ProfileImportItemToProto(&items[i])
|
|
}
|
|
|
|
resp, err := p.lint(ctx, workspace, pbItems)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &LintResult{
|
|
Valid: resp.GetValid(),
|
|
}
|
|
|
|
for _, d := range resp.GetDiagnostics() {
|
|
if diag := converter.ProfileDiagnosticFromProto(d); diag != nil {
|
|
result.Diagnostics = append(result.Diagnostics, *diag)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func profilesRequireStablePlaceholders(items []*pb.ProviderProfileImportItem) bool {
|
|
for _, item := range items {
|
|
for _, credential := range item.GetProfile().GetCredentials() {
|
|
if credential.GetStablePlaceholder() {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Lint acknowledges feature support before an opt-in is persisted. Legacy
|
|
// gateways can otherwise silently discard unknown credential fields.
|
|
func (p *profileClient) lint(ctx context.Context, workspace string, items []*pb.ProviderProfileImportItem) (*pb.LintProviderProfilesResponse, error) {
|
|
resp, err := p.client.LintProviderProfiles(ctx, &pb.LintProviderProfilesRequest{
|
|
Profiles: items,
|
|
WorkspaceScope: profileWorkspaceScope(workspace),
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
if profilesRequireStablePlaceholders(items) && !resp.GetSupportsStablePlaceholder() {
|
|
return nil, fmt.Errorf("gateway does not support stable_placeholder; upgrade the gateway before enabling it")
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func (p *profileClient) Delete(ctx context.Context, workspace, id string, opts ...DeleteOptions) (*DeletionResult, error) {
|
|
resp, err := p.client.DeleteProviderProfile(ctx, &pb.DeleteProviderProfileRequest{
|
|
AllowMissing: allowMissing(opts),
|
|
Id: id,
|
|
WorkspaceScope: profileWorkspaceScope(workspace),
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil
|
|
}
|