stx: login to Dockerhub within k8s

Docker hub sometimes refuses to serve requests due to rate limits on
anonymous pulls. We already support an option to login to Docker hub
before pulling or building environment containers. However, this has no
effect for images pulled by k8s itself, via a helm chart reference, eg
for the nginx container.

This patch adds a similar option: "stx start --use-dockerhub-cred":
* creates a k8s secret with Docker hub credentials extracted from the
  calling user's $HOME/.docker/config.json
* inserts this secret into the helm chart when starting

This option is now used when "stx-init-env" is called with the
"--dockerhub-login" option.

TESTS
=======================
* Use "stx control start --use-dockerhub-cred" and inspect the installed
  helm release to make sure the secret has been created and passed on to
  the chart
* Using a slightly modified chart that refers to a private image, not
  accessible to anonymous users:
  - make sure k8s image pull fails without the new option
  - make sure k8s image pull succeeds with the new option
* Delete the profile and re-create the environment with and without the
  login option
* Make sure the secret gets deleted by "stx control stop"
* Use "stx-init-env" with and without "--dockerhub-login" and make sure
  the helm chart is installed correctly

Change-Id: I4c6962ad82e659005b4b7ea840c1ca709c05c60e
Signed-off-by: Davlet Panech <davlet.panech@windriver.com>
This commit is contained in:
Davlet Panech 2025-04-01 16:54:31 -04:00
parent 09144c05d6
commit bce548b9bc
10 changed files with 112 additions and 9 deletions

View File

@ -83,6 +83,7 @@ START_PODS=1
RESET_SOFT=0
RESET_HARD=0
ASSUME_YES=0
STX_START_OPTS=
COREUTILS_DOCKER_IMAGE="debian:bookworm-20240130-slim"
@ -238,7 +239,7 @@ stx_stop() {
stx_start() {
stx config --upgrade || exit 1
stx control start --wait || exit 1
stx control start --wait $STX_START_OPTS || exit 1
}
#
@ -710,6 +711,10 @@ elif [[ $RESTART_MINIKUBE -eq 1 ]] ; then
warn "--restart-minikube is only supported on minikube platform -- ignoring"
fi
if [[ "$DOCKERHUB_LOGIN" -eq 1 ]] ; then
STX_START_OPTS+=" --use-dockerhub-cred"
fi
# Workaround: Wait for Minikube network to stabilize before building images
sleep 10

View File

@ -13,7 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import json
import logging
import os
import re
from stx import utils # pylint: disable=E0611
import subprocess
import tempfile
@ -115,6 +118,87 @@ class KubeHelper(object):
return podname
def __get_docker_cred_secret_name(self):
project_name = re.sub(r'[^a-z0-9-]', r'-',
self.config.get('project', 'name').lower())
return project_name + '-dockerconfigjson'
def try_create_docker_cred_secret(self):
'''Create a k8s secret with Docker Hub credentials.
Check the file $HOME/.docker/config.json for Docker Hub
credentials. If if found, create the secret and return
its name. Otherwise, do nothing and return None.
'''
cred_name = self.__get_docker_cred_secret_name()
# Create a temporary docker config file that contains only the
# docker hub credentials, by extracting it from the calling user's
# docker config
# Find docker config location
docker_config = '%s/config.json' % \
os.getenv('DOCKER_CONFIG',
os.path.expanduser('~/.docker'))
try:
with open(docker_config) as f:
docker_config_data = json.load(f)
except FileNotFoundError:
return None
# Look for dockerhub credentials
dockerhub_auth = docker_config_data.get('auths', {})\
.get('https://index.docker.io/v1/', {})\
.get('auth')
if not dockerhub_auth:
return None
# Create a temporary file that contains only docker hub credentials
with tempfile.NamedTemporaryFile(mode='w+t',
encoding='utf8',
prefix='stx_docker_config_',
suffix='.json') as f:
new_docker_config_data = {
'auths': {
'https://index.docker.io/v1/': {
'auth': dockerhub_auth
}
}
}
json.dump(new_docker_config_data, f)
f.flush()
# (re-)create the secret
self.__delete_docker_cred_secret(cred_name)
create_cmd = self.config.kubectl() + f' create secret generic {cred_name}' + \
f' --from-file=.dockerconfigjson="{f.name}"' + \
' --type=kubernetes.io/dockerconfigjson'
logger.info('Running: %s', create_cmd)
subprocess.run(create_cmd, shell=True, check=True)
return cred_name
def delete_docker_cred_secret(self):
'''Delete the docker secret from k8s
Do nothing if it doesn't exist.
'''
self.__delete_docker_cred_secret(self.__get_docker_cred_secret_name())
def __delete_docker_cred_secret(self, cred_name):
delete_cmd = self.config.kubectl() + \
f' delete secret {cred_name} --ignore-not-found'
try:
logger.info('Running: %s', delete_cmd)
subprocess.run(delete_cmd, shell=True,
stderr=subprocess.PIPE, check=True,
encoding='utf8', errors='utf8')
except subprocess.CalledProcessError as x:
logger.error('Failed while attempting to delete k8s ' +
'credentials "%s": %s', cred_name, x.stderr)
raise x
def helm_release_exists(self, projectname):
'''Check if the helm release exists'''

View File

@ -251,7 +251,7 @@ stx-pkgbuilder/configmap/')
return repomgr_type
def handleStartTask(self, projectname, wait):
def handleStartTask(self, projectname, wait, use_dockerhub_cred):
if self.config.use_minikube:
self.minikube_ctl.start()
@ -271,6 +271,11 @@ stx-pkgbuilder/configmap/')
if self.config.container_mtu:
cmd += f' --set stx-docker.mtu={self.config.container_mtu}'
if use_dockerhub_cred:
image_pull_secret = self.k8s.try_create_docker_cred_secret()
if image_pull_secret:
cmd += f' --set global.imagePullSecrets[0].name={image_pull_secret}'
self.logger.debug('Execute the helm start command: %s', cmd)
helm_status = self.k8s.helm_release_exists(self.projectname)
if helm_status:
@ -317,6 +322,8 @@ stx-pkgbuilder/configmap/')
self.logger.info("waiting for %d pod(s) to exit", pod_count)
time.sleep(2)
self.k8s.delete_docker_cred_secret()
def handleIsStartedTask(self, projectname):
if self.k8s.helm_release_exists(projectname):
self.logger.info('Helm release %s is installed' % projectname)
@ -420,7 +427,8 @@ no lat container is available!')
projectname = 'stx'
if args.ctl_task == 'start':
self.handleStartTask(projectname, args.wait)
self.handleStartTask(projectname, args.wait,
args.use_dockerhub_cred)
elif args.ctl_task == 'stop':
self.handleStopTask(projectname, args.wait)

View File

@ -84,6 +84,12 @@ task.\t\teg: [start|enter|stop|is-started|status|upgrade|keys-add]')
help='wait for operation to finish, ' +
'for start, stop\n\n',
action='store_true')
control_subparser.add_argument('--use-dockerhub-cred',
help='Use dockerhub credentials from ' +
'$HOME/.docker/config for pulling ' +
'environment pods\' images within ' +
'k8s (for "start" command only)\n\n',
action='store_true')
control_subparser.set_defaults(handle=self.handlecontrol.handleControl)
config_subparser = subparsers.add_parser('config',

View File

@ -21,7 +21,7 @@ spec:
labels:
{{- include "stx-repomgr.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
{{- with (.Values.imagePullSecrets | default .Values.global.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -21,7 +21,7 @@ spec:
labels:
{{- include "stx-builder-files-http.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
{{- with (.Values.imagePullSecrets | default .Values.global.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -21,7 +21,7 @@ spec:
labels:
{{- include "stx-docker.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
{{- with (.Values.imagePullSecrets | default .Values.global.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -21,7 +21,7 @@ spec:
labels:
{{- include "stx-lat-tool.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
{{- with (.Values.imagePullSecrets | default .Values.global.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -21,7 +21,7 @@ spec:
labels:
{{- include "stx-pkgbuilder.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
{{- with (.Values.imagePullSecrets | default .Values.global.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -21,7 +21,7 @@ spec:
labels:
{{- include "stx-repomgr.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
{{- with (.Values.imagePullSecrets | default .Values.global.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}