# Copyright 2023 The KServe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# coding: utf-8
"""
    KServe

    Python SDK for KServe  # noqa: E501

    The version of the OpenAPI document: v0.1
    Generated by: https://openapi-generator.tech
"""

from __future__ import absolute_import

import atexit
import datetime
from dateutil.parser import parse
import json
import mimetypes
from multiprocessing.pool import ThreadPool
import os
import re
import tempfile

# python 2 and python 3 compatibility library
import six
from six.moves.urllib.parse import quote

from kserve.configuration import Configuration
import kserve.models
from kserve import rest
from kserve.exceptions import ApiValueError, ApiException


class ApiClient(object):
    """Generic API client for OpenAPI client library builds.

    OpenAPI generic API client. This client handles the client-
    server communication, and is invariant across implementations. Specifics of
    the methods and models for each application are generated from the OpenAPI
    templates.

    NOTE: This class is auto generated by OpenAPI Generator.
    Ref: https://openapi-generator.tech
    Do not edit the class manually.

    :param configuration: .Configuration object for this client
    :param header_name: a header to pass when making calls to the API.
    :param header_value: a header value to pass when making calls to
        the API.
    :param cookie: a cookie to include in the header when making calls
        to the API
    :param pool_threads: The number of threads to use for async requests
        to the API. More threads means more concurrent API requests.
    """

    PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types
    NATIVE_TYPES_MAPPING = {
        'int': int,
        'long': int if six.PY3 else long,  # noqa: F821
        'float': float,
        'str': str,
        'bool': bool,
        'date': datetime.date,
        'datetime': datetime.datetime,
        'object': object,
    }
    _pool = None

    def __init__(self, configuration=None, header_name=None, header_value=None,
                 cookie=None, pool_threads=1):
        if configuration is None:
            configuration = Configuration.get_default_copy()
        self.configuration = configuration
        self.pool_threads = pool_threads

        self.rest_client = rest.RESTClientObject(configuration)
        self.default_headers = {}
        if header_name is not None:
            self.default_headers[header_name] = header_value
        self.cookie = cookie
        # Set default User-Agent.
        self.user_agent = 'OpenAPI-Generator/0.1/python'
        self.client_side_validation = configuration.client_side_validation

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def close(self):
        if self._pool:
            self._pool.close()
            self._pool.join()
            self._pool = None
            if hasattr(atexit, 'unregister'):
                atexit.unregister(self.close)

    @property
    def pool(self):
        """Create thread pool on first request
         avoids instantiating unused threadpool for blocking clients.
        """
        if self._pool is None:
            atexit.register(self.close)
            self._pool = ThreadPool(self.pool_threads)
        return self._pool

    @property
    def user_agent(self):
        """User agent for this API client"""
        return self.default_headers['User-Agent']

    @user_agent.setter
    def user_agent(self, value):
        self.default_headers['User-Agent'] = value

    def set_default_header(self, header_name, header_value):
        self.default_headers[header_name] = header_value

    def __call_api(
            self, resource_path, method, path_params=None,
            query_params=None, header_params=None, body=None, post_params=None,
            files=None, response_type=None, auth_settings=None,
            _return_http_data_only=None, collection_formats=None,
            _preload_content=True, _request_timeout=None, _host=None):

        config = self.configuration

        # header parameters
        header_params = header_params or {}
        header_params.update(self.default_headers)
        if self.cookie:
            header_params['Cookie'] = self.cookie
        if header_params:
            header_params = self.sanitize_for_serialization(header_params)
            header_params = dict(self.parameters_to_tuples(header_params,
                                                           collection_formats))

        # path parameters
        if path_params:
            path_params = self.sanitize_for_serialization(path_params)
            path_params = self.parameters_to_tuples(path_params,
                                                    collection_formats)
            for k, v in path_params:
                # specified safe chars, encode everything
                resource_path = resource_path.replace(
                    '{%s}' % k,
                    quote(str(v), safe=config.safe_chars_for_path_param)
                )

        # query parameters
        if query_params:
            query_params = self.sanitize_for_serialization(query_params)
            query_params = self.parameters_to_tuples(query_params,
                                                     collection_formats)

        # post parameters
        if post_params or files:
            post_params = post_params if post_params else []
            post_params = self.sanitize_for_serialization(post_params)
            post_params = self.parameters_to_tuples(post_params,
                                                    collection_formats)
            post_params.extend(self.files_parameters(files))

        # auth setting
        self.update_params_for_auth(header_params, query_params, auth_settings)

        # body
        if body:
            body = self.sanitize_for_serialization(body)

        # request url
        if _host is None:
            url = self.configuration.host + resource_path
        else:
            # use server/host defined in path or operation instead
            url = _host + resource_path

        try:
            # perform request and return response
            response_data = self.request(
                method, url, query_params=query_params, headers=header_params,
                post_params=post_params, body=body,
                _preload_content=_preload_content,
                _request_timeout=_request_timeout)
        except ApiException as e:
            e.body = e.body.decode('utf-8') if six.PY3 else e.body
            raise e

        content_type = response_data.getheader('content-type')

        self.last_response = response_data

        return_data = response_data

        if not _preload_content:
            return return_data

        if six.PY3 and response_type not in ["file", "bytes"]:
            match = None
            if content_type is not None:
                match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type)
            encoding = match.group(1) if match else "utf-8"
            response_data.data = response_data.data.decode(encoding)

        # deserialize response data
        if response_type:
            return_data = self.deserialize(response_data, response_type)
        else:
            return_data = None

        if _return_http_data_only:
            return (return_data)
        else:
            return (return_data, response_data.status,
                    response_data.getheaders())

    def sanitize_for_serialization(self, obj):
        """Builds a JSON POST object.

        If obj is None, return None.
        If obj is str, int, long, float, bool, return directly.
        If obj is datetime.datetime, datetime.date
            convert to string in iso8601 format.
        If obj is list, sanitize each element in the list.
        If obj is dict, return the dict.
        If obj is OpenAPI model, return the properties dict.

        :param obj: The data to serialize.
        :return: The serialized form of data.
        """
        if obj is None:
            return None
        elif isinstance(obj, self.PRIMITIVE_TYPES):
            return obj
        elif isinstance(obj, list):
            return [self.sanitize_for_serialization(sub_obj)
                    for sub_obj in obj]
        elif isinstance(obj, tuple):
            return tuple(self.sanitize_for_serialization(sub_obj)
                         for sub_obj in obj)
        elif isinstance(obj, (datetime.datetime, datetime.date)):
            return obj.isoformat()

        if isinstance(obj, dict):
            obj_dict = obj
        else:
            # Convert model obj to dict except
            # attributes `openapi_types`, `attribute_map`
            # and attributes which value is not None.
            # Convert attribute name to json key in
            # model definition for request.
            obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
                        for attr, _ in six.iteritems(obj.openapi_types)
                        if getattr(obj, attr) is not None}

        return {key: self.sanitize_for_serialization(val)
                for key, val in six.iteritems(obj_dict)}

    def deserialize(self, response, response_type):
        """Deserializes response into an object.

        :param response: RESTResponse object to be deserialized.
        :param response_type: class literal for
            deserialized object, or string of class name.

        :return: deserialized object.
        """
        # handle file downloading
        # save response body into a tmp file and return the instance
        if response_type == "file":
            return self.__deserialize_file(response)

        # fetch data from response object
        try:
            data = json.loads(response.data)
        except ValueError:
            data = response.data

        return self.__deserialize(data, response_type)

    def __deserialize(self, data, klass):
        """Deserializes dict, list, str into an object.

        :param data: dict, list or str.
        :param klass: class literal, or string of class name.

        :return: object.
        """
        if data is None:
            return None

        if type(klass) == str:
            if klass.startswith('list['):
                sub_kls = re.match(r'list\[(.*)\]', klass).group(1)
                return [self.__deserialize(sub_data, sub_kls)
                        for sub_data in data]

            if klass.startswith('dict('):
                sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2)
                return {k: self.__deserialize(v, sub_kls)
                        for k, v in six.iteritems(data)}

            # convert str to class
            if klass in self.NATIVE_TYPES_MAPPING:
                klass = self.NATIVE_TYPES_MAPPING[klass]
            else:
                klass = getattr(kserve.models, klass)

        if klass in self.PRIMITIVE_TYPES:
            return self.__deserialize_primitive(data, klass)
        elif klass == object:
            return self.__deserialize_object(data)
        elif klass == datetime.date:
            return self.__deserialize_date(data)
        elif klass == datetime.datetime:
            return self.__deserialize_datetime(data)
        else:
            return self.__deserialize_model(data, klass)

    def call_api(self, resource_path, method,
                 path_params=None, query_params=None, header_params=None,
                 body=None, post_params=None, files=None,
                 response_type=None, auth_settings=None, async_req=None,
                 _return_http_data_only=None, collection_formats=None,
                 _preload_content=True, _request_timeout=None, _host=None):
        """Makes the HTTP request (synchronous) and returns deserialized data.

        To make an async_req request, set the async_req parameter.

        :param resource_path: Path to method endpoint.
        :param method: Method to call.
        :param path_params: Path parameters in the url.
        :param query_params: Query parameters in the url.
        :param header_params: Header parameters to be
            placed in the request header.
        :param body: Request body.
        :param post_params dict: Request post form parameters,
            for `application/x-www-form-urlencoded`, `multipart/form-data`.
        :param auth_settings list: Auth Settings names for the request.
        :param response: Response data type.
        :param files dict: key -> filename, value -> filepath,
            for `multipart/form-data`.
        :param async_req bool: execute request asynchronously
        :param _return_http_data_only: response data without head status code
                                       and headers
        :param collection_formats: dict of collection formats for path, query,
            header, and post parameters.
        :param _preload_content: if False, the urllib3.HTTPResponse object will
                                 be returned without reading/decoding response
                                 data. Default is True.
        :param _request_timeout: timeout setting for this request. If one
                                 number provided, it will be total request
                                 timeout. It can also be a pair (tuple) of
                                 (connection, read) timeouts.
        :return:
            If async_req parameter is True,
            the request will be called asynchronously.
            The method will return the request thread.
            If parameter async_req is False or missing,
            then the method will return the response directly.
        """
        if not async_req:
            return self.__call_api(resource_path, method,
                                   path_params, query_params, header_params,
                                   body, post_params, files,
                                   response_type, auth_settings,
                                   _return_http_data_only, collection_formats,
                                   _preload_content, _request_timeout, _host)

        return self.pool.apply_async(self.__call_api, (resource_path,
                                                       method, path_params,
                                                       query_params,
                                                       header_params, body,
                                                       post_params, files,
                                                       response_type,
                                                       auth_settings,
                                                       _return_http_data_only,
                                                       collection_formats,
                                                       _preload_content,
                                                       _request_timeout,
                                                       _host))

    def request(self, method, url, query_params=None, headers=None,
                post_params=None, body=None, _preload_content=True,
                _request_timeout=None):
        """Makes the HTTP request using RESTClient."""
        if method == "GET":
            return self.rest_client.GET(url,
                                        query_params=query_params,
                                        _preload_content=_preload_content,
                                        _request_timeout=_request_timeout,
                                        headers=headers)
        elif method == "HEAD":
            return self.rest_client.HEAD(url,
                                         query_params=query_params,
                                         _preload_content=_preload_content,
                                         _request_timeout=_request_timeout,
                                         headers=headers)
        elif method == "OPTIONS":
            return self.rest_client.OPTIONS(url,
                                            query_params=query_params,
                                            headers=headers,
                                            _preload_content=_preload_content,
                                            _request_timeout=_request_timeout)
        elif method == "POST":
            return self.rest_client.POST(url,
                                         query_params=query_params,
                                         headers=headers,
                                         post_params=post_params,
                                         _preload_content=_preload_content,
                                         _request_timeout=_request_timeout,
                                         body=body)
        elif method == "PUT":
            return self.rest_client.PUT(url,
                                        query_params=query_params,
                                        headers=headers,
                                        post_params=post_params,
                                        _preload_content=_preload_content,
                                        _request_timeout=_request_timeout,
                                        body=body)
        elif method == "PATCH":
            return self.rest_client.PATCH(url,
                                          query_params=query_params,
                                          headers=headers,
                                          post_params=post_params,
                                          _preload_content=_preload_content,
                                          _request_timeout=_request_timeout,
                                          body=body)
        elif method == "DELETE":
            return self.rest_client.DELETE(url,
                                           query_params=query_params,
                                           headers=headers,
                                           _preload_content=_preload_content,
                                           _request_timeout=_request_timeout,
                                           body=body)
        else:
            raise ApiValueError(
                "http method must be `GET`, `HEAD`, `OPTIONS`,"
                " `POST`, `PATCH`, `PUT` or `DELETE`."
            )

    def parameters_to_tuples(self, params, collection_formats):
        """Get parameters as list of tuples, formatting collections.

        :param params: Parameters as dict or list of two-tuples
        :param dict collection_formats: Parameter collection formats
        :return: Parameters as list of tuples, collections formatted
        """
        new_params = []
        if collection_formats is None:
            collection_formats = {}
        for k, v in six.iteritems(params) if isinstance(params, dict) else params:  # noqa: E501
            if k in collection_formats:
                collection_format = collection_formats[k]
                if collection_format == 'multi':
                    new_params.extend((k, value) for value in v)
                else:
                    if collection_format == 'ssv':
                        delimiter = ' '
                    elif collection_format == 'tsv':
                        delimiter = '\t'
                    elif collection_format == 'pipes':
                        delimiter = '|'
                    else:  # csv is the default
                        delimiter = ','
                    new_params.append(
                        (k, delimiter.join(str(value) for value in v)))
            else:
                new_params.append((k, v))
        return new_params

    def files_parameters(self, files=None):
        """Builds form parameters.

        :param files: File parameters.
        :return: Form parameters with files.
        """
        params = []

        if files:
            for k, v in six.iteritems(files):
                if not v:
                    continue
                file_names = v if type(v) is list else [v]
                for n in file_names:
                    with open(n, 'rb') as f:
                        filename = os.path.basename(f.name)
                        filedata = f.read()
                        mimetype = (mimetypes.guess_type(filename)[0] or
                                    'application/octet-stream')
                        params.append(
                            tuple([k, tuple([filename, filedata, mimetype])]))

        return params

    def select_header_accept(self, accepts):
        """Returns `Accept` based on an array of accepts provided.

        :param accepts: List of headers.
        :return: Accept (e.g. application/json).
        """
        if not accepts:
            return

        accepts = [x.lower() for x in accepts]

        if 'application/json' in accepts:
            return 'application/json'
        else:
            return ', '.join(accepts)

    def select_header_content_type(self, content_types):
        """Returns `Content-Type` based on an array of content_types provided.

        :param content_types: List of content-types.
        :return: Content-Type (e.g. application/json).
        """
        if not content_types:
            return 'application/json'

        content_types = [x.lower() for x in content_types]

        if 'application/json' in content_types or '*/*' in content_types:
            return 'application/json'
        else:
            return content_types[0]

    def update_params_for_auth(self, headers, querys, auth_settings):
        """Updates header and query params based on authentication setting.

        :param headers: Header parameters dict to be updated.
        :param querys: Query parameters tuple list to be updated.
        :param auth_settings: Authentication setting identifiers list.
        """
        if not auth_settings:
            return

        for auth in auth_settings:
            auth_setting = self.configuration.auth_settings().get(auth)
            if auth_setting:
                if auth_setting['in'] == 'cookie':
                    headers['Cookie'] = auth_setting['value']
                elif auth_setting['in'] == 'header':
                    headers[auth_setting['key']] = auth_setting['value']
                elif auth_setting['in'] == 'query':
                    querys.append((auth_setting['key'], auth_setting['value']))
                else:
                    raise ApiValueError(
                        'Authentication token must be in `query` or `header`'
                    )

    def __deserialize_file(self, response):
        """Deserializes body to file

        Saves response body into a file in a temporary folder,
        using the filename from the `Content-Disposition` header if provided.

        :param response:  RESTResponse.
        :return: file path.
        """
        fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path)
        os.close(fd)
        os.remove(path)

        content_disposition = response.getheader("Content-Disposition")
        if content_disposition:
            filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?',
                                 content_disposition).group(1)
            path = os.path.join(os.path.dirname(path), filename)

        with open(path, "wb") as f:
            f.write(response.data)

        return path

    def __deserialize_primitive(self, data, klass):
        """Deserializes string to primitive type.

        :param data: str.
        :param klass: class literal.

        :return: int, long, float, str, bool.
        """
        try:
            return klass(data)
        except UnicodeEncodeError:
            return six.text_type(data)
        except TypeError:
            return data

    def __deserialize_object(self, value):
        """Return an original value.

        :return: object.
        """
        return value

    def __deserialize_date(self, string):
        """Deserializes string to date.

        :param string: str.
        :return: date.
        """
        try:
            return parse(string).date()
        except ImportError:
            return string
        except ValueError:
            raise rest.ApiException(
                status=0,
                reason="Failed to parse `{0}` as date object".format(string)
            )

    def __deserialize_datetime(self, string):
        """Deserializes string to datetime.

        The string should be in iso8601 datetime format.

        :param string: str.
        :return: datetime.
        """
        try:
            return parse(string)
        except ImportError:
            return string
        except ValueError:
            raise rest.ApiException(
                status=0,
                reason=(
                    "Failed to parse `{0}` as datetime object"
                    .format(string)
                )
            )

    def __deserialize_model(self, data, klass):
        """Deserializes list or dict to model.

        :param data: dict, list.
        :param klass: class literal.
        :return: model object.
        """
        has_discriminator = False
        if (hasattr(klass, 'get_real_child_model')
                and klass.discriminator_value_class_map):
            has_discriminator = True

        if not klass.openapi_types and has_discriminator is False:
            return data

        kwargs = {}
        if (data is not None and
                klass.openapi_types is not None and
                isinstance(data, (list, dict))):
            for attr, attr_type in six.iteritems(klass.openapi_types):
                if klass.attribute_map[attr] in data:
                    value = data[klass.attribute_map[attr]]
                    kwargs[attr] = self.__deserialize(value, attr_type)

        instance = klass(**kwargs)

        if has_discriminator:
            klass_name = instance.get_real_child_model(data)
            if klass_name:
                instance = self.__deserialize(data, klass_name)
        return instance

python/kserve/kserve/api_client.py
# Copyright 2023 The KServe Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# coding: utf-8
"""
    KServe

    Python SDK for KServe  # noqa: E501

    The version of the OpenAPI document: v0.1
    Generated by: https://openapi-generator.tech
"""

from __future__ import absolute_import

import atexit
import datetime
from dateutil.parser import parse
import json
import mimetypes
from multiprocessing.pool import ThreadPool
import os
import re
import tempfile

# python 2 and python 3 compatibility library
import six
from six.moves.urllib.parse import quote

from kserve.configuration import Configuration
import kserve.models
from kserve import rest
from kserve.exceptions import ApiValueError, ApiException


class ApiClient(object):
    """Generic API client for OpenAPI client library builds.

    OpenAPI generic API client. This client handles the client-
    server communication, and is invariant across implementations. Specifics of
    the methods and models for each application are generated from the OpenAPI
    templates.

    NOTE: This class is auto generated by OpenAPI Generator.
    Ref: https://openapi-generator.tech
    Do not edit the class manually.

    :param configuration: .Configuration object for this client
    :param header_name: a header to pass when making calls to the API.
    :param header_value: a header value to pass when making calls to
        the API.
    :param cookie: a cookie to include in the header when making calls
        to the API
    :param pool_threads: The number of threads to use for async requests
        to the API. More threads means more concurrent API requests.
    """

    PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types
    NATIVE_TYPES_MAPPING = {
        'int': int,
        'long': int if six.PY3 else long,  # noqa: F821
        'float': float,
        'str': str,
        'bool': bool,
        'date': datetime.date,
        'datetime': datetime.datetime,
        'object': object,
    }
    _pool = None

    def __init__(self, configuration=None, header_name=None, header_value=None,
                 cookie=None, pool_threads=1):
        if configuration is None:
            configuration = Configuration.get_default_copy()
        self.configuration = configuration
        self.pool_threads = pool_threads

        self.rest_client = rest.RESTClientObject(configuration)
        self.default_headers = {}
        if header_name is not None:
            self.default_headers[header_name] = header_value
        self.cookie = cookie
        # Set default User-Agent.
        self.user_agent = 'OpenAPI-Generator/0.1/python'
        self.client_side_validation = configuration.client_side_validation

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def close(self):
        if self._pool:
            self._pool.close()
            self._pool.join()
            self._pool = None
            if hasattr(atexit, 'unregister'):
                atexit.unregister(self.close)

    @property
    def pool(self):
        """Create thread pool on first request
         avoids instantiating unused threadpool for blocking clients.
        """
        if self._pool is None:
            atexit.register(self.close)
            self._pool = ThreadPool(self.pool_threads)
        return self._pool

    @property
    def user_agent(self):
        """User agent for this API client"""
        return self.default_headers['User-Agent']

    @user_agent.setter
    def user_agent(self, value):
        self.default_headers['User-Agent'] = value

    def set_default_header(self, header_name, header_value):
        self.default_headers[header_name] = header_value

    def __call_api(
            self, resource_path, method, path_params=None,
            query_params=None, header_params=None, body=None, post_params=None,
            files=None, response_type=None, auth_settings=None,
            _return_http_data_only=None, collection_formats=None,
            _preload_content=True, _request_timeout=None, _host=None):

        config = self.configuration

        # header parameters
        header_params = header_params or {}
        header_params.update(self.default_headers)
        if self.cookie:
            header_params['Cookie'] = self.cookie
        if header_params:
            header_params = self.sanitize_for_serialization(header_params)
            header_params = dict(self.parameters_to_tuples(header_params,
                                                           collection_formats))

        # path parameters
        if path_params:
            path_params = self.sanitize_for_serialization(path_params)
            path_params = self.parameters_to_tuples(path_params,
                                                    collection_formats)
            for k, v in path_params:
                # specified safe chars, encode everything
                resource_path = resource_path.replace(
                    '{%s}' % k,
                    quote(str(v), safe=config.safe_chars_for_path_param)
                )

        # query parameters
        if query_params:
            query_params = self.sanitize_for_serialization(query_params)
            query_params = self.parameters_to_tuples(query_params,
                                                     collection_formats)

        # post parameters
        if post_params or files:
            post_params = post_params if post_params else []
            post_params = self.sanitize_for_serialization(post_params)
            post_params = self.parameters_to_tuples(post_params,
                                                    collection_formats)
            post_params.extend(self.files_parameters(files))

        # auth setting
        self.update_params_for_auth(header_params, query_params, auth_settings)

        # body
        if body:
            body = self.sanitize_for_serialization(body)

        # request url
        if _host is None:
            url = self.configuration.host + resource_path
        else:
            # use server/host defined in path or operation instead
            url = _host + resource_path

        try:
            # perform request and return response
            response_data = self.request(
                method, url, query_params=query_params, headers=header_params,
                post_params=post_params, body=body,
                _preload_content=_preload_content,
                _request_timeout=_request_timeout)
        except ApiException as e:
            e.body = e.body.decode('utf-8') if six.PY3 else e.body
            raise e

        content_type = response_data.getheader('content-type')

        self.last_response = response_data

        return_data = response_data

        if not _preload_content:
            return return_data

        if six.PY3 and response_type not in ["file", "bytes"]:
            match = None
            if content_type is not None:
                match = re.search(r"charset=([a-zA-Z\-\d]+)[\s\;]?", content_type)
            encoding = match.group(1) if match else "utf-8"
            response_data.data = response_data.data.decode(encoding)

        # deserialize response data
        if response_type:
            return_data = self.deserialize(response_data, response_type)
        else:
            return_data = None

        if _return_http_data_only:
            return (return_data)
        else:
            return (return_data, response_data.status,
                    response_data.getheaders())

    def sanitize_for_serialization(self, obj):
        """Builds a JSON POST object.

        If obj is None, return None.
        If obj is str, int, long, float, bool, return directly.
        If obj is datetime.datetime, datetime.date
            convert to string in iso8601 format.
        If obj is list, sanitize each element in the list.
        If obj is dict, return the dict.
        If obj is OpenAPI model, return the properties dict.

        :param obj: The data to serialize.
        :return: The serialized form of data.
        """
        if obj is None:
            return None
        elif isinstance(obj, self.PRIMITIVE_TYPES):
            return obj
        elif isinstance(obj, list):
            return [self.sanitize_for_serialization(sub_obj)
                    for sub_obj in obj]
        elif isinstance(obj, tuple):
            return tuple(self.sanitize_for_serialization(sub_obj)
                         for sub_obj in obj)
        elif isinstance(obj, (datetime.datetime, datetime.date)):
            return obj.isoformat()

        if isinstance(obj, dict):
            obj_dict = obj
        else:
            # Convert model obj to dict except
            # attributes `openapi_types`, `attribute_map`
            # and attributes which value is not None.
            # Convert attribute name to json key in
            # model definition for request.
            obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
                        for attr, _ in six.iteritems(obj.openapi_types)
                        if getattr(obj, attr) is not None}

        return {key: self.sanitize_for_serialization(val)
                for key, val in six.iteritems(obj_dict)}

    def deserialize(self, response, response_type):
        """Deserializes response into an object.

        :param response: RESTResponse object to be deserialized.
        :param response_type: class literal for
            deserialized object, or string of class name.

        :return: deserialized object.
        """
        # handle file downloading
        # save response body into a tmp file and return the instance
        if response_type == "file":
            return self.__deserialize_file(response)

        # fetch data from response object
        try:
            data = json.loads(response.data)
        except ValueError:
            data = response.data

        return self.__deserialize(data, response_type)

    def __deserialize(self, data, klass):
        """Deserializes dict, list, str into an object.

        :param data: dict, list or str.
        :param klass: class literal, or string of class name.

        :return: object.
        """
        if data is None:
            return None

        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)
                        for sub_data in data]

            if klass.startswith('dict('):
                sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2)
                return {k: self.__deserialize(v, sub_kls)
                        for k, v in six.iteritems(data)}

            # convert str to class
            if klass in self.NATIVE_TYPES_MAPPING:
                klass = self.NATIVE_TYPES_MAPPING[klass]
            else:
                klass = getattr(kserve.models, klass)

        if klass in self.PRIMITIVE_TYPES:
            return self.__deserialize_primitive(data, klass)
        elif klass == object:
            return self.__deserialize_object(data)
        elif klass == datetime.date:
            return self.__deserialize_date(data)
        elif klass == datetime.datetime:
            return self.__deserialize_datetime(data)
        else:
            return self.__deserialize_model(data, klass)

    def call_api(self, resource_path, method,
                 path_params=None, query_params=None, header_params=None,
                 body=None, post_params=None, files=None,
                 response_type=None, auth_settings=None, async_req=None,
                 _return_http_data_only=None, collection_formats=None,
                 _preload_content=True, _request_timeout=None, _host=None):
        """Makes the HTTP request (synchronous) and returns deserialized data.

        To make an async_req request, set the async_req parameter.

        :param resource_path: Path to method endpoint.
        :param method: Method to call.
        :param path_params: Path parameters in the url.
        :param query_params: Query parameters in the url.
        :param header_params: Header parameters to be
            placed in the request header.
        :param body: Request body.
        :param post_params dict: Request post form parameters,
            for `application/x-www-form-urlencoded`, `multipart/form-data`.
        :param auth_settings list: Auth Settings names for the request.
        :param response: Response data type.
        :param files dict: key -> filename, value -> filepath,
            for `multipart/form-data`.
        :param async_req bool: execute request asynchronously
        :param _return_http_data_only: response data without head status code
                                       and headers
        :param collection_formats: dict of collection formats for path, query,
            header, and post parameters.
        :param _preload_content: if False, the urllib3.HTTPResponse object will
                                 be returned without reading/decoding response
                                 data. Default is True.
        :param _request_timeout: timeout setting for this request. If one
                                 number provided, it will be total request
                                 timeout. It can also be a pair (tuple) of
                                 (connection, read) timeouts.
        :return:
            If async_req parameter is True,
            the request will be called asynchronously.
            The method will return the request thread.
            If parameter async_req is False or missing,
            then the method will return the response directly.
        """
        if not async_req:
            return self.__call_api(resource_path, method,
                                   path_params, query_params, header_params,
                                   body, post_params, files,
                                   response_type, auth_settings,
                                   _return_http_data_only, collection_formats,
                                   _preload_content, _request_timeout, _host)

        return self.pool.apply_async(self.__call_api, (resource_path,
                                                       method, path_params,
                                                       query_params,
                                                       header_params, body,
                                                       post_params, files,
                                                       response_type,
                                                       auth_settings,
                                                       _return_http_data_only,
                                                       collection_formats,
                                                       _preload_content,
                                                       _request_timeout,
                                                       _host))

    def request(self, method, url, query_params=None, headers=None,
                post_params=None, body=None, _preload_content=True,
                _request_timeout=None):
        """Makes the HTTP request using RESTClient."""
        if method == "GET":
            return self.rest_client.GET(url,
                                        query_params=query_params,
                                        _preload_content=_preload_content,
                                        _request_timeout=_request_timeout,
                                        headers=headers)
        elif method == "HEAD":
            return self.rest_client.HEAD(url,
                                         query_params=query_params,
                                         _preload_content=_preload_content,
                                         _request_timeout=_request_timeout,
                                         headers=headers)
        elif method == "OPTIONS":
            return self.rest_client.OPTIONS(url,
                                            query_params=query_params,
                                            headers=headers,
                                            _preload_content=_preload_content,
                                            _request_timeout=_request_timeout)
        elif method == "POST":
            return self.rest_client.POST(url,
                                         query_params=query_params,
                                         headers=headers,
                                         post_params=post_params,
                                         _preload_content=_preload_content,
                                         _request_timeout=_request_timeout,
                                         body=body)
        elif method == "PUT":
            return self.rest_client.PUT(url,
                                        query_params=query_params,
                                        headers=headers,
                                        post_params=post_params,
                                        _preload_content=_preload_content,
                                        _request_timeout=_request_timeout,
                                        body=body)
        elif method == "PATCH":
            return self.rest_client.PATCH(url,
                                          query_params=query_params,
                                          headers=headers,
                                          post_params=post_params,
                                          _preload_content=_preload_content,
                                          _request_timeout=_request_timeout,
                                          body=body)
        elif method == "DELETE":
            return self.rest_client.DELETE(url,
                                           query_params=query_params,
                                           headers=headers,
                                           _preload_content=_preload_content,
                                           _request_timeout=_request_timeout,
                                           body=body)
        else:
            raise ApiValueError(
                "http method must be `GET`, `HEAD`, `OPTIONS`,"
                " `POST`, `PATCH`, `PUT` or `DELETE`."
            )

    def parameters_to_tuples(self, params, collection_formats):
        """Get parameters as list of tuples, formatting collections.

        :param params: Parameters as dict or list of two-tuples
        :param dict collection_formats: Parameter collection formats
        :return: Parameters as list of tuples, collections formatted
        """
        new_params = []
        if collection_formats is None:
            collection_formats = {}
        for k, v in six.iteritems(params) if isinstance(params, dict) else params:  # noqa: E501
            if k in collection_formats:
                collection_format = collection_formats[k]
                if collection_format == 'multi':
                    new_params.extend((k, value) for value in v)
                else:
                    if collection_format == 'ssv':
                        delimiter = ' '
                    elif collection_format == 'tsv':
                        delimiter = '\t'
                    elif collection_format == 'pipes':
                        delimiter = '|'
                    else:  # csv is the default
                        delimiter = ','
                    new_params.append(
                        (k, delimiter.join(str(value) for value in v)))
            else:
                new_params.append((k, v))
        return new_params

    def files_parameters(self, files=None):
        """Builds form parameters.

        :param files: File parameters.
        :return: Form parameters with files.
        """
        params = []

        if files:
            for k, v in six.iteritems(files):
                if not v:
                    continue
                file_names = v if type(v) is list else [v]
                for n in file_names:
                    with open(n, 'rb') as f:
                        filename = os.path.basename(f.name)
                        filedata = f.read()
                        mimetype = (mimetypes.guess_type(filename)[0] or
                                    'application/octet-stream')
                        params.append(
                            tuple([k, tuple([filename, filedata, mimetype])]))

        return params

    def select_header_accept(self, accepts):
        """Returns `Accept` based on an array of accepts provided.

        :param accepts: List of headers.
        :return: Accept (e.g. application/json).
        """
        if not accepts:
            return

        accepts = [x.lower() for x in accepts]

        if 'application/json' in accepts:
            return 'application/json'
        else:
            return ', '.join(accepts)

    def select_header_content_type(self, content_types):
        """Returns `Content-Type` based on an array of content_types provided.

        :param content_types: List of content-types.
        :return: Content-Type (e.g. application/json).
        """
        if not content_types:
            return 'application/json'

        content_types = [x.lower() for x in content_types]

        if 'application/json' in content_types or '*/*' in content_types:
            return 'application/json'
        else:
            return content_types[0]

    def update_params_for_auth(self, headers, querys, auth_settings):
        """Updates header and query params based on authentication setting.

        :param headers: Header parameters dict to be updated.
        :param querys: Query parameters tuple list to be updated.
        :param auth_settings: Authentication setting identifiers list.
        """
        if not auth_settings:
            return

        for auth in auth_settings:
            auth_setting = self.configuration.auth_settings().get(auth)
            if auth_setting:
                if auth_setting['in'] == 'cookie':
                    headers['Cookie'] = auth_setting['value']
                elif auth_setting['in'] == 'header':
                    headers[auth_setting['key']] = auth_setting['value']
                elif auth_setting['in'] == 'query':
                    querys.append((auth_setting['key'], auth_setting['value']))
                else:
                    raise ApiValueError(
                        'Authentication token must be in `query` or `header`'
                    )

    def __deserialize_file(self, response):
        """Deserializes body to file

        Saves response body into a file in a temporary folder,
        using the filename from the `Content-Disposition` header if provided.

        :param response:  RESTResponse.
        :return: file path.
        """
        fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path)
        os.close(fd)
        os.remove(path)

        content_disposition = response.getheader("Content-Disposition")
        if content_disposition:
            filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?',
                                 content_disposition).group(1)
            path = os.path.join(os.path.dirname(path), filename)

        with open(path, "wb") as f:
            f.write(response.data)

        return path

    def __deserialize_primitive(self, data, klass):
        """Deserializes string to primitive type.

        :param data: str.
        :param klass: class literal.

        :return: int, long, float, str, bool.
        """
        try:
            return klass(data)
        except UnicodeEncodeError:
            return six.text_type(data)
        except TypeError:
            return data

    def __deserialize_object(self, value):
        """Return an original value.

        :return: object.
        """
        return value

    def __deserialize_date(self, string):
        """Deserializes string to date.

        :param string: str.
        :return: date.
        """
        try:
            return parse(string).date()
        except ImportError:
            return string
        except ValueError:
            raise rest.ApiException(
                status=0,
                reason="Failed to parse `{0}` as date object".format(string)
            )

    def __deserialize_datetime(self, string):
        """Deserializes string to datetime.

        The string should be in iso8601 datetime format.

        :param string: str.
        :return: datetime.
        """
        try:
            return parse(string)
        except ImportError:
            return string
        except ValueError:
            raise rest.ApiException(
                status=0,
                reason=(
                    "Failed to parse `{0}` as datetime object"
                    .format(string)
                )
            )

    def __deserialize_model(self, data, klass):
        """Deserializes list or dict to model.

        :param data: dict, list.
        :param klass: class literal.
        :return: model object.
        """
        has_discriminator = False
        if (hasattr(klass, 'get_real_child_model')
                and klass.discriminator_value_class_map):
            has_discriminator = True

        if not klass.openapi_types and has_discriminator is False:
            return data

        kwargs = {}
        if (data is not None and
                klass.openapi_types is not None and
                isinstance(data, (list, dict))):
            for attr, attr_type in six.iteritems(klass.openapi_types):
                if klass.attribute_map[attr] in data:
                    value = data[klass.attribute_map[attr]]
                    kwargs[attr] = self.__deserialize(value, attr_type)

        instance = klass(**kwargs)

        if has_discriminator:
            klass_name = instance.get_real_child_model(data)
            if klass_name:
                instance = self.__deserialize(data, klass_name)
        return instance

python/kserve/kserve/api_client.py
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)

option to load credentials directly from a secret for s3
Currently to download from a private S3 bucket [you create both a secret and a service account that you link it to](https://github.com/kubeflow/kfserving/blob/master/docs/samples/s3/s3_secret.yaml). You then set the [serviceAccountName on the KFService ](https://github.com/kubeflow/kfserving/blob/master/docs/samples/s3/tensorflow_s3.yaml#L7) and it is [used to add env vars based on the secret to the initContainer that downloads the model](https://github.com/kubeflow/kfserving/blob/master/pkg/controller/kfservice/resources/credentials/service_account_credentials.go#L94).

It might be easier for s3 users to just create a secret containing entries intended as environment variables and link that directly by having a 'envSecretRefName' entry in the CRD. This could be used in the implementation to use 'envFrom' to apply the secret's values.

It seems the[ original idea for credentials was to use a Secret ](https://github.com/kubeflow/kfserving/issues/36)and this morphed into a ServiceAccount as a 'first pass'. Presumably there's no in principle objection to also supporting a direct secret for s3?
