mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-25 17:12:50 +00:00
feat: add artifact command.
Signed-off-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
parent
f48624e097
commit
fe588fad3a
|
|
@ -0,0 +1,12 @@
|
|||
- hosts:
|
||||
- localhost
|
||||
roles:
|
||||
- init/init-artifact
|
||||
tasks:
|
||||
- name: Package image
|
||||
image:
|
||||
pull: "{{ image_manifests }}"
|
||||
when: image_manifests|length > 0
|
||||
- name: Export artifact
|
||||
command: |
|
||||
cd {{ work_dir }} && tar -czvf kubekey-artifact.tar.gz kubekey/
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
- hosts:
|
||||
- localhost
|
||||
tags: ["always"]
|
||||
roles:
|
||||
- init/init-artifact
|
||||
- install/image-registry
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
- localhost
|
||||
roles:
|
||||
- role: precheck/artifact_check
|
||||
when: artifact.artifact_file | defined
|
||||
when: artifact_file | defined
|
||||
|
||||
- hosts:
|
||||
- k8s_cluster
|
||||
|
|
|
|||
|
|
@ -88,11 +88,6 @@ artifact:
|
|||
# {% if (kkzone == 'cn') %}https://kubernetes-release.pek3b.qingstor.com/osixia/keepalived/releases/download/{{ keepalived_version }}/keepalived-{{ keepalived_version }}-linux-amd64.tgz{% else %}https://github.com/osixia/keepalived/releases/download/{{ keepalived_version }}/keepalived-{{ keepalived_version }}-linux-amd64.tgz{% endif %}
|
||||
# arm64: |
|
||||
# {% if (kkzone == 'cn') %}https://kubernetes-release.pek3b.qingstor.com/osixia/keepalived/releases/download/{{ keepalived_version }}/keepalived-{{ keepalived_version }}-linux-arm64.tgz{% else %}https://github.com/osixia/keepalived/releases/download/{{ keepalived_version }}/keepalived-{{ keepalived_version }}-linux-arm64.tgz{% endif %}
|
||||
oras:
|
||||
amd64: |
|
||||
https://github.com/oras-project/oras/releases/download/{{ oras_version }}/oras_{{ oras_version|slice:'1:' }}_linux_amd64.tar.gz
|
||||
arm64: |
|
||||
https://github.com/oras-project/oras/releases/download/{{ oras_version }}/oras_{{ oras_version|slice:'1:' }}_linux_arm64.tar.gz
|
||||
cilium: https://helm.cilium.io/cilium-{{ cilium_version }}.tgz
|
||||
kubeovn: https://kubeovn.github.io/kube-ovn/kube-ovn-{{ kubeovn_version }}.tgz
|
||||
hybridnet: https://github.com/alibaba/hybridnet/releases/download/helm-chart-{{ hybridnet_version }}/hybridnet-{{ hybridnet_version }}.tgz
|
||||
|
|
|
|||
|
|
@ -264,21 +264,3 @@
|
|||
loop: "{{ artifact.arch }}"
|
||||
when:
|
||||
- keepalived_version | defined && keepalived_version != ""
|
||||
|
||||
- name: Check binaries for oras
|
||||
command: |
|
||||
artifact_name={{ artifact.artifact_url.oras[item]|split:"/"|last }}
|
||||
artifact_path={{ work_dir }}/kubekey/oras/{{ oras_version }}/{{ item }}
|
||||
if [ ! -f $artifact_path/$artifact_name ]; then
|
||||
mkdir -p $artifact_path
|
||||
# download online
|
||||
http_code=$(curl -Lo /dev/null -s -w "%{http_code}" {{ artifact.artifact_url.oras[item] }})
|
||||
if [ $http_code != 200 ]; then
|
||||
echo "http code is $http_code"
|
||||
exit 1
|
||||
fi
|
||||
curl -L -o $artifact_path/$artifact_name {{ artifact.artifact_url.oras[item] }}
|
||||
fi
|
||||
loop: "{{ artifact.arch }}"
|
||||
when:
|
||||
- oras_version | defined && oras_version != ""
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
---
|
||||
- name: Create work_dir
|
||||
tags: ["always"]
|
||||
command: |
|
||||
if [ ! -d "{{ work_dir }}" ]; then
|
||||
mkdir -p {{ work_dir }}
|
||||
fi
|
||||
|
||||
- name: Extract artifact to work_dir
|
||||
tags: ["always"]
|
||||
command: |
|
||||
if [ ! -f "{{ artifact.artifact_file }}" ]; then
|
||||
tar -zxvf {{ artifact.artifact_file }} -C {{ work_dir }}
|
||||
if [ ! -f "{{ artifact_file }}" ]; then
|
||||
tar -zxvf {{ artifact_file }} -C {{ work_dir }}
|
||||
fi
|
||||
when: artifact.artifact_file | defined
|
||||
when: artifact_file | defined
|
||||
|
||||
- name: Download binaries
|
||||
block:
|
||||
|
|
@ -24,5 +26,6 @@
|
|||
- include_tasks: pki.yaml
|
||||
|
||||
- name: Chown work_dir to sudo
|
||||
tags: ["always"]
|
||||
command: |
|
||||
chown -R ${SUDO_UID}:${SUDO_GID} {{ work_dir }}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
image_registry:
|
||||
# ha_vip: 192.168.122.59
|
||||
namespace_override: ""
|
||||
auth:
|
||||
registry: "{% if (image_registry.ha_vip|defined) %}{{ image_registry.ha_vip }}{% else %}{{ groups['image_registry']|first }}{% endif %}"
|
||||
username: admin
|
||||
password: Harbor12345
|
||||
# registry type. support: harbor, registry
|
||||
type: harbor
|
||||
# Virtual IP address for repository High Availability. the Virtual IP address should be available.
|
||||
# ha_vip: 192.168.122.59
|
||||
harbor:
|
||||
admin_password: Harbor12345
|
||||
registry:
|
||||
version: 2
|
||||
config:
|
||||
|
|
|
|||
|
|
@ -1,27 +1,6 @@
|
|||
---
|
||||
- name: Check if image to load
|
||||
ignore_errors: true
|
||||
command: |
|
||||
ls {{ work_dir }}/kubekey/images/
|
||||
register: local_images_dir
|
||||
|
||||
- name: Sync oras to remote
|
||||
copy:
|
||||
src: "{{ work_dir }}/kubekey/oras/{{ oras_version }}/{{ binary_type.stdout }}/oras_{{ oras_version|slice:'1:' }}_linux_{{ binary_type.stdout }}.tar.gz"
|
||||
dest: "/tmp/kubekey/oras_{{ oras_version|slice:'1:' }}_linux_{{ binary_type.stdout }}.tar.gz"
|
||||
when: local_images_dir.stderr == ""
|
||||
|
||||
- name: Unpackage oras binary
|
||||
command: tar -zxvf /tmp/kubekey/oras_{{ oras_version|slice:'1:' }}_linux_{{ binary_type.stdout }}.tar.gz -C /usr/local/bin oras
|
||||
when: local_images_dir.stderr == ""
|
||||
|
||||
- name: Sync images package to remote
|
||||
copy:
|
||||
src: "{{ work_dir }}/kubekey/images/"
|
||||
dest: "/tmp/kubekey/images/"
|
||||
when: local_images_dir.stderr == ""
|
||||
|
||||
- name: Sync images to registry
|
||||
- name: Create harbor project for each image
|
||||
tags: ["only_image"]
|
||||
command: |
|
||||
for dir in /tmp/kubekey/images/*; do
|
||||
if [ ! -d "$dir" ]; then
|
||||
|
|
@ -40,12 +19,34 @@
|
|||
fi
|
||||
|
||||
# if project is not exist, create if
|
||||
http_code=$(curl -Iks -u "admin:{{ image_registry.harbor.admin_password }}" 'https://localhost/api/v2.0/projects?project_name=${project}' | grep HTTP | awk '{print $2}')
|
||||
http_code=$(curl -Iks -u "{{ image_registry.auth.username }}:{{ image_registry.auth.password }}" 'https://localhost/api/v2.0/projects?project_name=${project}' | grep HTTP | awk '{print $2}')
|
||||
if [ $http_code == 404 ]; then
|
||||
# create project
|
||||
curl -u "admin:{{ image_registry.harbor.admin_password }}" -k -X POST -H "Content-Type: application/json" "https://localhost/api/v2.0/projects" -d "{ \"project_name\": \"${project}\", \"public\": true}"
|
||||
fi
|
||||
|
||||
oras cp --to-username admin --to-password {{ image_registry.harbor.admin_password }} ${dir##*/} localhost/${project}/${dest_image}:${tag}
|
||||
curl -u "{{ image_registry.auth.username }}:{{ image_registry.auth.password }}" -k -X POST -H "Content-Type: application/json" "https://localhost/api/v2.0/projects" -d "{ \"project_name\": \"${project}\", \"public\": true}"
|
||||
fi
|
||||
done
|
||||
when: local_images_dir.stderr == ""
|
||||
when:
|
||||
- image_registry.type == 'harbor'
|
||||
- image_registry.namespace_override == ""
|
||||
-
|
||||
- name: Create harbor project for namespace_override
|
||||
tags: ["only_image"]
|
||||
command: |
|
||||
# if project is not exist, create if
|
||||
http_code=$(curl -Iks -u "{{ image_registry.auth.username }}:{{ image_registry.auth.password }}" 'https://localhost/api/v2.0/projects?project_name={{ image_registry.namespace_override }}' | grep HTTP | awk '{print $2}')
|
||||
if [ $http_code == 404 ]; then
|
||||
# create project
|
||||
curl -u "{{ image_registry.auth.username }}:{{ image_registry.auth.password }}" -k -X POST -H "Content-Type: application/json" "https://localhost/api/v2.0/projects" -d "{ \"project_name\": \"{{ image_registry.namespace_override }}\", \"public\": true}"
|
||||
fi
|
||||
when:
|
||||
- image_registry.type == 'harbor'
|
||||
- image_registry.namespace_override != ""
|
||||
|
||||
- name: Sync images package to harbor
|
||||
tags: ["only_image"]
|
||||
image:
|
||||
push:
|
||||
registry: "{{ image_registry.auth.registry }}"
|
||||
namespace_override: "{{ image_registry.namespace_override }}"
|
||||
username: "{{ image_registry.auth.username }}"
|
||||
password: "{{ image_registry.auth.password }}"
|
||||
|
|
|
|||
|
|
@ -14,3 +14,4 @@
|
|||
when: image_registry.type == 'harbor'
|
||||
|
||||
- include_tasks: load_images.yaml
|
||||
tags: ["only_image"]
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ https:
|
|||
# The initial password of Harbor admin
|
||||
# It only works in first time to install harbor
|
||||
# Remember Change the admin password from UI after launching Harbor.
|
||||
harbor_admin_password: {{ image_registry.harbor.admin_password }}
|
||||
harbor_admin_password: {{ image_registry.auth.password }}
|
||||
|
||||
# Harbor DB configuration
|
||||
database:
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
---
|
||||
- name: Check artifact is exits
|
||||
command:
|
||||
if [ ! -f "{{ artifact.artifact_file }}" ]; then
|
||||
if [ ! -f "{{ artifact_file }}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check artifact file type
|
||||
command:
|
||||
if [[ "{{ artifact.artifact_file }}" != *{{ item }} ]]; then
|
||||
if [[ "{{ artifact_file }}" != *{{ item }} ]]; then
|
||||
exit 1
|
||||
fi
|
||||
loop: ['.tgz','.tar.gz']
|
||||
|
||||
- name: Check md5 of artifact
|
||||
command:
|
||||
if [[ $(md5sum {{ artifact.artifact_file }}) != {{ artifact.artifact_md5 }} ]]; then
|
||||
if [[ $(md5sum {{ artifact_file }}) != {{ artifact.artifact_md5 }} ]]; then
|
||||
exit 1
|
||||
fi
|
||||
when:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
Copyright 2024 The KubeSphere 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.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||
|
||||
"github.com/kubesphere/kubekey/v4/cmd/kk/app/options"
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
)
|
||||
|
||||
func newArtifactCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "artifact",
|
||||
Short: "Manage a KubeKey offline installation package",
|
||||
}
|
||||
|
||||
cmd.AddCommand(newArtifactExportCommand())
|
||||
cmd.AddCommand(newArtifactImagesCommand())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newArtifactExportCommand() *cobra.Command {
|
||||
o := options.NewArtifactExportOptions()
|
||||
cmd := &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Export a KubeKey offline installation package",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
pipeline, config, inventory, err := o.Complete(cmd, []string{"playbooks/artifact_export.yaml"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// set workdir
|
||||
_const.SetWorkDir(o.WorkDir)
|
||||
// create workdir directory,if not exists
|
||||
if _, err := os.Stat(o.WorkDir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(o.WorkDir, fs.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return run(signals.SetupSignalHandler(), pipeline, config, inventory)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
for _, f := range o.Flags().FlagSets {
|
||||
flags.AddFlagSet(f)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newArtifactImagesCommand() *cobra.Command {
|
||||
o := options.NewArtifactImagesOptions()
|
||||
cmd := &cobra.Command{
|
||||
Use: "images",
|
||||
Short: "push images to a registry from an artifact",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
pipeline, config, inventory, err := o.Complete(cmd, []string{"playbooks/artifact_images.yaml"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// set workdir
|
||||
_const.SetWorkDir(o.WorkDir)
|
||||
// create workdir directory,if not exists
|
||||
if _, err := os.Stat(o.WorkDir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(o.WorkDir, fs.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return run(signals.SetupSignalHandler(), pipeline, config, inventory)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
for _, f := range o.Flags().FlagSets {
|
||||
flags.AddFlagSet(f)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerInternalCommand(newArtifactCommand())
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
Copyright 2024 The KubeSphere 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.
|
||||
*/
|
||||
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
|
||||
kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1"
|
||||
)
|
||||
|
||||
// ======================================================================================
|
||||
// artifact export
|
||||
// ======================================================================================
|
||||
|
||||
type ArtifactExportOptions struct {
|
||||
CommonOptions
|
||||
}
|
||||
|
||||
func (o *ArtifactExportOptions) Flags() cliflag.NamedFlagSets {
|
||||
fss := o.CommonOptions.Flags()
|
||||
return fss
|
||||
}
|
||||
|
||||
func (o ArtifactExportOptions) Complete(cmd *cobra.Command, args []string) (*kubekeyv1.Pipeline, *kubekeyv1.Config, *kubekeyv1.Inventory, error) {
|
||||
pipeline := &kubekeyv1.Pipeline{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "artifact-export-",
|
||||
Namespace: o.Namespace,
|
||||
Annotations: map[string]string{
|
||||
kubekeyv1.BuiltinsProjectAnnotation: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// complete playbook. now only support one playbook
|
||||
if len(args) == 1 {
|
||||
o.Playbook = args[0]
|
||||
} else {
|
||||
return nil, nil, nil, fmt.Errorf("%s\nSee '%s -h' for help and examples", cmd.Use, cmd.CommandPath())
|
||||
}
|
||||
|
||||
pipeline.Spec = kubekeyv1.PipelineSpec{
|
||||
Playbook: o.Playbook,
|
||||
Debug: o.Debug,
|
||||
}
|
||||
config, inventory, err := o.completeRef(pipeline)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return pipeline, config, inventory, nil
|
||||
}
|
||||
|
||||
func NewArtifactExportOptions() *ArtifactExportOptions {
|
||||
// set default value
|
||||
return &ArtifactExportOptions{CommonOptions: newCommonOptions()}
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
// artifact image
|
||||
// ======================================================================================
|
||||
|
||||
type ArtifactImagesOptions struct {
|
||||
CommonOptions
|
||||
}
|
||||
|
||||
func (o *ArtifactImagesOptions) Flags() cliflag.NamedFlagSets {
|
||||
fss := o.CommonOptions.Flags()
|
||||
return fss
|
||||
}
|
||||
|
||||
func (o ArtifactImagesOptions) Complete(cmd *cobra.Command, args []string) (*kubekeyv1.Pipeline, *kubekeyv1.Config, *kubekeyv1.Inventory, error) {
|
||||
pipeline := &kubekeyv1.Pipeline{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "artifact-images-",
|
||||
Namespace: o.Namespace,
|
||||
Annotations: map[string]string{
|
||||
kubekeyv1.BuiltinsProjectAnnotation: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// complete playbook. now only support one playbook
|
||||
if len(args) == 1 {
|
||||
o.Playbook = args[0]
|
||||
} else {
|
||||
return nil, nil, nil, fmt.Errorf("%s\nSee '%s -h' for help and examples", cmd.Use, cmd.CommandPath())
|
||||
}
|
||||
|
||||
pipeline.Spec = kubekeyv1.PipelineSpec{
|
||||
Playbook: o.Playbook,
|
||||
Debug: o.Debug,
|
||||
Tags: []string{"only_image"},
|
||||
}
|
||||
config, inventory, err := o.completeRef(pipeline)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return pipeline, config, inventory, nil
|
||||
}
|
||||
|
||||
func NewArtifactImagesOptions() *ArtifactImagesOptions {
|
||||
// set default value
|
||||
return &ArtifactImagesOptions{CommonOptions: newCommonOptions()}
|
||||
}
|
||||
|
|
@ -37,8 +37,6 @@ type CreateClusterOptions struct {
|
|||
Kubernetes string
|
||||
// ContainerRuntime for kubernetes. Such as docker, containerd etc.
|
||||
ContainerManager string
|
||||
// Artifact container all binaries which used to install kubernetes.
|
||||
Artifact string
|
||||
}
|
||||
|
||||
func (o *CreateClusterOptions) Flags() cliflag.NamedFlagSets {
|
||||
|
|
@ -46,7 +44,6 @@ func (o *CreateClusterOptions) Flags() cliflag.NamedFlagSets {
|
|||
kfs := fss.FlagSet("config")
|
||||
kfs.StringVar(&o.Kubernetes, "with-kubernetes", "", "Specify a supported version of kubernetes")
|
||||
kfs.StringVar(&o.ContainerManager, "container-manager", "", "Container runtime: docker, crio, containerd and isula.")
|
||||
kfs.StringVarP(&o.Artifact, "artifact", "a", "", "Path to a KubeKey artifact")
|
||||
return fss
|
||||
}
|
||||
|
||||
|
|
@ -88,12 +85,6 @@ func (o *CreateClusterOptions) Complete(cmd *cobra.Command, args []string) (*kub
|
|||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
if o.Artifact != "" {
|
||||
// override artifact_file in config
|
||||
if err := config.SetValue("artifact_file", o.Artifact); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pipeline, config, inventory, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,14 +32,10 @@ import (
|
|||
|
||||
type InitOSOptions struct {
|
||||
CommonOptions
|
||||
// Artifact container all binaries which used to install kubernetes.
|
||||
Artifact string
|
||||
}
|
||||
|
||||
func (o *InitOSOptions) Flags() cliflag.NamedFlagSets {
|
||||
fss := o.CommonOptions.Flags()
|
||||
kfs := fss.FlagSet("config")
|
||||
kfs.StringVarP(&o.Artifact, "artifact", "a", "", "Path to a KubeKey artifact")
|
||||
return fss
|
||||
}
|
||||
|
||||
|
|
@ -69,12 +65,6 @@ func (o InitOSOptions) Complete(cmd *cobra.Command, args []string) (*kubekeyv1.P
|
|||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if o.Artifact != "" {
|
||||
// override artifact_file in config
|
||||
if err := config.SetValue("artifact_file", o.Artifact); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pipeline, config, inventory, nil
|
||||
}
|
||||
|
|
@ -85,19 +75,15 @@ func NewInitOSOptions() *InitOSOptions {
|
|||
}
|
||||
|
||||
// ======================================================================================
|
||||
// init registry
|
||||
// init registry
|
||||
// ======================================================================================
|
||||
|
||||
type InitRegistryOptions struct {
|
||||
CommonOptions
|
||||
// Artifact container all binaries which used to install kubernetes.
|
||||
Artifact string
|
||||
}
|
||||
|
||||
func (o *InitRegistryOptions) Flags() cliflag.NamedFlagSets {
|
||||
fss := o.CommonOptions.Flags()
|
||||
kfs := fss.FlagSet("config")
|
||||
kfs.StringVarP(&o.Artifact, "artifact", "a", "", "Path to a KubeKey artifact")
|
||||
return fss
|
||||
}
|
||||
|
||||
|
|
@ -127,12 +113,6 @@ func (o InitRegistryOptions) Complete(cmd *cobra.Command, args []string) (*kubek
|
|||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if o.Artifact != "" {
|
||||
// override artifact_file in config
|
||||
if err := config.SetValue("artifact_file", o.Artifact); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return pipeline, config, inventory, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ type CommonOptions struct {
|
|||
Set []string
|
||||
// WorkDir is the baseDir which command find any resource (project etc.)
|
||||
WorkDir string
|
||||
// Artifact is the path of offline package for kubekey.
|
||||
Artifact string
|
||||
// Debug mode, after a successful execution of Pipeline, will retain runtime data, which includes task execution status and parameters.
|
||||
Debug bool
|
||||
// Namespace for all resources.
|
||||
|
|
@ -80,6 +82,7 @@ func (o *CommonOptions) Flags() cliflag.NamedFlagSets {
|
|||
fss := cliflag.NamedFlagSets{}
|
||||
gfs := fss.FlagSet("generic")
|
||||
gfs.StringVar(&o.WorkDir, "work-dir", o.WorkDir, "the base Dir for kubekey. Default current dir. ")
|
||||
gfs.StringVarP(&o.Artifact, "artifact", "a", "", "Path to a KubeKey artifact")
|
||||
gfs.StringVarP(&o.ConfigFile, "config", "c", o.ConfigFile, "the config file path. support *.yaml ")
|
||||
gfs.StringArrayVar(&o.Set, "set", o.Set, "set value in config. format --set key=val")
|
||||
gfs.StringVarP(&o.InventoryFile, "inventory", "i", o.InventoryFile, "the host list file path. support *.ini")
|
||||
|
|
@ -108,6 +111,13 @@ func (o *CommonOptions) completeRef(pipeline *kubekeyv1.Pipeline) (*kubekeyv1.Co
|
|||
} else if err := config.SetValue("work_dir", o.WorkDir); err != nil {
|
||||
return nil, nil, fmt.Errorf("work_dir to config error: %w", err)
|
||||
}
|
||||
if o.Artifact != "" {
|
||||
// override artifact_file in config
|
||||
if err := config.SetValue("artifact_file", o.Artifact); err != nil {
|
||||
return nil, nil, fmt.Errorf("artifact file to config error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range o.Set {
|
||||
ss := strings.Split(s, "=")
|
||||
if len(ss) != 2 {
|
||||
|
|
|
|||
5
go.mod
5
go.mod
|
|
@ -22,6 +22,7 @@ require (
|
|||
k8s.io/component-base v0.29.1
|
||||
k8s.io/klog/v2 v2.120.1
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e
|
||||
oras.land/oras-go/v2 v2.5.0
|
||||
sigs.k8s.io/controller-runtime v0.17.0
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
|
|
@ -75,6 +76,8 @@ require (
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.18.0 // indirect
|
||||
|
|
@ -104,7 +107,7 @@ require (
|
|||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/oauth2 v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
|
|
|
|||
10
go.sum
10
go.sum
|
|
@ -167,6 +167,10 @@ github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY
|
|||
github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
|
@ -302,8 +306,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -407,6 +411,8 @@ k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15 h1:m6dl1pkxz3HuE2mP9MUYPC
|
|||
k8s.io/kube-openapi v0.0.0-20240117194847-208609032b15/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ=
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=
|
||||
oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 h1:/U5vjBbQn3RChhv7P11uhYvCSm5G2GaIi5AIGBS6r4c=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4=
|
||||
sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s=
|
||||
|
|
|
|||
|
|
@ -49,12 +49,10 @@ func TestParseBool(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "container string",
|
||||
condition: []string{"instr[0].test"},
|
||||
condition: []string{"test in instr"},
|
||||
variable: pongo2.Context{
|
||||
"instr": []pongo2.Context{
|
||||
{"test": true},
|
||||
{"test": false},
|
||||
},
|
||||
"test": "a1",
|
||||
"instr": "vda hjilsa1 sdte",
|
||||
},
|
||||
excepted: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ func appendSANsToAltNames(altNames *certutil.AltNames, SANs []string, certName s
|
|||
} else if len(validation.IsWildcardDNS1123Subdomain(altname)) == 0 {
|
||||
altNames.DNSNames = append(altNames.DNSNames, altname)
|
||||
} else {
|
||||
klog.Warningf(
|
||||
klog.V(4).Infof(
|
||||
"[certificates] WARNING: '%s' was not added to the '%s' SAN, because it is not a valid IP or RFC-1123 compliant DNS entry\n",
|
||||
altname,
|
||||
certName,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
Copyright 2024 The KubeSphere 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.
|
||||
*/
|
||||
|
||||
package modules
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
Copyright 2024 The KubeSphere 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.
|
||||
*/
|
||||
|
||||
package modules
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
imagev1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"k8s.io/klog/v2"
|
||||
"oras.land/oras-go/v2"
|
||||
"oras.land/oras-go/v2/registry"
|
||||
"oras.land/oras-go/v2/registry/remote"
|
||||
"oras.land/oras-go/v2/registry/remote/auth"
|
||||
"oras.land/oras-go/v2/registry/remote/retry"
|
||||
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
||||
)
|
||||
|
||||
func ModuleImage(ctx context.Context, options ExecOptions) (stdout string, stderr string) {
|
||||
// get host variable
|
||||
ha, err := options.Variable.Get(variable.GetAllVariable(options.Host))
|
||||
if err != nil {
|
||||
klog.V(4).ErrorS(err, "failed to get host variable", "hostname", options.Host)
|
||||
return "", err.Error()
|
||||
}
|
||||
|
||||
// check args
|
||||
args := variable.Extension2Variables(options.Args)
|
||||
pullParam, _ := variable.StringSliceVar(ha.(map[string]any), args, "pull")
|
||||
// if namespace_override is not empty, it will override the image manifests namespace_override. (namespace maybe multi sub path)
|
||||
// push to private registry
|
||||
pushParam := args["push"]
|
||||
|
||||
// pull image manifests to local dir
|
||||
for _, img := range pullParam {
|
||||
src, err := remote.NewRepository(img)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("failed to get remote image: %v", err)
|
||||
}
|
||||
dst, err := NewLocalRepository(filepath.Join(domain, src.Reference.Repository) + ":" + src.Reference.Reference)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("failed to get local image: %v", err)
|
||||
}
|
||||
if _, err = oras.Copy(context.Background(), src, src.Reference.Reference, dst, "", oras.DefaultCopyOptions); err != nil {
|
||||
return "", fmt.Sprintf("failed to copy image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// push image to private registry
|
||||
if pushParam != nil {
|
||||
registry, _ := variable.StringVar(ha.(map[string]any), pushParam.(map[string]any), "registry")
|
||||
username, _ := variable.StringVar(ha.(map[string]any), pushParam.(map[string]any), "username")
|
||||
password, _ := variable.StringVar(ha.(map[string]any), pushParam.(map[string]any), "password")
|
||||
namespace, _ := variable.StringVar(ha.(map[string]any), pushParam.(map[string]any), "namespace_override")
|
||||
|
||||
manifests, err := findLocalImageManifests(filepath.Join(_const.GetWorkDir(), "kubekey", "images"))
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("failed to find local image manifests: %v", err)
|
||||
}
|
||||
for _, img := range manifests {
|
||||
src, err := NewLocalRepository(filepath.Join(domain, img))
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("failed to get local image: %v", err)
|
||||
}
|
||||
repo := src.Reference.Repository
|
||||
if namespace != "" {
|
||||
repo = filepath.Join(namespace, filepath.Base(repo))
|
||||
}
|
||||
dst, err := remote.NewRepository(filepath.Join(registry, repo) + ":" + src.Reference.Reference)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("failed to get local image: %v", err)
|
||||
}
|
||||
dst.Client = &auth.Client{
|
||||
Client: retry.DefaultClient,
|
||||
Cache: auth.NewCache(),
|
||||
Credential: auth.StaticCredential(registry, auth.Credential{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}),
|
||||
}
|
||||
|
||||
if _, err = oras.Copy(context.Background(), src, src.Reference.Reference, dst, "", oras.DefaultCopyOptions); err != nil {
|
||||
return "", fmt.Sprintf("failed to copy image: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func findLocalImageManifests(localDir string) ([]string, error) {
|
||||
var manifests []string
|
||||
if err := filepath.WalkDir(localDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if path == filepath.Join(localDir, "blobs") {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if d.IsDir() || filepath.Base(path) == "manifests" {
|
||||
return nil
|
||||
}
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.Unmarshal(file, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
if data["mediaType"].(string) == imagev1.MediaTypeImageIndex {
|
||||
subpath, err := filepath.Rel(localDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// the last dir is manifests. should delete it
|
||||
manifests = append(manifests, filepath.Dir(filepath.Dir(subpath))+":"+filepath.Base(subpath))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return manifests, nil
|
||||
}
|
||||
|
||||
func NewLocalRepository(reference string) (*remote.Repository, error) {
|
||||
ref, err := registry.ParseReference(reference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &remote.Repository{
|
||||
Reference: ref,
|
||||
Client: &http.Client{Transport: &imageTransport{baseDir: filepath.Join(_const.GetWorkDir(), "kubekey", "images")}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var ResponseNotFound = &http.Response{Proto: "Local", StatusCode: http.StatusNotFound}
|
||||
var ResponseNotAllowed = &http.Response{Proto: "Local", StatusCode: http.StatusMethodNotAllowed}
|
||||
var ResponseServerError = &http.Response{Proto: "Local", StatusCode: http.StatusInternalServerError}
|
||||
var ResponseCreated = &http.Response{Proto: "Local", StatusCode: http.StatusCreated}
|
||||
var ResponseOK = &http.Response{Proto: "Local", StatusCode: http.StatusOK}
|
||||
|
||||
const domain = "internal"
|
||||
const apiPrefix = "/v2/"
|
||||
|
||||
type imageTransport struct {
|
||||
baseDir string
|
||||
}
|
||||
|
||||
func (i imageTransport) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
switch request.Method {
|
||||
case http.MethodHead: // check if file exist
|
||||
if strings.HasSuffix(filepath.Dir(request.URL.Path), "blobs") { // blobs
|
||||
filename := filepath.Join(i.baseDir, "blobs", filepath.Base(request.URL.Path))
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
return ResponseNotFound, nil
|
||||
}
|
||||
return ResponseOK, nil
|
||||
} else if strings.HasSuffix(filepath.Dir(request.URL.Path), "manifests") { // manifests
|
||||
filename := filepath.Join(i.baseDir, strings.TrimPrefix(request.URL.Path, apiPrefix))
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
return ResponseNotFound, nil
|
||||
}
|
||||
file, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return ResponseServerError, err
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.Unmarshal(file, &data); err != nil {
|
||||
return ResponseServerError, err
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
Proto: "Local",
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{
|
||||
"Content-Type": []string{data["mediaType"].(string)},
|
||||
},
|
||||
ContentLength: int64(len(file)),
|
||||
}, nil
|
||||
}
|
||||
return ResponseNotAllowed, nil
|
||||
case http.MethodPost:
|
||||
if strings.HasSuffix(request.URL.Path, "/uploads/") {
|
||||
return &http.Response{
|
||||
Proto: "Local",
|
||||
StatusCode: http.StatusAccepted,
|
||||
Header: http.Header{
|
||||
"Location": []string{filepath.Dir(request.URL.Path)},
|
||||
},
|
||||
Request: request,
|
||||
}, nil
|
||||
}
|
||||
return ResponseNotAllowed, nil
|
||||
case http.MethodPut:
|
||||
if strings.HasSuffix(request.URL.Path, "/uploads") { // blobs
|
||||
body, err := io.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
return ResponseServerError, nil
|
||||
}
|
||||
defer request.Body.Close()
|
||||
|
||||
filename := filepath.Join(i.baseDir, "blobs", request.URL.Query().Get("digest"))
|
||||
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
|
||||
return ResponseServerError, nil
|
||||
}
|
||||
if err := os.WriteFile(filename, body, 0644); err != nil {
|
||||
return ResponseServerError, nil
|
||||
}
|
||||
return ResponseCreated, nil
|
||||
} else if strings.HasSuffix(filepath.Dir(request.URL.Path), "/manifests") { // manifest
|
||||
filename := filepath.Join(i.baseDir, strings.TrimPrefix(request.URL.Path, apiPrefix))
|
||||
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
|
||||
return ResponseServerError, nil
|
||||
}
|
||||
body, err := io.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
return ResponseServerError, nil
|
||||
}
|
||||
defer request.Body.Close()
|
||||
if err := os.WriteFile(filename, body, 0644); err != nil {
|
||||
return ResponseServerError, nil
|
||||
}
|
||||
return ResponseCreated, nil
|
||||
}
|
||||
|
||||
return ResponseNotAllowed, nil
|
||||
case http.MethodGet:
|
||||
if strings.HasSuffix(filepath.Dir(request.URL.Path), "blobs") { // blobs
|
||||
filename := filepath.Join(i.baseDir, "blobs", filepath.Base(request.URL.Path))
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
return ResponseNotFound, nil
|
||||
}
|
||||
file, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return ResponseServerError, err
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
Proto: "Local",
|
||||
StatusCode: http.StatusOK,
|
||||
ContentLength: int64(len(file)),
|
||||
Body: io.NopCloser(bytes.NewReader(file)),
|
||||
}, nil
|
||||
} else if strings.HasSuffix(filepath.Dir(request.URL.Path), "manifests") { // manifests
|
||||
filename := filepath.Join(i.baseDir, strings.TrimPrefix(request.URL.Path, apiPrefix))
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
return ResponseNotFound, nil
|
||||
}
|
||||
file, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return ResponseServerError, err
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.Unmarshal(file, &data); err != nil {
|
||||
return ResponseServerError, err
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
Proto: "Local",
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{
|
||||
"Content-Type": []string{data["mediaType"].(string)},
|
||||
},
|
||||
ContentLength: int64(len(file)),
|
||||
Body: io.NopCloser(bytes.NewReader(file)),
|
||||
}, nil
|
||||
}
|
||||
return ResponseNotAllowed, nil
|
||||
default:
|
||||
return ResponseNotAllowed, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +68,7 @@ func init() {
|
|||
RegisterModule("template", ModuleTemplate)
|
||||
RegisterModule("set_fact", ModuleSetFact)
|
||||
RegisterModule("gen_cert", ModuleGenCert)
|
||||
RegisterModule("image", ModuleImage)
|
||||
}
|
||||
|
||||
// ConnKey for connector which store in context
|
||||
|
|
|
|||
|
|
@ -151,9 +151,27 @@ func StringSliceVar(d map[string]any, vars map[string]any, key string) ([]string
|
|||
return nil, err
|
||||
}
|
||||
var ss []string
|
||||
if err := json.Unmarshal([]byte(as), &ss); err != nil {
|
||||
// if is not json format. only return a value contains this
|
||||
return []string{as}, nil
|
||||
switch {
|
||||
case regexp.MustCompile(`^<\[\](.*?) Value>$`).MatchString(as):
|
||||
// in pongo2 cannot get slice value. add extension filter value.
|
||||
var input = val.(string)
|
||||
// try to escape string
|
||||
if ns, err := strconv.Unquote(val.(string)); err == nil {
|
||||
input = ns
|
||||
}
|
||||
vv := GetValue(d, input)
|
||||
if _, ok := vv.([]any); ok {
|
||||
ss = make([]string, len(vv.([]any)))
|
||||
for i, a := range vv.([]any) {
|
||||
ss[i] = a.(string)
|
||||
}
|
||||
}
|
||||
default:
|
||||
// value is simple string
|
||||
if err := json.Unmarshal([]byte(as), &ss); err != nil {
|
||||
// if is not json format. only return a value contains this
|
||||
return []string{as}, nil
|
||||
}
|
||||
}
|
||||
return ss, nil
|
||||
default:
|
||||
|
|
|
|||
Loading…
Reference in New Issue