diff --git a/charts/kserve-resources/templates/configmap.yaml b/charts/kserve-resources/templates/configmap.yaml index 8b4dd0e6c31..346d3e2895d 100644 --- a/charts/kserve-resources/templates/configmap.yaml +++ b/charts/kserve-resources/templates/configmap.yaml @@ -64,7 +64,6 @@ data: "memoryLimit": "1Gi", "cpuRequest": "100m", "cpuLimit": "1", - "storageSpecSecretName": "storage-config", "enableDirectPvcVolumeMount": false } storageInitializer: |- @@ -84,9 +83,6 @@ data: # cpuLimit is the limits.cpu to set for the storage initializer init container. "cpuLimit": "1", - # storageSpecSecretName contains the secret name which has the credentials for downloading the model. - "storageSpecSecretName": "storage-config", - # enableDirectPvcVolumeMount controls whether users can mount pvc volumes directly. # if pvc volume is provided in storageuri then the pvc volume is directly mounted to /mnt/models in the user container. # rather than symlink it to a shared volume. For more info see https://github.com/kserve/kserve/issues/2737 @@ -97,6 +93,8 @@ data: # Example credentials: |- { + "storageSpecSecretName": "storage-config", + "storageSecretNameAnnotation": "serving.kserve.io/storageSecretName", "gcs": { "gcsCredentialFileName": "gcloud-application-credentials.json" }, @@ -124,6 +122,14 @@ data: # The rest of the fields are used in both authentication methods (IAM Role for Service Account & IAM User Access Key Secret) if a non-empty value is provided. credentials: |- { + # storageSpecSecretName contains the secret name which has the credentials for downloading the model. + # This option is used when specifying the storage spec on isvc yaml. + "storageSpecSecretName": "storage-config", + + # The annotation can be specified on isvc yaml to allow overriding with the secret name reference from the annotation value. + # When using storageUri the order of the precedence is: secret name reference annotation > secret name references from service account + # When using storageSpec the order of the precedence is: secret name reference annotation > storageSpecSecretName in configmap + "storageSecretNameAnnotation": "serving.kserve.io/storageSecretName", # Configuration for google cloud storage "gcs": { # gcsCredentialFileName specifies the filename of the gcs credential @@ -419,6 +425,8 @@ data: } credentials: |- { + "storageSpecSecretName": "{{ .Values.kserve.storage.storageSpecSecretName }}", + "storageSecretNameAnnotation": "{{ .Values.kserve.storage.storageSecretNameAnnotation }}", "gcs": { "gcsCredentialFileName": "gcloud-application-credentials.json" }, @@ -475,8 +483,7 @@ data: "memoryRequest": "100Mi", "memoryLimit": "1Gi", "cpuRequest": "100m", - "cpuLimit": "1", - "storageSpecSecretName": "{{ .Values.kserve.storage.storageSpecSecretName }}" + "cpuLimit": "1" } metricsAggregator: |- { diff --git a/charts/kserve-resources/values.yaml b/charts/kserve-resources/values.yaml index f0e62f30460..6eb69df2e27 100644 --- a/charts/kserve-resources/values.yaml +++ b/charts/kserve-resources/values.yaml @@ -11,6 +11,7 @@ kserve: image: kserve/storage-initializer tag: *defaultVersion storageSpecSecretName: storage-config + storageSecretNameAnnotation: serving.kserve.io/secretName s3: accessKeyIdName: AWS_ACCESS_KEY_ID secretAccessKeyName: AWS_SECRET_ACCESS_KEY diff --git a/config/configmap/inferenceservice.yaml b/config/configmap/inferenceservice.yaml index edbc4a04ccb..d58408a350c 100644 --- a/config/configmap/inferenceservice.yaml +++ b/config/configmap/inferenceservice.yaml @@ -63,7 +63,6 @@ data: "memoryLimit": "1Gi", "cpuRequest": "100m", "cpuLimit": "1", - "storageSpecSecretName": "storage-config", "enableDirectPvcVolumeMount": false } storageInitializer: |- @@ -82,9 +81,6 @@ data: # cpuLimit is the limits.cpu to set for the storage initializer init container. "cpuLimit": "1", - - # storageSpecSecretName contains the secret name which has the credentials for downloading the model. - "storageSpecSecretName": "storage-config", # enableDirectPvcVolumeMount controls whether users can mount pvc volumes directly. # if pvc volume is provided in storageuri then the pvc volume is directly mounted to /mnt/models in the user container. @@ -96,6 +92,8 @@ data: # Example credentials: |- { + "storageSpecSecretName": "storage-config", + "storageSecretNameAnnotation": "serving.kserve.io/storageSecretName", "gcs": { "gcsCredentialFileName": "gcloud-application-credentials.json" }, @@ -123,6 +121,14 @@ data: # The rest of the fields are used in both authentication methods (IAM Role for Service Account & IAM User Access Key Secret) if a non-empty value is provided. credentials: |- { + # storageSpecSecretName contains the secret name which has the credentials for downloading the model. + # This option is used when specifying the storage spec on isvc yaml. + "storageSpecSecretName": "storage-config", + + # The annotation can be specified on isvc yaml to allow overriding with the secret name reference from the annotation value. + # When using storageUri the order of the precedence is: secret name reference annotation > secret name references from service account + # When using storageSpec the order of the precedence is: secret name reference annotation > storageSpecSecretName in configmap + # Configuration for google cloud storage "gcs": { # gcsCredentialFileName specifies the filename of the gcs credential @@ -411,12 +417,13 @@ data: "memoryLimit": "1Gi", "cpuRequest": "100m", "cpuLimit": "1", - "storageSpecSecretName": "storage-config", "enableDirectPvcVolumeMount": false } credentials: |- { + "storageSpecSecretName": "storage-config", + "storageSecretNameAnnotation": "serving.kserve.io/storageSecretName", "gcs": { "gcsCredentialFileName": "gcloud-application-credentials.json" }, diff --git a/docs/samples/storage/uri/README.md b/docs/samples/storage/uri/README.md index fcf8809cf99..cbc2182d0e9 100644 --- a/docs/samples/storage/uri/README.md +++ b/docs/samples/storage/uri/README.md @@ -4,11 +4,11 @@ This allows you to specify a model object via the URI (Uniform Resource Identifi This `storageUri` option supports single file models, like `sklearn` which is specified by a [joblib](https://joblib.readthedocs.io/en/latest/) file, or artifacts (e.g. `tar` or `zip`) which contain all the necessary dependencies for other model types (e.g. `tensorflow` or `pytorch`). Here, we'll show examples from both of the above. ## Setup -1. Your ~/.kube/config should point to a cluster with [KFServing installed](https://github.com/kubeflow/kfserving/#install-kfserving). +1. Your ~/.kube/config should point to a cluster with [KServe installed](https://github.com/kserve/kserve). 2. Your cluster's Istio Ingress gateway must be [network-accessible](https://istio.io/latest/docs/tasks/traffic-management/ingress/ingress-control/). 3. Your cluster's Istio Egress gateway must [allow http / https traffic](https://istio.io/latest/docs/tasks/traffic-management/egress/egress-gateway/) -## Create HTTP/HTTPS header Secret and attach to Service account +## Create HTTP/HTTPS header Secret If you do not require headers in your HTTP/HTTPS service request then you can skip this step. You can define headers using the following format: @@ -22,15 +22,7 @@ data: https-host: ZXhhbXBsZS5jb20= headers: |- ewoiYWNjb3VudC1uYW1lIjogInNvbWVfYWNjb3VudF9uYW1lIiwKInNlY3JldC1rZXkiOiAic29tZV9zZWNyZXRfa2V5Igp9 ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: sa -secrets: - - name: mysecret ``` -Make sure you have serviceAccountName specified in your predictor in your inference service. These headers will be applied to any http/https requests that have the same host. You will need to base64 encode the headers and host. Make sure the headers are in proper json format. ```text @@ -46,9 +38,39 @@ ZXhhbXBsZS5jb20= ewoiYWNjb3VudC1uYW1lIjogInNvbWVfYWNjb3VudF9uYW1lIiwKInNlY3JldC1rZXkiOiAic29tZV9zZWNyZXRfa2V5Igp9 ``` +### Reference The Secret +You can refer the secret with annotation `serving.kserve.io/storageSecretName`. +```yaml +apiVersion: serving.kserve.io/v1beta1 +kind: InferenceService +metadata: + name: sklearn-from-uri + annotations: + serving.kserve.io/storageSecretName: mysecret + +spec: + predictor: + sklearn: + storageUri: https://github.com/tduffy000/kfserving-uri-examples/blob/master/sklearn/frozen/model.joblib?raw=true +``` + +Alternatively you can attach the secret name references to the service account secrets. + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sa +secrets: + - name: mysecret +``` + +Make sure you have serviceAccountName specified in your predictor in your inference service. These headers will be applied to any http/https requests that have the same host. + + ## Sklearn ### Train and freeze the model -Here, we'll train a simple iris model. Please note that `kfserving` requires `sklearn==0.20.3`. +Here, we'll train a simple iris model. Please note that `kserve` requires `scikit-learn==1.0.2`. ```python from sklearn import svm @@ -74,16 +96,14 @@ Now, you'll need to take that frozen model object and put it somewhere on the we ### Specify and create the `InferenceService` ```yaml -apiVersion: serving.kserve.io/v1alpha2 +apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: sklearn-from-uri spec: - default: - predictor: - sklearn: - storageUri: https://github.com/tduffy000/kfserving-uri-examples/blob/master/sklearn/frozen/model.joblib?raw=true - + predictor: + sklearn: + storageUri: https://github.com/tduffy000/kfserving-uri-examples/blob/master/sklearn/frozen/model.joblib?raw=true ``` Apply the CRD, @@ -95,7 +115,7 @@ Expected Output $ inferenceservice.serving.kserve.io/sklearn-from-uri created ``` ### Run a prediction -The first is to [determine the ingress IP and ports](https://github.com/kubeflow/kfserving/blob/master/README.md#determine-the-ingress-ip-and-ports) and set `INGRESS_HOST` and `INGRESS_PORT`. +The first is to [determine the ingress IP and ports](https://kserve.github.io/website/master/get_started/first_isvc/#4-determine-the-ingress-ip-and-ports) and set `INGRESS_HOST` and `INGRESS_PORT`. Now, if everything went according to plan you should be able to hit the endpoint exposing the model we just uploaded. @@ -193,19 +213,19 @@ Where we assume the `0001/` directory has the structure: Note that building the tarball from the directory specifying a version number is required for `tensorflow`. Now, you can either push the `.tar` or `.tgz` file to some remote uri. + ### Specify and create the `InferenceService` And again, if everything went to plan we should be able to pull down the tarball and expose the endpoint. ```yaml -apiVersion: serving.kserve.io/v1alpha2 +apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: tensorflow-from-uri-gzip spec: - default: - predictor: - tensorflow: - storageUri: https://raw.githubusercontent.com/tduffy000/kfserving-uri-examples/master/tensorflow/frozen/model_artifacts.tar.gz + predictor: + tensorflow: + storageUri: https://raw.githubusercontent.com/tduffy000/kfserving-uri-examples/master/tensorflow/frozen/model_artifacts.tar.gz ``` Apply the CRD, ```bash @@ -217,7 +237,7 @@ $ inferenceservice.serving.kserve.io/tensorflow-from-uri created ``` ## Run a prediction -Again, make sure to first [determine the ingress IP and ports](https://github.com/kubeflow/kfserving/blob/master/README.md#determine-the-ingress-ip-and-ports) and set `INGRESS_HOST` and `INGRESS_PORT`. +Again, make sure to first [determine the ingress IP and ports](https://kserve.github.io/website/master/get_started/first_isvc/#4-determine-the-ingress-ip-and-ports) and set `INGRESS_HOST` and `INGRESS_PORT`. Now that our endpoint is up and running, we can get some predictions. @@ -266,4 +286,4 @@ $ * Trying 10.0.1.16... ] ] } -``` \ No newline at end of file +``` diff --git a/docs/samples/storage/uri/sklearn.yaml b/docs/samples/storage/uri/sklearn.yaml index bf34599c31c..74ae7621569 100644 --- a/docs/samples/storage/uri/sklearn.yaml +++ b/docs/samples/storage/uri/sklearn.yaml @@ -1,9 +1,8 @@ -apiVersion: serving.kserve.io/v1alpha2 +apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: sklearn-from-uri spec: - default: - predictor: - sklearn: - storageUri: https://github.com/tduffy000/kfserving-uri-examples/blob/master/sklearn/frozen/model.joblib?raw=true + predictor: + sklearn: + storageUri: https://github.com/tduffy000/kfserving-uri-examples/blob/master/sklearn/frozen/model.joblib?raw=true diff --git a/docs/samples/storage/uri/tensorflow.yaml b/docs/samples/storage/uri/tensorflow.yaml index 063244846f3..f38d3cb8019 100644 --- a/docs/samples/storage/uri/tensorflow.yaml +++ b/docs/samples/storage/uri/tensorflow.yaml @@ -1,9 +1,8 @@ -apiVersion: serving.kserve.io/v1alpha2 +apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: tensorflow-from-uri spec: - default: - predictor: - tensorflow: - storageUri: https://raw.githubusercontent.com/tduffy000/kfserving-uri-examples/master/tensorflow/frozen/model_artifacts.tar.gz + predictor: + tensorflow: + storageUri: https://raw.githubusercontent.com/tduffy000/kfserving-uri-examples/master/tensorflow/frozen/model_artifacts.tar.gz diff --git a/hack/quick_install.sh b/hack/quick_install.sh index 99bef7cc275..f3e9d91d615 100755 --- a/hack/quick_install.sh +++ b/hack/quick_install.sh @@ -30,7 +30,7 @@ while getopts ":hsr" option; do esac done -export ISTIO_VERSION=1.16.2 +export ISTIO_VERSION=1.17.2 export KNATIVE_SERVING_VERSION=knative-v1.10.1 export KNATIVE_ISTIO_VERSION=knative-v1.10.0 export KSERVE_VERSION=v0.11.0-rc1 diff --git a/pkg/apis/serving/v1beta1/openapi_generated.go b/pkg/apis/serving/v1beta1/openapi_generated.go index cc1739e2c19..8aae5fec4f7 100644 --- a/pkg/apis/serving/v1beta1/openapi_generated.go +++ b/pkg/apis/serving/v1beta1/openapi_generated.go @@ -1150,7 +1150,6 @@ func schema_pkg_apis_serving_v1alpha1_SupportedModelFormat(ref common.ReferenceC }, }, }, - }, }, } @@ -3765,7 +3764,6 @@ func schema_pkg_apis_serving_v1beta1_ExplainerExtensionSpec(ref common.Reference }, }, }, - }, }, Dependencies: []string{ @@ -4996,7 +4994,6 @@ func schema_pkg_apis_serving_v1beta1_LightGBMSpec(ref common.ReferenceCallback) }, }, }, - }, }, Dependencies: []string{ @@ -5081,7 +5078,6 @@ func schema_pkg_apis_serving_v1beta1_ModelFormat(ref common.ReferenceCallback) c }, }, }, - }, }, } @@ -5706,7 +5702,6 @@ func schema_pkg_apis_serving_v1beta1_ONNXRuntimeSpec(ref common.ReferenceCallbac }, }, }, - }, }, Dependencies: []string{ @@ -5979,7 +5974,6 @@ func schema_pkg_apis_serving_v1beta1_PMMLSpec(ref common.ReferenceCallback) comm }, }, }, - }, }, Dependencies: []string{ @@ -6251,7 +6245,6 @@ func schema_pkg_apis_serving_v1beta1_PaddleServerSpec(ref common.ReferenceCallba }, }, }, - }, }, Dependencies: []string{ @@ -6974,7 +6967,6 @@ func schema_pkg_apis_serving_v1beta1_PredictorExtensionSpec(ref common.Reference }, }, }, - }, }, Dependencies: []string{ @@ -7850,7 +7842,6 @@ func schema_pkg_apis_serving_v1beta1_SKLearnSpec(ref common.ReferenceCallback) c }, }, }, - }, }, Dependencies: []string{ @@ -8172,7 +8163,6 @@ func schema_pkg_apis_serving_v1beta1_TFServingSpec(ref common.ReferenceCallback) }, }, }, - }, }, Dependencies: []string{ @@ -8445,7 +8435,6 @@ func schema_pkg_apis_serving_v1beta1_TorchServeSpec(ref common.ReferenceCallback }, }, }, - }, }, Dependencies: []string{ @@ -9261,7 +9250,6 @@ func schema_pkg_apis_serving_v1beta1_TritonSpec(ref common.ReferenceCallback) co }, }, }, - }, }, Dependencies: []string{ @@ -9534,7 +9522,6 @@ func schema_pkg_apis_serving_v1beta1_XGBoostSpec(ref common.ReferenceCallback) c }, }, }, - }, }, Dependencies: []string{ diff --git a/pkg/apis/serving/v1beta1/zz_generated.deepcopy.go b/pkg/apis/serving/v1beta1/zz_generated.deepcopy.go index 00021c8006f..9936f9a4929 100644 --- a/pkg/apis/serving/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/serving/v1beta1/zz_generated.deepcopy.go @@ -17,24 +17,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Code generated by deepcopy-gen. DO NOT EDIT. +// Code generated by controller-gen. DO NOT EDIT. package v1beta1 import ( - constants "github.com/kserve/kserve/pkg/constants" + "github.com/kserve/kserve/pkg/constants" corev1 "k8s.io/api/core/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - apis "knative.dev/pkg/apis" - duckv1 "knative.dev/pkg/apis/duck/v1" - v1 "knative.dev/serving/pkg/apis/serving/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" + "knative.dev/pkg/apis/duck/v1" + servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ARTExplainerSpec) DeepCopyInto(out *ARTExplainerSpec) { *out = *in in.ExplainerExtensionSpec.DeepCopyInto(&out.ExplainerExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ARTExplainerSpec. @@ -51,7 +50,6 @@ func (in *ARTExplainerSpec) DeepCopy() *ARTExplainerSpec { func (in *AlibiExplainerSpec) DeepCopyInto(out *AlibiExplainerSpec) { *out = *in in.ExplainerExtensionSpec.DeepCopyInto(&out.ExplainerExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlibiExplainerSpec. @@ -82,7 +80,6 @@ func (in *Batcher) DeepCopyInto(out *Batcher) { *out = new(int) **out = **in } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Batcher. @@ -152,7 +149,6 @@ func (in *ComponentExtensionSpec) DeepCopyInto(out *ComponentExtensionSpec) { (*out)[key] = val } } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentExtensionSpec. @@ -170,7 +166,7 @@ func (in *ComponentStatusSpec) DeepCopyInto(out *ComponentStatusSpec) { *out = *in if in.Traffic != nil { in, out := &in.Traffic, &out.Traffic - *out = make([]v1.TrafficTarget, len(*in)) + *out = make([]servingv1.TrafficTarget, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -192,10 +188,9 @@ func (in *ComponentStatusSpec) DeepCopyInto(out *ComponentStatusSpec) { } if in.Address != nil { in, out := &in.Address, &out.Address - *out = new(duckv1.Addressable) + *out = new(v1.Addressable) (*in).DeepCopyInto(*out) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentStatusSpec. @@ -212,7 +207,6 @@ func (in *ComponentStatusSpec) DeepCopy() *ComponentStatusSpec { func (in *CustomExplainer) DeepCopyInto(out *CustomExplainer) { *out = *in in.PodSpec.DeepCopyInto(&out.PodSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomExplainer. @@ -229,7 +223,6 @@ func (in *CustomExplainer) DeepCopy() *CustomExplainer { func (in *CustomPredictor) DeepCopyInto(out *CustomPredictor) { *out = *in in.PodSpec.DeepCopyInto(&out.PodSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomPredictor. @@ -246,7 +239,6 @@ func (in *CustomPredictor) DeepCopy() *CustomPredictor { func (in *CustomTransformer) DeepCopyInto(out *CustomTransformer) { *out = *in in.PodSpec.DeepCopyInto(&out.PodSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomTransformer. @@ -259,38 +251,6 @@ func (in *CustomTransformer) DeepCopy() *CustomTransformer { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DeployConfig) DeepCopyInto(out *DeployConfig) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeployConfig. -func (in *DeployConfig) DeepCopy() *DeployConfig { - if in == nil { - return nil - } - out := new(DeployConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExplainerConfig) DeepCopyInto(out *ExplainerConfig) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExplainerConfig. -func (in *ExplainerConfig) DeepCopy() *ExplainerConfig { - if in == nil { - return nil - } - out := new(ExplainerConfig) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExplainerExtensionSpec) DeepCopyInto(out *ExplainerExtensionSpec) { *out = *in @@ -312,7 +272,6 @@ func (in *ExplainerExtensionSpec) DeepCopyInto(out *ExplainerExtensionSpec) { *out = new(StorageSpec) (*in).DeepCopyInto(*out) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExplainerExtensionSpec. @@ -340,7 +299,6 @@ func (in *ExplainerSpec) DeepCopyInto(out *ExplainerSpec) { } in.PodSpec.DeepCopyInto(&out.PodSpec) in.ComponentExtensionSpec.DeepCopyInto(&out.ComponentExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExplainerSpec. @@ -353,24 +311,6 @@ func (in *ExplainerSpec) DeepCopy() *ExplainerSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ExplainersConfig) DeepCopyInto(out *ExplainersConfig) { - *out = *in - out.AlibiExplainer = in.AlibiExplainer - out.ARTExplainer = in.ARTExplainer - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExplainersConfig. -func (in *ExplainersConfig) DeepCopy() *ExplainersConfig { - if in == nil { - return nil - } - out := new(ExplainersConfig) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FailureInfo) DeepCopyInto(out *FailureInfo) { *out = *in @@ -378,7 +318,6 @@ func (in *FailureInfo) DeepCopyInto(out *FailureInfo) { in, out := &in.Time, &out.Time *out = (*in).DeepCopy() } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailureInfo. @@ -398,7 +337,6 @@ func (in *InferenceService) DeepCopyInto(out *InferenceService) { in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferenceService. @@ -431,7 +369,6 @@ func (in *InferenceServiceList) DeepCopyInto(out *InferenceServiceList) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferenceServiceList. @@ -466,7 +403,6 @@ func (in *InferenceServiceSpec) DeepCopyInto(out *InferenceServiceSpec) { *out = new(TransformerSpec) (*in).DeepCopyInto(*out) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferenceServiceSpec. @@ -485,7 +421,7 @@ func (in *InferenceServiceStatus) DeepCopyInto(out *InferenceServiceStatus) { in.Status.DeepCopyInto(&out.Status) if in.Address != nil { in, out := &in.Address, &out.Address - *out = new(duckv1.Addressable) + *out = new(v1.Addressable) (*in).DeepCopyInto(*out) } if in.URL != nil { @@ -501,7 +437,6 @@ func (in *InferenceServiceStatus) DeepCopyInto(out *InferenceServiceStatus) { } } in.ModelStatus.DeepCopyInto(&out.ModelStatus) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferenceServiceStatus. @@ -514,49 +449,10 @@ func (in *InferenceServiceStatus) DeepCopy() *InferenceServiceStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *InferenceServicesConfig) DeepCopyInto(out *InferenceServicesConfig) { - *out = *in - out.Explainers = in.Explainers - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferenceServicesConfig. -func (in *InferenceServicesConfig) DeepCopy() *InferenceServicesConfig { - if in == nil { - return nil - } - out := new(InferenceServicesConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IngressConfig) DeepCopyInto(out *IngressConfig) { - *out = *in - if in.IngressClassName != nil { - in, out := &in.IngressClassName, &out.IngressClassName - *out = new(string) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressConfig. -func (in *IngressConfig) DeepCopy() *IngressConfig { - if in == nil { - return nil - } - out := new(IngressConfig) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LightGBMSpec) DeepCopyInto(out *LightGBMSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LightGBMSpec. @@ -577,7 +473,6 @@ func (in *LoggerSpec) DeepCopyInto(out *LoggerSpec) { *out = new(string) **out = **in } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoggerSpec. @@ -593,7 +488,6 @@ func (in *LoggerSpec) DeepCopy() *LoggerSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ModelCopies) DeepCopyInto(out *ModelCopies) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelCopies. @@ -614,7 +508,6 @@ func (in *ModelFormat) DeepCopyInto(out *ModelFormat) { *out = new(string) **out = **in } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelFormat. @@ -630,7 +523,6 @@ func (in *ModelFormat) DeepCopy() *ModelFormat { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ModelRevisionStates) DeepCopyInto(out *ModelRevisionStates) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelRevisionStates. @@ -653,7 +545,6 @@ func (in *ModelSpec) DeepCopyInto(out *ModelSpec) { **out = **in } in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelSpec. @@ -684,7 +575,6 @@ func (in *ModelStatus) DeepCopyInto(out *ModelStatus) { *out = new(ModelCopies) **out = **in } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelStatus. @@ -701,7 +591,6 @@ func (in *ModelStatus) DeepCopy() *ModelStatus { func (in *ONNXRuntimeSpec) DeepCopyInto(out *ONNXRuntimeSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ONNXRuntimeSpec. @@ -718,7 +607,6 @@ func (in *ONNXRuntimeSpec) DeepCopy() *ONNXRuntimeSpec { func (in *PMMLSpec) DeepCopyInto(out *PMMLSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PMMLSpec. @@ -735,7 +623,6 @@ func (in *PMMLSpec) DeepCopy() *PMMLSpec { func (in *PaddleServerSpec) DeepCopyInto(out *PaddleServerSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PaddleServerSpec. @@ -906,7 +793,6 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpec. @@ -943,7 +829,6 @@ func (in *PredictorExtensionSpec) DeepCopyInto(out *PredictorExtensionSpec) { *out = new(StorageSpec) (*in).DeepCopyInto(*out) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredictorExtensionSpec. @@ -1011,7 +896,6 @@ func (in *PredictorSpec) DeepCopyInto(out *PredictorSpec) { } in.PodSpec.DeepCopyInto(&out.PodSpec) in.ComponentExtensionSpec.DeepCopyInto(&out.ComponentExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredictorSpec. @@ -1028,7 +912,6 @@ func (in *PredictorSpec) DeepCopy() *PredictorSpec { func (in *SKLearnSpec) DeepCopyInto(out *SKLearnSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SKLearnSpec. @@ -1070,7 +953,6 @@ func (in *StorageSpec) DeepCopyInto(out *StorageSpec) { *out = new(string) **out = **in } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageSpec. @@ -1087,7 +969,6 @@ func (in *StorageSpec) DeepCopy() *StorageSpec { func (in *TFServingSpec) DeepCopyInto(out *TFServingSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TFServingSpec. @@ -1104,7 +985,6 @@ func (in *TFServingSpec) DeepCopy() *TFServingSpec { func (in *TorchServeSpec) DeepCopyInto(out *TorchServeSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TorchServeSpec. @@ -1122,7 +1002,6 @@ func (in *TransformerSpec) DeepCopyInto(out *TransformerSpec) { *out = *in in.PodSpec.DeepCopyInto(&out.PodSpec) in.ComponentExtensionSpec.DeepCopyInto(&out.ComponentExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TransformerSpec. @@ -1139,7 +1018,6 @@ func (in *TransformerSpec) DeepCopy() *TransformerSpec { func (in *TritonSpec) DeepCopyInto(out *TritonSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TritonSpec. @@ -1156,7 +1034,6 @@ func (in *TritonSpec) DeepCopy() *TritonSpec { func (in *XGBoostSpec) DeepCopyInto(out *XGBoostSpec) { *out = *in in.PredictorExtensionSpec.DeepCopyInto(&out.PredictorExtensionSpec) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XGBoostSpec. diff --git a/pkg/credentials/service_account_credentials.go b/pkg/credentials/service_account_credentials.go index e0286c86ffe..e63adb919e8 100644 --- a/pkg/credentials/service_account_credentials.go +++ b/pkg/credentials/service_account_credentials.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/kserve/kserve/pkg/constants" "strings" "github.com/kserve/kserve/pkg/credentials/https" @@ -55,8 +56,10 @@ var ( ) type CredentialConfig struct { - S3 s3.S3Config `json:"s3,omitempty"` - GCS gcs.GCSConfig `json:"gcs,omitempty"` + S3 s3.S3Config `json:"s3,omitempty"` + GCS gcs.GCSConfig `json:"gcs,omitempty"` + StorageSpecSecretName string `json:"storageSpecSecretName,omitempty"` + StorageSecretNameAnnotation string `json:"storageSecretNameAnnotation,omitempty"` } type CredentialBuilder struct { @@ -64,9 +67,9 @@ type CredentialBuilder struct { config CredentialConfig } -var log = logf.Log.WithName("CredentialBulder") +var log = logf.Log.WithName("CredentialBuilder") -func NewCredentialBulder(client client.Client, config *v1.ConfigMap) *CredentialBuilder { +func NewCredentialBuilder(client client.Client, config *v1.ConfigMap) *CredentialBuilder { credentialConfig := CredentialConfig{} if credential, ok := config.Data[CredentialConfigKeyName]; ok { err := json.Unmarshal([]byte(credential), &credentialConfig) @@ -74,19 +77,30 @@ func NewCredentialBulder(client client.Client, config *v1.ConfigMap) *Credential panic(fmt.Errorf("Unable to unmarshall json string due to %v ", err)) } } + return &CredentialBuilder{ client: client, config: credentialConfig, } } -func (c *CredentialBuilder) CreateStorageSpecSecretEnvs(namespace string, storageKey string, - storageSecretName string, overrideParams map[string]string, container *v1.Container) error { +func (c *CredentialBuilder) CreateStorageSpecSecretEnvs(namespace string, annotations map[string]string, storageKey string, + overrideParams map[string]string, container *v1.Container) error { stype, ok := overrideParams["type"] - bucket := overrideParams["bucket"] + storageSecretName := constants.DefaultStorageSpecSecret + if c.config.StorageSpecSecretName != "" { + storageSecretName = c.config.StorageSpecSecretName + } + // secret annotation takes precedence + if annotations != nil { + if secretName, ok := annotations[c.config.StorageSecretNameAnnotation]; ok { + storageSecretName = secretName + } + } + secret := &v1.Secret{} var storageData []byte if err := c.client.Get(context.TODO(), @@ -170,21 +184,11 @@ func (c *CredentialBuilder) CreateStorageSpecSecretEnvs(namespace string, storag return nil } -func (c *CredentialBuilder) CreateSecretVolumeAndEnv(namespace string, serviceAccountName string, +func (c *CredentialBuilder) CreateSecretVolumeAndEnv(namespace string, annotations map[string]string, serviceAccountName string, container *v1.Container, volumes *[]v1.Volume) error { if serviceAccountName == "" { serviceAccountName = "default" } - s3SecretAccessKeyName := s3.AWSSecretAccessKeyName - gcsCredentialFileName := gcs.GCSCredentialFileName - - if c.config.S3.S3SecretAccessKeyName != "" { - s3SecretAccessKeyName = c.config.S3.S3SecretAccessKeyName - } - - if c.config.GCS.GCSCredentialFileName != "" { - gcsCredentialFileName = c.config.GCS.GCSCredentialFileName - } serviceAccount := &v1.ServiceAccount{} err := c.client.Get(context.TODO(), types.NamespacedName{Name: serviceAccountName, @@ -203,56 +207,90 @@ func (c *CredentialBuilder) CreateSecretVolumeAndEnv(namespace string, serviceAc } } + // secret name annotation takes precedence + if annotations != nil && c.config.StorageSecretNameAnnotation != "" { + if secretName, ok := annotations[c.config.StorageSecretNameAnnotation]; ok { + err := c.mountSecretCredential(secretName, namespace, container, volumes) + if err != nil { + log.Error(err, "Failed to amount the secret credentials", "secretName", secretName) + return err + } + return nil + } + } + + // Find the secret references from service account for _, secretRef := range serviceAccount.Secrets { - log.Info("found secret", "SecretName", secretRef.Name) - secret := &v1.Secret{} - err := c.client.Get(context.TODO(), types.NamespacedName{Name: secretRef.Name, - Namespace: namespace}, secret) + err := c.mountSecretCredential(secretRef.Name, namespace, container, volumes) if err != nil { - log.Error(err, "Failed to find secret", "SecretName", secretRef.Name) - continue - } - if _, ok := secret.Data[s3SecretAccessKeyName]; ok { - log.Info("Setting secret envs for s3", "S3Secret", secret.Name) - envs := s3.BuildSecretEnvs(secret, &c.config.S3) - // Merge envs here to override values possibly present from IAM Role annotations with values from secret annotations - container.Env = utils.MergeEnvs(container.Env, envs) - } else if _, ok := secret.Data[gcsCredentialFileName]; ok { - log.Info("Setting secret volume for gcs", "GCSSecret", secret.Name) - volume, volumeMount := gcs.BuildSecretVolume(secret) - *volumes = utils.AppendVolumeIfNotExists(*volumes, volume) - container.VolumeMounts = - append(container.VolumeMounts, volumeMount) - container.Env = append(container.Env, - v1.EnvVar{ - Name: gcs.GCSCredentialEnvKey, - Value: gcs.GCSCredentialVolumeMountPath + gcsCredentialFileName, - }) - } else if _, ok := secret.Data[azure.LegacyAzureClientId]; ok { - log.Info("Setting secret envs for azure", "AzureSecret", secret.Name) - envs := azure.BuildSecretEnvs(secret) - container.Env = append(container.Env, envs...) - } else if _, ok := secret.Data[azure.AzureClientId]; ok { - log.Info("Setting secret envs for azure", "AzureSecret", secret.Name) - envs := azure.BuildSecretEnvs(secret) - container.Env = append(container.Env, envs...) - } else if _, ok := secret.Data[azure.AzureStorageAccessKey]; ok { - log.Info("Setting secret envs with azure storage access key for azure", "AzureSecret", secret.Name) - envs := azure.BuildStorageAccessKeySecretEnv(secret) - container.Env = append(container.Env, envs...) - } else if _, ok := secret.Data[https.HTTPSHost]; ok { - log.Info("Setting secret volume from uri", "HTTP(S)Secret", secret.Name) - envs := https.BuildSecretEnvs(secret) - container.Env = append(container.Env, envs...) - } else if _, ok := secret.Data[hdfs.HdfsNamenode]; ok { - log.Info("Setting secret for hdfs", "HdfsSecret", secret.Name) - volume, volumeMount := hdfs.BuildSecret(secret) - *volumes = utils.AppendVolumeIfNotExists(*volumes, volume) - container.VolumeMounts = append(container.VolumeMounts, volumeMount) - } else { - log.V(5).Info("Skipping non gcs/s3/azure secret", "Secret", secret.Name) + return err } } return nil } + +func (c *CredentialBuilder) mountSecretCredential(secretName string, namespace string, + container *v1.Container, volumes *[]v1.Volume) error { + + secret := &v1.Secret{} + err := c.client.Get(context.TODO(), types.NamespacedName{Name: secretName, + Namespace: namespace}, secret) + if err != nil { + log.Error(err, "Failed to find secret", "SecretName", secretName) + return err + } else { + log.Info("found secret", "SecretName", secretName) + } + s3SecretAccessKeyName := s3.AWSSecretAccessKeyName + gcsCredentialFileName := gcs.GCSCredentialFileName + + if c.config.S3.S3SecretAccessKeyName != "" { + s3SecretAccessKeyName = c.config.S3.S3SecretAccessKeyName + } + + if c.config.GCS.GCSCredentialFileName != "" { + gcsCredentialFileName = c.config.GCS.GCSCredentialFileName + } + if _, ok := secret.Data[s3SecretAccessKeyName]; ok { + log.Info("Setting secret envs for s3", "S3Secret", secret.Name) + envs := s3.BuildSecretEnvs(secret, &c.config.S3) + // Merge envs here to override values possibly present from IAM Role annotations with values from secret annotations + container.Env = utils.MergeEnvs(container.Env, envs) + } else if _, ok := secret.Data[gcsCredentialFileName]; ok { + log.Info("Setting secret volume for gcs", "GCSSecret", secret.Name) + volume, volumeMount := gcs.BuildSecretVolume(secret) + *volumes = utils.AppendVolumeIfNotExists(*volumes, volume) + container.VolumeMounts = + append(container.VolumeMounts, volumeMount) + container.Env = append(container.Env, + v1.EnvVar{ + Name: gcs.GCSCredentialEnvKey, + Value: gcs.GCSCredentialVolumeMountPath + gcsCredentialFileName, + }) + } else if _, ok := secret.Data[azure.LegacyAzureClientId]; ok { + log.Info("Setting secret envs for azure", "AzureSecret", secret.Name) + envs := azure.BuildSecretEnvs(secret) + container.Env = append(container.Env, envs...) + } else if _, ok := secret.Data[azure.AzureClientId]; ok { + log.Info("Setting secret envs for azure", "AzureSecret", secret.Name) + envs := azure.BuildSecretEnvs(secret) + container.Env = append(container.Env, envs...) + } else if _, ok := secret.Data[azure.AzureStorageAccessKey]; ok { + log.Info("Setting secret envs with azure storage access key for azure", "AzureSecret", secret.Name) + envs := azure.BuildStorageAccessKeySecretEnv(secret) + container.Env = append(container.Env, envs...) + } else if _, ok := secret.Data[https.HTTPSHost]; ok { + log.Info("Setting secret volume from uri", "HTTP(S)Secret", secret.Name) + envs := https.BuildSecretEnvs(secret) + container.Env = append(container.Env, envs...) + } else if _, ok := secret.Data[hdfs.HdfsNamenode]; ok { + log.Info("Setting secret for hdfs", "HdfsSecret", secret.Name) + volume, volumeMount := hdfs.BuildSecret(secret) + *volumes = utils.AppendVolumeIfNotExists(*volumes, volume) + container.VolumeMounts = append(container.VolumeMounts, volumeMount) + } else { + log.V(5).Info("Skipping unsupported secret", "Secret", secret.Name) + } + return nil +} diff --git a/pkg/credentials/service_account_credentials_test.go b/pkg/credentials/service_account_credentials_test.go index 5cf3148555d..18f8f66a0aa 100644 --- a/pkg/credentials/service_account_credentials_test.go +++ b/pkg/credentials/service_account_credentials_test.go @@ -36,6 +36,8 @@ import ( var configMap = &v1.ConfigMap{ Data: map[string]string{ "credentials": `{ + "storageSecretNameAnnotation": "serving.kserve.io/storageSecretName", + "storageSpecSecretName": "storage-secret", "gcs" : {"gcsCredentialFileName": "gcloud-application-credentials.json"}, "s3" : { "s3AccessKeyIDName": "awsAccessKeyID", @@ -162,12 +164,145 @@ func TestS3CredentialBuilder(t *testing.T) { }, } - builder := NewCredentialBulder(c, configMap) + builder := NewCredentialBuilder(c, configMap) for name, scenario := range scenarios { g.Expect(c.Create(context.TODO(), existingServiceAccount)).NotTo(gomega.HaveOccurred()) g.Expect(c.Create(context.TODO(), existingS3Secret)).NotTo(gomega.HaveOccurred()) - err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, scenario.serviceAccount.Name, + err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, nil, + scenario.serviceAccount.Name, + &scenario.inputConfiguration.Spec.Template.Spec.Containers[0], + &scenario.inputConfiguration.Spec.Template.Spec.Volumes, + ) + if scenario.shouldFail && err == nil { + t.Errorf("Test %q failed: returned success but expected error", name) + } + // Validate + if !scenario.shouldFail { + if err != nil { + t.Errorf("Test %q failed: returned error: %v", name, err) + } + if diff := cmp.Diff(scenario.expectedConfiguration, scenario.inputConfiguration); diff != "" { + t.Errorf("Test %q unexpected configuration spec (-want +got): %v", name, diff) + } + } + g.Expect(c.Delete(context.TODO(), existingServiceAccount)).NotTo(gomega.HaveOccurred()) + g.Expect(c.Delete(context.TODO(), existingS3Secret)).NotTo(gomega.HaveOccurred()) + + } +} + +func TestS3CredentialBuilderWithStorageSecret(t *testing.T) { + g := gomega.NewGomegaWithT(t) + existingServiceAccount := &v1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: "default", + }, + } + existingS3Secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "s3-secret", + Namespace: "default", + Annotations: map[string]string{ + s3.InferenceServiceS3SecretEndpointAnnotation: "s3.aws.com", + }, + }, + Data: map[string][]byte{ + "awsAccessKeyID": {}, + "awsSecretAccessKey": {}, + }, + } + scenarios := map[string]struct { + inputConfiguration *knservingv1.Configuration + expectedConfiguration *knservingv1.Configuration + shouldFail bool + }{ + "Build s3 secrets envs": { + inputConfiguration: &knservingv1.Configuration{ + Spec: knservingv1.ConfigurationSpec{ + Template: knservingv1.RevisionTemplateSpec{ + Spec: knservingv1.RevisionSpec{ + PodSpec: v1.PodSpec{ + Containers: []v1.Container{ + {}, + }, + }, + }, + }, + }, + }, + expectedConfiguration: &knservingv1.Configuration{ + Spec: knservingv1.ConfigurationSpec{ + Template: knservingv1.RevisionTemplateSpec{ + Spec: knservingv1.RevisionSpec{ + PodSpec: v1.PodSpec{ + Containers: []v1.Container{ + { + Env: []v1.EnvVar{ + { + Name: s3.AWSAccessKeyId, + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "s3-secret", + }, + Key: "awsAccessKeyID", + }, + }, + }, + { + Name: s3.AWSSecretAccessKey, + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "s3-secret", + }, + Key: "awsSecretAccessKey", + }, + }, + }, + { + Name: s3.S3Endpoint, + Value: "s3.aws.com", + }, + { + Name: s3.AWSEndpointUrl, + Value: "https://s3.aws.com", + }, + { + Name: s3.S3VerifySSL, + Value: "1", + }, + { + Name: s3.AWSAnonymousCredential, + Value: "false", + }, + { + Name: s3.AWSRegion, + Value: "us-east-2", + }, + }, + }, + }, + }, + }, + }, + }, + }, + shouldFail: false, + }, + } + + builder := NewCredentialBuilder(c, configMap) + for name, scenario := range scenarios { + g.Expect(c.Create(context.TODO(), existingServiceAccount)).NotTo(gomega.HaveOccurred()) + g.Expect(c.Create(context.TODO(), existingS3Secret)).NotTo(gomega.HaveOccurred()) + annotations := map[string]string{ + "serving.kserve.io/storageSecretName": "s3-secret", + } + err := builder.CreateSecretVolumeAndEnv(existingServiceAccount.Namespace, annotations, + existingServiceAccount.Name, &scenario.inputConfiguration.Spec.Template.Spec.Containers[0], &scenario.inputConfiguration.Spec.Template.Spec.Volumes, ) @@ -263,11 +398,11 @@ func TestS3ServiceAccountCredentialBuilder(t *testing.T) { }, } - builder := NewCredentialBulder(c, configMap) + builder := NewCredentialBuilder(c, configMap) for name, scenario := range scenarios { g.Expect(c.Create(context.TODO(), existingServiceAccount)).NotTo(gomega.HaveOccurred()) - err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, scenario.serviceAccount.Name, + err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, nil, scenario.serviceAccount.Name, &scenario.inputConfiguration.Spec.Template.Spec.Containers[0], &scenario.inputConfiguration.Spec.Template.Spec.Volumes, ) @@ -373,12 +508,12 @@ func TestGCSCredentialBuilder(t *testing.T) { }, } - builder := NewCredentialBulder(c, configMap) + builder := NewCredentialBuilder(c, configMap) for name, scenario := range scenarios { g.Expect(c.Create(context.TODO(), existingServiceAccount)).NotTo(gomega.HaveOccurred()) g.Expect(c.Create(context.TODO(), existingGCSSecret)).NotTo(gomega.HaveOccurred()) - err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, scenario.serviceAccount.Name, + err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, nil, scenario.serviceAccount.Name, &scenario.inputConfiguration.Spec.Template.Spec.Containers[0], &scenario.inputConfiguration.Spec.Template.Spec.Volumes, ) @@ -515,10 +650,10 @@ func TestLegacyAzureCredentialBuilder(t *testing.T) { g.Expect(c.Create(context.TODO(), customAzureSecret)).NotTo(gomega.HaveOccurred()) g.Expect(c.Create(context.TODO(), customOnlyServiceAccount)).NotTo(gomega.HaveOccurred()) - builder := NewCredentialBulder(c, configMap) + builder := NewCredentialBuilder(c, configMap) for name, scenario := range scenarios { - err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, scenario.serviceAccount.Name, + err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, nil, scenario.serviceAccount.Name, &scenario.inputConfiguration.Spec.Template.Spec.Containers[0], &scenario.inputConfiguration.Spec.Template.Spec.Volumes, ) @@ -626,10 +761,10 @@ func TestHdfsCredentialBuilder(t *testing.T) { g.Expect(c.Create(context.TODO(), customHdfsSecret)).NotTo(gomega.HaveOccurred()) g.Expect(c.Create(context.TODO(), customOnlyServiceAccount)).NotTo(gomega.HaveOccurred()) - builder := NewCredentialBulder(c, configMap) + builder := NewCredentialBuilder(c, configMap) for name, scenario := range scenarios { - err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, scenario.serviceAccount.Name, + err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, nil, scenario.serviceAccount.Name, &scenario.inputConfiguration.Spec.Template.Spec.Containers[0], &scenario.inputConfiguration.Spec.Template.Spec.Volumes, ) @@ -766,10 +901,10 @@ func TestAzureCredentialBuilder(t *testing.T) { g.Expect(c.Create(context.TODO(), customAzureSecret)).NotTo(gomega.HaveOccurred()) g.Expect(c.Create(context.TODO(), customOnlyServiceAccount)).NotTo(gomega.HaveOccurred()) - builder := NewCredentialBulder(c, configMap) + builder := NewCredentialBuilder(c, configMap) for name, scenario := range scenarios { - err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, scenario.serviceAccount.Name, + err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, nil, scenario.serviceAccount.Name, &scenario.inputConfiguration.Spec.Template.Spec.Containers[0], &scenario.inputConfiguration.Spec.Template.Spec.Volumes, ) @@ -870,10 +1005,10 @@ func TestAzureStorageAccessKeyCredentialBuilder(t *testing.T) { g.Expect(c.Create(context.TODO(), customAzureSecret)).NotTo(gomega.HaveOccurred()) g.Expect(c.Create(context.TODO(), customOnlyServiceAccount)).NotTo(gomega.HaveOccurred()) - builder := NewCredentialBulder(c, configMap) + builder := NewCredentialBuilder(c, configMap) for name, scenario := range scenarios { - err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, scenario.serviceAccount.Name, + err := builder.CreateSecretVolumeAndEnv(scenario.serviceAccount.Namespace, nil, scenario.serviceAccount.Name, &scenario.inputConfiguration.Spec.Template.Spec.Containers[0], &scenario.inputConfiguration.Spec.Template.Spec.Volumes, ) @@ -898,7 +1033,7 @@ func TestAzureStorageAccessKeyCredentialBuilder(t *testing.T) { func TestCredentialBuilder_CreateStorageSpecSecretEnvs(t *testing.T) { g := gomega.NewGomegaWithT(t) namespace := "default" - builder := NewCredentialBulder(c, configMap) + builder := NewCredentialBuilder(c, configMap) scenarios := map[string]struct { secret *v1.Secret @@ -1393,7 +1528,7 @@ func TestCredentialBuilder_CreateStorageSpecSecretEnvs(t *testing.T) { if err := c.Create(context.TODO(), tc.secret); err != nil { t.Errorf("Failed to create secret %s: %v", "storage-secret", err) } - err := builder.CreateStorageSpecSecretEnvs(namespace, tc.storageKey, tc.storageSecretName, tc.overrideParams, tc.container) + err := builder.CreateStorageSpecSecretEnvs(namespace, nil, tc.storageKey, tc.overrideParams, tc.container) if !tc.shouldFail { g.Expect(err).Should(gomega.BeNil()) g.Expect(tc.container).Should(tc.matcher) diff --git a/pkg/webhook/admission/pod/agent_injector.go b/pkg/webhook/admission/pod/agent_injector.go index c783ec0feb7..9b211c4f1ab 100644 --- a/pkg/webhook/admission/pod/agent_injector.go +++ b/pkg/webhook/admission/pod/agent_injector.go @@ -282,6 +282,7 @@ func (ag *AgentInjector) InjectAgent(pod *v1.Pod) error { // Inject credentials if err := ag.credentialBuilder.CreateSecretVolumeAndEnv( pod.Namespace, + pod.Annotations, pod.Spec.ServiceAccountName, agentContainer, &pod.Spec.Volumes, diff --git a/pkg/webhook/admission/pod/agent_injector_test.go b/pkg/webhook/admission/pod/agent_injector_test.go index 0d196343052..cd8e4e0d2f5 100644 --- a/pkg/webhook/admission/pod/agent_injector_test.go +++ b/pkg/webhook/admission/pod/agent_injector_test.go @@ -1101,7 +1101,7 @@ func TestAgentInjector(t *testing.T) { }, } - credentialBuilder := credentials.NewCredentialBulder(c, &v1.ConfigMap{ + credentialBuilder := credentials.NewCredentialBuilder(c, &v1.ConfigMap{ Data: map[string]string{}, }) diff --git a/pkg/webhook/admission/pod/mutator.go b/pkg/webhook/admission/pod/mutator.go index c320967f3c1..0f1ba1f32d9 100644 --- a/pkg/webhook/admission/pod/mutator.go +++ b/pkg/webhook/admission/pod/mutator.go @@ -77,7 +77,7 @@ func (mutator *Mutator) Handle(ctx context.Context, req admission.Request) admis } func (mutator *Mutator) mutate(pod *v1.Pod, configMap *v1.ConfigMap) error { - credentialBuilder := credentials.NewCredentialBulder(mutator.Client, configMap) + credentialBuilder := credentials.NewCredentialBuilder(mutator.Client, configMap) storageInitializerConfig, err := getStorageInitializerConfigs(configMap) if err != nil { diff --git a/pkg/webhook/admission/pod/storage_initializer_injector.go b/pkg/webhook/admission/pod/storage_initializer_injector.go index fb7aa032ab2..99428b40c0b 100644 --- a/pkg/webhook/admission/pod/storage_initializer_injector.go +++ b/pkg/webhook/admission/pod/storage_initializer_injector.go @@ -49,7 +49,6 @@ type StorageInitializerConfig struct { CpuLimit string `json:"cpuLimit"` MemoryRequest string `json:"memoryRequest"` MemoryLimit string `json:"memoryLimit"` - StorageSpecSecretName string `json:"storageSpecSecretName"` EnableDirectPvcVolumeMount bool `json:"enableDirectPvcVolumeMount"` } @@ -299,20 +298,16 @@ func (mi *StorageInitializerInjector) InjectStorageInitializer(pod *v1.Pod) erro storageKey := pod.ObjectMeta.Annotations[constants.StorageSpecKeyAnnotationKey] // Inject Storage Spec credentials if exist if hasStorageSpec == "true" { - storageSpecSecret := mi.config.StorageSpecSecretName var overrideParams map[string]string if storageSpecParam, ok := pod.ObjectMeta.Annotations[constants.StorageSpecParamAnnotationKey]; ok { if err := json.Unmarshal([]byte(storageSpecParam), &overrideParams); err != nil { return err } } - if storageSpecSecret == "" { - storageSpecSecret = constants.DefaultStorageSpecSecret - } if err := mi.credentialBuilder.CreateStorageSpecSecretEnvs( pod.Namespace, + pod.Annotations, storageKey, - storageSpecSecret, overrideParams, initContainer, ); err != nil { @@ -322,6 +317,7 @@ func (mi *StorageInitializerInjector) InjectStorageInitializer(pod *v1.Pod) erro // Inject service account credentials if storage spec doesn't exist if err := mi.credentialBuilder.CreateSecretVolumeAndEnv( pod.Namespace, + pod.Annotations, pod.Spec.ServiceAccountName, initContainer, &pod.Spec.Volumes, diff --git a/pkg/webhook/admission/pod/storage_initializer_injector_test.go b/pkg/webhook/admission/pod/storage_initializer_injector_test.go index 80456344032..c3f94922b7b 100644 --- a/pkg/webhook/admission/pod/storage_initializer_injector_test.go +++ b/pkg/webhook/admission/pod/storage_initializer_injector_test.go @@ -38,7 +38,6 @@ const ( StorageInitializerDefaultCPULimit = "1" StorageInitializerDefaultMemoryRequest = "200Mi" StorageInitializerDefaultMemoryLimit = "1Gi" - StorageInitializerDefaultStorageSpecSecretName = "storage-config" StorageInitializerDefaultEnableDirectPvcVolumeMount = false ) @@ -48,7 +47,6 @@ var ( CpuLimit: StorageInitializerDefaultCPULimit, MemoryRequest: StorageInitializerDefaultMemoryRequest, MemoryLimit: StorageInitializerDefaultMemoryLimit, - StorageSpecSecretName: StorageInitializerDefaultStorageSpecSecretName, EnableDirectPvcVolumeMount: StorageInitializerDefaultEnableDirectPvcVolumeMount, } @@ -349,7 +347,7 @@ func TestStorageInitializerInjector(t *testing.T) { for name, scenario := range scenarios { injector := &StorageInitializerInjector{ - credentialBuilder: credentials.NewCredentialBulder(c, &v1.ConfigMap{ + credentialBuilder: credentials.NewCredentialBuilder(c, &v1.ConfigMap{ Data: map[string]string{}, }), config: storageInitializerConfig, @@ -389,7 +387,7 @@ func TestStorageInitializerFailureCases(t *testing.T) { for name, scenario := range scenarios { injector := &StorageInitializerInjector{ - credentialBuilder: credentials.NewCredentialBulder(c, &v1.ConfigMap{ + credentialBuilder: credentials.NewCredentialBuilder(c, &v1.ConfigMap{ Data: map[string]string{}, }), config: storageInitializerConfig, @@ -488,7 +486,7 @@ func TestCustomSpecStorageUriInjection(t *testing.T) { for name, scenario := range scenarios { injector := &StorageInitializerInjector{ - credentialBuilder: credentials.NewCredentialBulder(c, &v1.ConfigMap{ + credentialBuilder: credentials.NewCredentialBuilder(c, &v1.ConfigMap{ Data: map[string]string{}, }), config: storageInitializerConfig, @@ -935,7 +933,7 @@ func TestCredentialInjection(t *testing.T) { }, } - builder := credentials.NewCredentialBulder(c, configMap) + builder := credentials.NewCredentialBuilder(c, configMap) for name, scenario := range scenarios { g.Expect(c.Create(context.TODO(), scenario.sa)).NotTo(gomega.HaveOccurred()) g.Expect(c.Create(context.TODO(), scenario.secret)).NotTo(gomega.HaveOccurred()) @@ -998,7 +996,7 @@ func TestStorageInitializerConfigmap(t *testing.T) { InitContainers: []v1.Container{ { Name: "storage-initializer", - Image: "kfserving/storage-initializer@sha256:xxx", + Image: "kserve/storage-initializer@sha256:xxx", Args: []string{"gs://foo", constants.DefaultModelLocalMountPath}, Resources: resourceRequirement, TerminationMessagePolicy: "FallbackToLogsOnError", @@ -1025,16 +1023,15 @@ func TestStorageInitializerConfigmap(t *testing.T) { for name, scenario := range scenarios { injector := &StorageInitializerInjector{ - credentialBuilder: credentials.NewCredentialBulder(c, &v1.ConfigMap{ + credentialBuilder: credentials.NewCredentialBuilder(c, &v1.ConfigMap{ Data: map[string]string{}, }), config: &StorageInitializerConfig{ - Image: "kfserving/storage-initializer@sha256:xxx", - CpuRequest: StorageInitializerDefaultCPURequest, - CpuLimit: StorageInitializerDefaultCPULimit, - MemoryRequest: StorageInitializerDefaultMemoryRequest, - MemoryLimit: StorageInitializerDefaultMemoryLimit, - StorageSpecSecretName: StorageInitializerDefaultStorageSpecSecretName, + Image: "kserve/storage-initializer@sha256:xxx", + CpuRequest: StorageInitializerDefaultCPURequest, + CpuLimit: StorageInitializerDefaultCPULimit, + MemoryRequest: StorageInitializerDefaultMemoryRequest, + MemoryLimit: StorageInitializerDefaultMemoryLimit, }, } if err := injector.InjectStorageInitializer(scenario.original); err != nil { @@ -1060,24 +1057,22 @@ func TestGetStorageInitializerConfigs(t *testing.T) { ObjectMeta: metav1.ObjectMeta{}, Data: map[string]string{ StorageInitializerConfigMapKeyName: `{ - "Image": "gcr.io/kfserving/storage-initializer:latest", + "Image": "gcr.io/kserve/storage-initializer:latest", "CpuRequest": "100m", "CpuLimit": "1", "MemoryRequest": "200Mi", - "MemoryLimit": "1Gi", - "StorageSpecSecretName": "storage-secret" + "MemoryLimit": "1Gi" }`, }, BinaryData: map[string][]byte{}, }, matchers: []types.GomegaMatcher{ gomega.Equal(&StorageInitializerConfig{ - Image: "gcr.io/kfserving/storage-initializer:latest", - CpuRequest: "100m", - CpuLimit: "1", - MemoryRequest: "200Mi", - MemoryLimit: "1Gi", - StorageSpecSecretName: "storage-secret", + Image: "gcr.io/kserve/storage-initializer:latest", + CpuRequest: "100m", + CpuLimit: "1", + MemoryRequest: "200Mi", + MemoryLimit: "1Gi", }), gomega.BeNil(), }, @@ -1089,24 +1084,22 @@ func TestGetStorageInitializerConfigs(t *testing.T) { ObjectMeta: metav1.ObjectMeta{}, Data: map[string]string{ StorageInitializerConfigMapKeyName: `{ - "Image": "gcr.io/kfserving/storage-initializer:latest", + "Image": "gcr.io/kserve/storage-initializer:latest", "CpuRequest": "100m", "CpuLimit": "1", "MemoryRequest": "200MC", - "MemoryLimit": "1Gi", - "StorageSpecSecretName": "storage-secret" + "MemoryLimit": "1Gi" }`, }, BinaryData: map[string][]byte{}, }, matchers: []types.GomegaMatcher{ gomega.Equal(&StorageInitializerConfig{ - Image: "gcr.io/kfserving/storage-initializer:latest", - CpuRequest: "100m", - CpuLimit: "1", - MemoryRequest: "200MC", - MemoryLimit: "1Gi", - StorageSpecSecretName: "storage-secret", + Image: "gcr.io/kserve/storage-initializer:latest", + CpuRequest: "100m", + CpuLimit: "1", + MemoryRequest: "200MC", + MemoryLimit: "1Gi", }), gomega.HaveOccurred(), }, @@ -1265,7 +1258,7 @@ func TestDirectVolumeMountForPvc(t *testing.T) { for name, scenario := range scenarios { injector := &StorageInitializerInjector{ - credentialBuilder: credentials.NewCredentialBulder(c, &v1.ConfigMap{ + credentialBuilder: credentials.NewCredentialBuilder(c, &v1.ConfigMap{ Data: map[string]string{}, }), config: &StorageInitializerConfig{ @@ -1589,7 +1582,7 @@ func TestTransformerCollocation(t *testing.T) { for name, scenario := range scenarios { injector := &StorageInitializerInjector{ - credentialBuilder: credentials.NewCredentialBulder(c, &v1.ConfigMap{ + credentialBuilder: credentials.NewCredentialBuilder(c, &v1.ConfigMap{ Data: map[string]string{}, }), config: scenario.storageConfig, diff --git a/python/kserve/kserve/api_client.py b/python/kserve/kserve/api_client.py index d1298accf2d..cc571c6410b 100644 --- a/python/kserve/kserve/api_client.py +++ b/python/kserve/kserve/api_client.py @@ -304,7 +304,7 @@ def __deserialize(self, data, klass): if data is None: return None - if type(klass) == str: + if type(klass) is str: if klass.startswith('list['): sub_kls = re.match(r'list\[(.*)\]', klass).group(1) return [self.__deserialize(sub_data, sub_kls)