mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-26 01:22:51 +00:00
feat: more type to defined playbook file (#2522)
Signed-off-by: joyceliu <joyceliu@yunify.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
parent
954579beb5
commit
2c19021fb2
|
|
@ -42,13 +42,13 @@ func (c *Config) UnmarshalJSON(data []byte) error {
|
|||
*c = Config(*aux)
|
||||
|
||||
// Decode spec.Raw into spec.Object if it's not already set
|
||||
objMap := make(map[string]any)
|
||||
if len(c.Spec.Raw) > 0 && c.Spec.Object == nil {
|
||||
var objMap map[string]interface{}
|
||||
if err := json.Unmarshal(c.Spec.Raw, &objMap); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal spec.Raw")
|
||||
}
|
||||
c.Spec.Object = &unstructured.Unstructured{Object: objMap}
|
||||
}
|
||||
c.Spec.Object = &unstructured.Unstructured{Object: objMap}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -71,5 +71,9 @@ func (c *Config) MarshalJSON() ([]byte, error) {
|
|||
// Value returns the underlying map[string]any from the Config's unstructured Object.
|
||||
// This provides direct access to the config values stored in Spec.Object.
|
||||
func (c *Config) Value() map[string]any {
|
||||
if c.Spec.Object == nil {
|
||||
return make(map[string]any)
|
||||
}
|
||||
|
||||
return c.Spec.Object.(*unstructured.Unstructured).Object
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
// TaskAnnotationRole is the absolute dir of task in project.
|
||||
TaskAnnotationRole = "kubesphere.io/role"
|
||||
// TaskAnnotationRelativePath is the relative dir of task in project.
|
||||
TaskAnnotationRelativePath = "kubesphere.io/rel-path"
|
||||
)
|
||||
|
||||
// TaskSpec of Task
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
register: cloud_config_out
|
||||
- name: set_fact of cloud-config value
|
||||
set_fact:
|
||||
cloud_config: "{{ .cloud_config_out.stdout | toJson }}"
|
||||
cloud_config: "{{ .cloud_config_out.stdout | fromYaml | toJson }}"
|
||||
roles:
|
||||
- role: init/init-artifacts
|
||||
when: .kubernetes_installed | default false | eq false
|
||||
|
|
|
|||
|
|
@ -35,4 +35,5 @@ k8s_registry: |
|
|||
{{- end -}}
|
||||
|
||||
cri:
|
||||
# support: containerd,docker
|
||||
container_manager: docker
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
---
|
||||
- name: Delete Node
|
||||
command: |
|
||||
if kubectl get node {{ .hostname }}; then
|
||||
if kubectl get node {{ .hostname }} > /dev/null 2>&1; then
|
||||
kubectl cordon {{ .hostname }}
|
||||
kubectl drain {{ .hostname }} --ignore-daemonsets --delete-emptydir-data --force
|
||||
if [ $(kubectl get nodes --no-headers | wc -l) -gt 1 ]; then
|
||||
kubectl drain {{ .hostname }} --ignore-daemonsets --delete-emptydir-data --force --disable-eviction
|
||||
else
|
||||
kubectl drain {{ .hostname }} --ignore-daemonsets --delete-emptydir-data --force
|
||||
fi
|
||||
{{- if .cni.type | eq "calico" }}
|
||||
calicoctl delete node {{ .hostname }}
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
---
|
||||
# load defaults vars
|
||||
- hosts:
|
||||
- all
|
||||
vars_files:
|
||||
- vars/certs_renew.yaml
|
||||
|
||||
- import_playbook: hook/pre_install.yaml
|
||||
|
||||
- hosts:
|
||||
- localhost
|
||||
tags: ["certs"]
|
||||
vars_files:
|
||||
- vars/certs_renew.yaml
|
||||
roles:
|
||||
- init/init-cert
|
||||
|
||||
- hosts:
|
||||
- etcd
|
||||
tags: ["certs"]
|
||||
vars_files:
|
||||
- vars/certs_renew.yaml
|
||||
roles:
|
||||
- role: certs/renew-etcd
|
||||
when: and (.groups.etcd | default list | len | lt 0) .renew_etcd
|
||||
|
|
@ -21,16 +23,12 @@
|
|||
- hosts:
|
||||
- image_registry
|
||||
tags: ["certs"]
|
||||
vars_files:
|
||||
- vars/certs_renew.yaml
|
||||
roles:
|
||||
- role: certs/renew-registry
|
||||
when: and (.groups.image_registry | default list | len | lt 0) .renew_image_registry
|
||||
|
||||
- hosts:
|
||||
- kube_control_plane
|
||||
vars_files:
|
||||
- vars/certs_renew.yaml
|
||||
tags: ["certs"]
|
||||
roles:
|
||||
- role: certs/renew-kubernetes
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
---
|
||||
# load defaults vars
|
||||
- hosts:
|
||||
- all
|
||||
vars_files:
|
||||
- vars/create_cluster.yaml
|
||||
|
||||
- import_playbook: hook/pre_install.yaml
|
||||
|
||||
# precheck
|
||||
|
|
@ -67,8 +73,6 @@
|
|||
|
||||
- hosts:
|
||||
- k8s_cluster
|
||||
vars_files:
|
||||
- vars/create_cluster_kubernetes.yaml
|
||||
gather_facts: true
|
||||
roles:
|
||||
- install/cri
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ kubernetes:
|
|||
etcd:
|
||||
deployment_type: external
|
||||
cri:
|
||||
# support: containerd,docker
|
||||
container_manager: docker
|
||||
image_registry:
|
||||
type: harbor
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
cluster_require:
|
||||
supported_architectures:
|
||||
amd64:
|
||||
- amd64
|
||||
- x86_64
|
||||
arm64:
|
||||
- arm64
|
||||
- aarch64
|
||||
|
|
@ -30,3 +30,13 @@ k8s_registry: |
|
|||
{{- end -}}
|
||||
|
||||
security_enhancement: false
|
||||
|
||||
kubernetes:
|
||||
etcd:
|
||||
# It is possible to deploy etcd with three methods.
|
||||
# external: Deploy etcd cluster with external etcd cluster.
|
||||
# internal: Deploy etcd cluster by static pod.
|
||||
deployment_type: external
|
||||
cri:
|
||||
# support: containerd,docker
|
||||
container_manager: docker
|
||||
|
|
@ -1,222 +0,0 @@
|
|||
artifact:
|
||||
arch: [ "amd64" ]
|
||||
# offline artifact package for kk.
|
||||
artifact_file: ""
|
||||
# the md5_file of artifact_file.
|
||||
artifact_md5: ""
|
||||
artifact_url:
|
||||
etcd:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/etcd/release/download/{{ .etcd_version }}/etcd-{{ .etcd_version }}-linux-amd64.tar.gz
|
||||
{{- else }}
|
||||
https://github.com/etcd-io/etcd/releases/download/{{ .etcd_version }}/etcd-{{ .etcd_version }}-linux-amd64.tar.gz
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/etcd/release/download/{{ .etcd_version }}/etcd-{{ .etcd_version }}-linux-arm64.tar.gz
|
||||
{{- else }}
|
||||
https://github.com/etcd-io/etcd/releases/download/{{ .etcd_version }}/etcd-{{ .etcd_version }}-linux-arm64.tar.gz
|
||||
{{- end }}
|
||||
kubeadm:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/release/{{ .kube_version }}/bin/linux/amd64/kubeadm
|
||||
{{- else }}
|
||||
https://storage.googleapis.com/kubernetes-release/release/{{ .kube_version }}/bin/linux/amd64/kubeadm
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/release/{{ .kube_version }}/bin/linux/arm64/kubeadm
|
||||
{{- else }}
|
||||
https://storage.googleapis.com/kubernetes-release/release/{{ .kube_version }}/bin/linux/arm64/kubeadm
|
||||
{{- end }}
|
||||
kubelet:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/release/{{ .kube_version }}/bin/linux/amd64/kubelet
|
||||
{{- else }}
|
||||
https://storage.googleapis.com/kubernetes-release/release/{{ .kube_version }}/bin/linux/amd64/kubelet
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/release/{{ .kube_version }}/bin/linux/arm64/kubelet
|
||||
{{- else }}
|
||||
https://storage.googleapis.com/kubernetes-release/release/{{ .kube_version }}/bin/linux/arm64/kubelet
|
||||
{{- end }}
|
||||
kubectl:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/release/{{ .kube_version }}/bin/linux/amd64/kubectl
|
||||
{{- else }}
|
||||
https://storage.googleapis.com/kubernetes-release/release/{{ .kube_version }}/bin/linux/amd64/kubectl
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/release/{{ .kube_version }}/bin/linux/arm64/kubectl
|
||||
{{- else }}
|
||||
https://storage.googleapis.com/kubernetes-release/release/{{ .kube_version }}/bin/linux/arm64/kubectl
|
||||
{{- end }}
|
||||
cni_plugins:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://github.com/containernetworking/plugins/releases/download/{{ .cni_plugins_version }}/cni-plugins-linux-amd64-{{ .cni_plugins_version }}.tgz
|
||||
{{- else }}
|
||||
https://containernetworking.pek3b.qingstor.com/plugins/releases/download/{{ .cni_plugins_version }}/cni-plugins-linux-amd64-{{ .cni_plugins_version }}.tgz
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://github.com/containernetworking/plugins/releases/download/{{ .cni_plugins_version }}/cni-plugins-linux-arm64-{{ .cni_plugins_version }}.tgz
|
||||
{{- else }}
|
||||
https://containernetworking.pek3b.qingstor.com/plugins/releases/download/{{ .cni_plugins_version }}/cni-plugins-linux-arm64-{{ .cni_plugins_version }}.tgz
|
||||
{{- end }}
|
||||
helm:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-helm.pek3b.qingstor.com/helm-{{ .helm_version }}-linux-amd64.tar.gz
|
||||
{{- else }}
|
||||
https://get.helm.sh/helm-{{ .helm_version }}-linux-amd64.tar.gz
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-helm.pek3b.qingstor.com/helm-{{ .helm_version }}-linux-arm64.tar.gz
|
||||
{{- else }}
|
||||
https://get.helm.sh/helm-{{ .helm_version }}-linux-arm64.tar.gz
|
||||
{{- end }}
|
||||
crictl:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/cri-tools/releases/download/{{ .crictl_version }}/crictl-{{ .crictl_version }}-linux-amd64.tar.gz
|
||||
{{- else }}
|
||||
https://github.com/kubernetes-sigs/cri-tools/releases/download/{{ .crictl_version }}/crictl-{{ .crictl_version }}-linux-amd64.tar.gz
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/cri-tools/releases/download/{{ .crictl_version }}/crictl-{{ .crictl_version }}-linux-arm64.tar.gz
|
||||
{{- else }}
|
||||
https://github.com/kubernetes-sigs/cri-tools/releases/download/{{ .crictl_version }}/crictl-{{ .crictl_version }}-linux-arm64.tar.gz
|
||||
{{- end }}
|
||||
docker:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://mirrors.aliyun.com/docker-ce/linux/static/stable/x86_64/docker-{{ .docker_version }}.tgz
|
||||
{{- else }}
|
||||
https://download.docker.com/linux/static/stable/x86_64/docker-{{ .docker_version }}.tgz
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://mirrors.aliyun.com/docker-ce/linux/static/stable/aarch64/docker-{{ .docker_version }}.tgz
|
||||
{{- else }}
|
||||
https://download.docker.com/linux/static/stable/aarch64/docker-{{ .docker_version }}.tgz
|
||||
{{- end }}
|
||||
cridockerd:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/releases/download/{{ .cridockerd_version }}/cri-dockerd-{{ .cridockerd_version | default "" | trimPrefix "v" }}.amd64.tgz
|
||||
{{- else }}
|
||||
https://github.com/Mirantis/cri-dockerd/releases/download/{{ .cridockerd_version }}/cri-dockerd-{{ .cridockerd_version | default "" | trimPrefix "v" }}.amd64.tgz
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/releases/download/{{ .cridockerd_version }}/cri-dockerd-{{ .cridockerd_version | default "" | trimPrefix "v" }}.arm64.tgz
|
||||
{{- else }}
|
||||
https://github.com/Mirantis/cri-dockerd/releases/download/{{ .cridockerd_version }}/cri-dockerd-{{ .cridockerd_version | default "" | trimPrefix "v" }}.arm64.tgz
|
||||
{{- end }}
|
||||
containerd:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/containerd/containerd/releases/download/{{ .containerd_version }}/containerd-{{ .containerd_version | default "" | trimPrefix "v" }}-linux-amd64.tar.gz
|
||||
{{- else }}
|
||||
https://github.com/containerd/containerd/releases/download/{{ .containerd_version }}/containerd-{{ .containerd_version | default "" | trimPrefix "v" }}-linux-amd64.tar.gz
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/containerd/containerd/releases/download/{{ .containerd_version }}/containerd-{{ .containerd_version | default "" | trimPrefix "v" }}-linux-arm64.tar.gz
|
||||
{{- else }}
|
||||
https://github.com/containerd/containerd/releases/download/{{ .containerd_version }}/containerd-{{ .containerd_version | default "" | trimPrefix "v" }}-linux-arm64.tar.gz
|
||||
{{- end }}
|
||||
runc:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/opencontainers/runc/releases/download/{{ .runc_version }}/runc.amd64
|
||||
{{- else }}
|
||||
https://github.com/opencontainers/runc/releases/download/{{ .runc_version }}/runc.amd64
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/opencontainers/runc/releases/download/{{ .runc_version }}/runc.arm64
|
||||
{{- else }}
|
||||
https://github.com/opencontainers/runc/releases/download/{{ .runc_version }}/runc.arm64
|
||||
{{- end }}
|
||||
calicoctl:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/projectcalico/calico/releases/download/{{ .calico_version }}/calicoctl-linux-amd64
|
||||
{{- else }}
|
||||
https://github.com/projectcalico/calico/releases/download/{{ .calico_version }}/calicoctl-linux-amd64
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/projectcalico/calico/releases/download/{{ .calico_version }}/calicoctl-linux-arm64
|
||||
{{- else }}
|
||||
https://github.com/projectcalico/calico/releases/download/{{ .calico_version }}/calicoctl-linux-arm64
|
||||
{{- end }}
|
||||
dockercompose:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/docker/compose/releases/download/{{ .dockercompose_version }}/docker-compose-linux-x86_64
|
||||
{{- else }}
|
||||
https://github.com/docker/compose/releases/download/{{ .dockercompose_version }}/docker-compose-linux-x86_64
|
||||
{{- end }}
|
||||
arm64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://kubernetes-release.pek3b.qingstor.com/docker/compose/releases/download/{{ .dockercompose_version }}/docker-compose-linux-aarch64
|
||||
{{- else }}
|
||||
https://github.com/docker/compose/releases/download/{{ .dockercompose_version }}/docker-compose-linux-aarch64
|
||||
{{- end }}
|
||||
# registry:
|
||||
# amd64: |
|
||||
# {{- if .kkzone | eq "cn" }}
|
||||
# https://kubernetes-release.pek3b.qingstor.com/registry/{{ .registry_version }}/registry-{{ .registry_version }}-linux-amd64.tgz
|
||||
# {{- else }}
|
||||
# https://github.com/kubesphere/kubekey/releases/download/{{ .registry_version }}/registry-{{ .registry_version }}-linux-amd64.tgz
|
||||
# {{- end }}
|
||||
# arm64: |
|
||||
# {{- if .kkzone | eq "cn" }}
|
||||
# https://kubernetes-release.pek3b.qingstor.com/registry/{{ .registry_version }}/registry-{{ .registry_version }}-linux-arm64.tgz
|
||||
# {{- else }}
|
||||
# https://github.com/kubesphere/kubekey/releases/download/{{ .registry_version }}/registry-{{ .registry_version }}-linux-arm64.tgz
|
||||
# {{- end }}
|
||||
harbor:
|
||||
amd64: |
|
||||
{{- if .kkzone | eq "cn" }}
|
||||
https://github.com/goharbor/harbor/releases/download/{{ .harbor_version }}/harbor-offline-installer-{{ .harbor_version }}.tgz
|
||||
{{- else }}
|
||||
https://github.com/goharbor/harbor/releases/download/{{ .harbor_version }}/harbor-offline-installer-{{ .harbor_version }}.tgz
|
||||
{{- end }}
|
||||
# arm64: |
|
||||
# {{- if .kkzone | eq "cn" }}
|
||||
# https://github.com/goharbor/harbor/releases/download/{{ .harbor_version }}/harbor-{{ .harbor_version }}-linux-arm64.tgz
|
||||
# {{- else }}
|
||||
# https://github.com/goharbor/harbor/releases/download/{{ .harbor_version }}/harbor-{{ .harbor_version }}-linux-arm64.tgz
|
||||
# {{- end }}
|
||||
# keepalived:
|
||||
# amd64: |
|
||||
# {{- if .kkzone | eq "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
|
||||
# {{- end }}
|
||||
# arm64: |
|
||||
# {{- if .kkzone | eq "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
|
||||
# {{- end }}
|
||||
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
|
||||
nfs_provisioner: https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/download/nfs-subdir-external-provisioner-4.0.18/nfs-subdir-external-provisioner-{{ .nfs_provisioner_version }}.tgz
|
||||
images:
|
||||
auth: []
|
||||
list: []
|
||||
|
|
@ -3,8 +3,8 @@ cri:
|
|||
cgroup_driver: systemd
|
||||
sandbox_image: |
|
||||
{{ .k8s_registry }}/pause:3.5
|
||||
# support: containerd,docker,crio
|
||||
container_manager: docker
|
||||
# support: containerd,docker
|
||||
# container_manager: docker
|
||||
# the endpoint of containerd
|
||||
cri_socket: |
|
||||
{{- if .cri.container_manager | eq "containerd" }}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ kubernetes:
|
|||
# It is possible to deploy etcd with three methods.
|
||||
# external: Deploy etcd cluster with external etcd cluster.
|
||||
# internal: Deploy etcd cluster by static pod.
|
||||
deployment_type: external
|
||||
# deployment_type: external
|
||||
image: |
|
||||
{{ .k8s_registry }}/etcd:3.5.0
|
||||
custom_label: {}
|
||||
|
|
|
|||
|
|
@ -28,4 +28,4 @@ cluster_require:
|
|||
arm64:
|
||||
- arm64
|
||||
- aarch64
|
||||
min_kernel_version: 4.9.17
|
||||
min_kernel_version: 4.9.17
|
||||
|
|
@ -7,9 +7,9 @@
|
|||
run_once: true
|
||||
when: and .kubernetes.etcd.deployment_type (ne .kubernetes.etcd.deployment_type "")
|
||||
|
||||
- name: Stop if etcd group is empty in internal etcd mode
|
||||
- name: Stop if etcd group is empty in external etcd mode
|
||||
assert:
|
||||
that: .groups.etcd
|
||||
that: .groups.etcd | len | lt 0
|
||||
fail_msg: "group \"etcd\" cannot be empty in external etcd mode"
|
||||
run_once: true
|
||||
when: .kubernetes.etcd.deployment_type | eq "external"
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
assert:
|
||||
that: (mod (.groups.etcd | len) 2) | eq 1
|
||||
fail_msg: "etcd number should be odd number"
|
||||
when: .groups.etcd
|
||||
when: .kubernetes.etcd.deployment_type | eq "external"
|
||||
|
||||
## https://cwiki.yunify.com/pages/viewpage.action?pageId=145920824
|
||||
- name: Check dev io for etcd
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import (
|
|||
|
||||
const (
|
||||
defaultKubeVersion = "v1.23.15"
|
||||
defaultContainerManager = "containerd"
|
||||
defaultContainerManager = "docker"
|
||||
)
|
||||
|
||||
// NewCreateClusterOptions for newCreateClusterCommand
|
||||
|
|
|
|||
|
|
@ -79,13 +79,11 @@ const ProjectRolesDir = "roles"
|
|||
const ProjectRolesTasksDir = "tasks"
|
||||
|
||||
// ProjectRolesTasksMainFile is a mandatory file under the tasks directory that must be executed when the role is run. It supports files with .yaml or .yml extensions.
|
||||
const ProjectRolesTasksMainFile = "main"
|
||||
|
||||
// ProjectRolesDefaultsDir is a fixed directory name under a role, used to set default variables for the role.
|
||||
const ProjectRolesDefaultsDir = "defaults"
|
||||
|
||||
// ProjectRolesDefaultsMainFile is a mandatory file under the defaults directory. It supports files with .yaml or .yml extensions.
|
||||
const ProjectRolesDefaultsMainFile = "main"
|
||||
|
||||
// ProjectRolesTemplateDir is a fixed directory name under a role, used to store templates required by tasks.
|
||||
const ProjectRolesTemplateDir = "templates"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||
|
|
@ -384,16 +385,20 @@ func (r *KKMachineReconciler) getConfig(scope *clusterScope, kkmachine *capkkinf
|
|||
if err := unstructured.SetNestedField(config.Value(), _const.ProviderID2Host(scope.Name, kkmachine.Spec.ProviderID), "node_name"); err != nil {
|
||||
return config, errors.Wrapf(err, "failed to set %q in config", "node_name")
|
||||
}
|
||||
if err := unstructured.SetNestedField(config.Value(), kkmachine.Spec.Version, "kube_version"); err != nil {
|
||||
if err := unstructured.SetNestedField(config.Value(), *kkmachine.Spec.Version, "kube_version"); err != nil {
|
||||
return config, errors.Wrapf(err, "failed to set %q in config", "kube_version")
|
||||
}
|
||||
if err := unstructured.SetNestedField(config.Value(), scope.Cluster.Name, "kubernetes", "cluster_name"); err != nil {
|
||||
return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.cluster_name")
|
||||
}
|
||||
if err := unstructured.SetNestedField(config.Value(), kkmachine.Spec.Roles, "kubernetes", "roles"); err != nil {
|
||||
if err := unstructured.SetNestedStringSlice(config.Value(), kkmachine.Spec.Roles, "kubernetes", "roles"); err != nil {
|
||||
return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.roles")
|
||||
}
|
||||
if err := unstructured.SetNestedField(config.Value(), scope.Cluster.Spec.ClusterNetwork, "cluster_network"); err != nil {
|
||||
converted, err := runtime.DefaultUnstructuredConverter.ToUnstructured(scope.Cluster.Spec.ClusterNetwork)
|
||||
if err != nil {
|
||||
return config, errors.Wrap(err, "failed to convert scope.Cluster.Spec.ClusterNetwork")
|
||||
}
|
||||
if err := unstructured.SetNestedMap(config.Value(), converted, "cluster_network"); err != nil {
|
||||
return config, errors.Wrapf(err, "failed to set %q in config", "cluster_network")
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +416,7 @@ func (r *KKMachineReconciler) getConfig(scope *clusterScope, kkmachine *capkkinf
|
|||
if err := unstructured.SetNestedField(config.Value(), scope.Cluster.Spec.ControlPlaneEndpoint.Host, "kubernetes", "control_plane_endpoint", "host"); err != nil {
|
||||
return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.control_plane_endpoint.host")
|
||||
}
|
||||
if err := unstructured.SetNestedField(config.Value(), scope.KKCluster.Spec.ControlPlaneEndpointType, "kubernetes", "control_plane_endpoint", "type"); err != nil {
|
||||
if err := unstructured.SetNestedField(config.Value(), string(scope.KKCluster.Spec.ControlPlaneEndpointType), "kubernetes", "control_plane_endpoint", "type"); err != nil {
|
||||
return config, errors.Wrapf(err, "failed to set %q in config", "kubernetes.control_plane_endpoint.kube_vip.type")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import (
|
|||
)
|
||||
|
||||
// MarshalBlock marshal block to task
|
||||
func MarshalBlock(role string, hosts []string, when []string, block kkprojectv1.Block) *kkcorev1alpha1.Task {
|
||||
func MarshalBlock(hosts []string, when []string, block kkprojectv1.Block) *kkcorev1alpha1.Task {
|
||||
task := &kkcorev1alpha1.Task{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Task",
|
||||
|
|
@ -44,9 +44,6 @@ func MarshalBlock(role string, hosts []string, when []string, block kkprojectv1.
|
|||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
CreationTimestamp: metav1.Now(),
|
||||
Annotations: map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRole: role,
|
||||
},
|
||||
},
|
||||
Spec: kkcorev1alpha1.TaskSpec{
|
||||
Name: block.Name,
|
||||
|
|
@ -58,6 +55,9 @@ func MarshalBlock(role string, hosts []string, when []string, block kkprojectv1.
|
|||
Register: block.Register,
|
||||
},
|
||||
}
|
||||
if annotation, ok := block.UnknownField["annotations"].(map[string]string); ok {
|
||||
task.ObjectMeta.Annotations = annotation
|
||||
}
|
||||
|
||||
if block.Loop != nil {
|
||||
data, err := json.Marshal(block.Loop)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ func funcMap() template.FuncMap {
|
|||
delete(f, "expandenv")
|
||||
// add custom function
|
||||
f["toYaml"] = toYAML
|
||||
f["fromYaml"] = fromYAML
|
||||
f["ipInCIDR"] = ipInCIDR
|
||||
f["ipFamily"] = ipFamily
|
||||
f["pow"] = pow
|
||||
|
|
@ -43,6 +44,15 @@ func toYAML(v any) string {
|
|||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
// fromYAML takes a YAML string, unmarshals it into an interface{}, and returns the result.
|
||||
// If there is an error during unmarshaling, it will be returned along with nil for the value.
|
||||
func fromYAML(v string) (any, error) {
|
||||
var output any
|
||||
err := yaml.Unmarshal([]byte(v), &output)
|
||||
|
||||
return output, err
|
||||
}
|
||||
|
||||
// ipInCIDR get the IP of a specific location within the cidr range
|
||||
func ipInCIDR(index int, cidr string) (string, error) {
|
||||
var ips = make([]string, 0)
|
||||
|
|
|
|||
|
|
@ -525,6 +525,18 @@ func TestParseFunction(t *testing.T) {
|
|||
},
|
||||
excepted: []byte("a1: b1\na2: b2"),
|
||||
},
|
||||
// ======= fromYaml =======
|
||||
{
|
||||
name: "fromYaml 1",
|
||||
input: "{{ .foo | fromYaml | toJson }}",
|
||||
variable: map[string]any{
|
||||
"foo": `
|
||||
a1: b1
|
||||
a2:
|
||||
b2: 1`,
|
||||
},
|
||||
excepted: []byte("{\"a1\":\"b1\",\"a2\":{\"b2\":1}}"),
|
||||
},
|
||||
// ======= indent =======
|
||||
{
|
||||
name: "indent 1",
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ func (e blockExecutor) dealBlock(ctx context.Context, hosts []string, ignoreErro
|
|||
|
||||
// dealTask "block" argument is not defined in block.
|
||||
func (e blockExecutor) dealTask(ctx context.Context, hosts []string, when []string, block kkprojectv1.Block) error {
|
||||
task := converter.MarshalBlock(e.role, hosts, when, block)
|
||||
task := converter.MarshalBlock(hosts, when, block)
|
||||
// complete module by unknown field
|
||||
for n, a := range block.UnknownField {
|
||||
data, err := json.Marshal(a)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func NewPlaybookExecutor(ctx context.Context, client ctrlclient.Client, playbook
|
|||
// get variable
|
||||
v, err := variable.New(ctx, client, *playbook, source.FileSource)
|
||||
if err != nil {
|
||||
klog.V(5).ErrorS(nil, "convert playbook error", "playbook", ctrlclient.ObjectKeyFromObject(playbook))
|
||||
klog.V(5).ErrorS(err, "get variable error", "playbook", ctrlclient.ObjectKeyFromObject(playbook))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import (
|
|||
kkcorev1 "github.com/kubesphere/kubekey/api/core/v1"
|
||||
kkcorev1alpha1 "github.com/kubesphere/kubekey/api/core/v1alpha1"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/klog/v2"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
|
@ -85,8 +84,8 @@ func (e *taskExecutor) runTaskLoop(ctx context.Context) error {
|
|||
|
||||
// Add role prefix to log output if role annotation exists
|
||||
var roleLog string
|
||||
if e.task.Annotations[kkcorev1alpha1.TaskAnnotationRole] != "" {
|
||||
roleLog = "[" + e.task.Annotations[kkcorev1alpha1.TaskAnnotationRole] + "] "
|
||||
if e.task.Annotations[kkcorev1alpha1.TaskAnnotationRelativePath] != "" {
|
||||
roleLog = "[" + e.task.Annotations[kkcorev1alpha1.TaskAnnotationRelativePath] + "] "
|
||||
}
|
||||
fmt.Fprintf(e.logOutput, "%s %s%s\n", time.Now().Format(time.TimeOnly+" MST"), roleLog, e.task.Spec.Name)
|
||||
|
||||
|
|
@ -241,7 +240,8 @@ func (e *taskExecutor) execTaskHostLogs(ctx context.Context, h string, stdout, s
|
|||
// progress bar for task
|
||||
var bar = progressbar.NewOptions(-1,
|
||||
progressbar.OptionSetWriter(e.logOutput),
|
||||
progressbar.OptionSpinnerCustom([]string{" "}),
|
||||
// progressbar.OptionSpinnerCustom([]string{" "}),
|
||||
progressbar.OptionSpinnerType(14),
|
||||
progressbar.OptionEnableColorCodes(true),
|
||||
progressbar.OptionSetDescription(fmt.Sprintf("[\033[36m%s\033[0m]%s \033[36mrunning\033[0m", h, placeholder)),
|
||||
progressbar.OptionOnCompletion(func() {
|
||||
|
|
@ -377,13 +377,10 @@ func (e *taskExecutor) dealRegister(stdout, stderr, host string) error {
|
|||
if e.task.Spec.Register != "" {
|
||||
var stdoutResult any = stdout
|
||||
var stderrResult any = stderr
|
||||
// try to convert by json or yaml
|
||||
// try to convert by json
|
||||
if json.Valid([]byte(stdout)) {
|
||||
_ = json.Unmarshal([]byte(stdout), &stdoutResult)
|
||||
_ = json.Unmarshal([]byte(stderr), &stderrResult)
|
||||
} else {
|
||||
_ = yaml.Unmarshal([]byte(stdout), &stdoutResult)
|
||||
_ = yaml.Unmarshal([]byte(stderr), &stderrResult)
|
||||
}
|
||||
// set variable to parent location
|
||||
if err := e.variable.Merge(variable.MergeRuntimeVariable(map[string]any{
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/kubesphere/kubekey/v4/pkg/connector"
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/project"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
||||
)
|
||||
|
|
@ -98,57 +99,106 @@ func ModuleCopy(ctx context.Context, options ExecOptions) (string, string) {
|
|||
|
||||
// copySrc copy src file to dest
|
||||
func (ca copyArgs) copySrc(ctx context.Context, options ExecOptions, conn connector.Connector) (string, string) {
|
||||
dealAbsoluteFilePath := func() (string, string) {
|
||||
fileInfo, err := os.Stat(ca.src)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf(" get src file %s in local path error: %v", ca.src, err)
|
||||
}
|
||||
if fileInfo.IsDir() { // src is dir
|
||||
if err := ca.absDir(ctx, conn); err != nil {
|
||||
return "", fmt.Sprintf("sync copy absolute dir error %s", err)
|
||||
}
|
||||
} else { // src is file
|
||||
data, err := os.ReadFile(ca.src)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("read file error: %s", err)
|
||||
}
|
||||
if err := ca.readFile(ctx, data, fileInfo.Mode(), conn); err != nil {
|
||||
return "", fmt.Sprintf("sync copy absolute dir error %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
dealRelativeFilePath := func() (string, string) {
|
||||
pj, err := project.New(ctx, options.Playbook, false)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("get project error: %v", err)
|
||||
}
|
||||
fileInfo, err := pj.Stat(ca.src, project.GetFileOption{IsFile: true, Role: options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRole]})
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("get file %s from project error %v", ca.src, err)
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
if err := ca.relDir(ctx, pj, options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRole], conn); err != nil {
|
||||
return "", fmt.Sprintf("sync copy relative dir error %s", err)
|
||||
}
|
||||
} else {
|
||||
data, err := pj.ReadFile(ca.src, project.GetFileOption{IsFile: true, Role: options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRole]})
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("read file error: %s", err)
|
||||
}
|
||||
if err := ca.readFile(ctx, data, fileInfo.Mode(), conn); err != nil {
|
||||
return "", fmt.Sprintf("sync copy relative dir error %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
if filepath.IsAbs(ca.src) { // if src is absolute path. find it in local path
|
||||
return dealAbsoluteFilePath()
|
||||
return ca.handleAbsolutePath(ctx, conn)
|
||||
}
|
||||
// if src is not absolute path. find file in project
|
||||
return dealRelativeFilePath()
|
||||
return ca.handleRelativePath(ctx, options, conn)
|
||||
}
|
||||
|
||||
func (ca copyArgs) handleAbsolutePath(ctx context.Context, conn connector.Connector) (string, string) {
|
||||
fileInfo, err := os.Stat(ca.src)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf(" get src file %s in local path error: %v", ca.src, err)
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() { // src is dir
|
||||
if err := ca.absDir(ctx, conn); err != nil {
|
||||
return "", fmt.Sprintf("sync copy absolute dir error %s", err)
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
// src is file
|
||||
data, err := os.ReadFile(ca.src)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("read file error: %s", err)
|
||||
}
|
||||
if err := ca.readFile(ctx, data, fileInfo.Mode(), conn); err != nil {
|
||||
return "", fmt.Sprintf("sync copy absolute dir error %s", err)
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
func (ca copyArgs) handleRelativePath(ctx context.Context, options ExecOptions, conn connector.Connector) (string, string) {
|
||||
pj, err := project.New(ctx, options.Playbook, false)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("get project error: %v", err)
|
||||
}
|
||||
|
||||
relPath := filepath.Join(options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRelativePath], _const.ProjectRolesFilesDir, ca.src)
|
||||
fileInfo, err := pj.Stat(relPath)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("get file %s from project error %v", ca.src, err)
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
if err := ca.handleRelativeDir(ctx, pj, relPath, conn); err != nil {
|
||||
return "", fmt.Sprintf("sync copy relative dir error %s", err)
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
// Handle single file
|
||||
data, err := pj.ReadFile(relPath)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("read file error: %s", err)
|
||||
}
|
||||
if err := ca.readFile(ctx, data, fileInfo.Mode(), conn); err != nil {
|
||||
return "", fmt.Sprintf("sync copy relative dir error %s", err)
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
func (ca copyArgs) handleRelativeDir(ctx context.Context, pj project.Project, relPath string, conn connector.Connector) error {
|
||||
return pj.WalkDir(relPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() { // only copy file
|
||||
return nil
|
||||
}
|
||||
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get file info")
|
||||
}
|
||||
|
||||
mode := info.Mode()
|
||||
if ca.mode != nil {
|
||||
mode = os.FileMode(*ca.mode)
|
||||
}
|
||||
|
||||
data, err := pj.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read file")
|
||||
}
|
||||
|
||||
dest := ca.dest
|
||||
if strings.HasSuffix(ca.dest, "/") {
|
||||
rel, err := pj.Rel(relPath, path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get relative file path")
|
||||
}
|
||||
dest = filepath.Join(ca.dest, rel)
|
||||
}
|
||||
|
||||
return conn.PutFile(ctx, data, dest, mode)
|
||||
})
|
||||
}
|
||||
|
||||
// copyContent convert content param and copy to dest
|
||||
|
|
@ -168,44 +218,6 @@ func (ca copyArgs) copyContent(ctx context.Context, mode fs.FileMode, conn conne
|
|||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
// relDir when copy.src is relative dir, get all files from project, and copy to remote.
|
||||
func (ca copyArgs) relDir(ctx context.Context, pj project.Project, role string, conn connector.Connector) error {
|
||||
return pj.WalkDir(ca.src, project.GetFileOption{IsFile: true, Role: role}, func(path string, d fs.DirEntry, err error) error {
|
||||
if d.IsDir() { // only copy file
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get file info")
|
||||
}
|
||||
|
||||
mode := info.Mode()
|
||||
if ca.mode != nil {
|
||||
mode = os.FileMode(*ca.mode)
|
||||
}
|
||||
|
||||
data, err := pj.ReadFile(path, project.GetFileOption{Role: role})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read file")
|
||||
}
|
||||
|
||||
dest := ca.dest
|
||||
if strings.HasSuffix(ca.dest, "/") {
|
||||
rel, err := pj.Rel(ca.src, path, project.GetFileOption{Role: role})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get relative file path")
|
||||
}
|
||||
dest = filepath.Join(ca.dest, rel)
|
||||
}
|
||||
|
||||
return conn.PutFile(ctx, data, dest, mode)
|
||||
})
|
||||
}
|
||||
|
||||
// absFile when copy.src is absolute file, get file from os, and copy to remote.
|
||||
func (ca copyArgs) readFile(ctx context.Context, data []byte, mode fs.FileMode, conn connector.Connector) error {
|
||||
dest := ca.dest
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/kubesphere/kubekey/v4/pkg/connector"
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/converter/tmpl"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/project"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
||||
|
|
@ -74,78 +75,132 @@ func newTemplateArgs(_ context.Context, raw runtime.RawExtension, vars map[strin
|
|||
|
||||
// ModuleTemplate deal "template" module
|
||||
func ModuleTemplate(ctx context.Context, options ExecOptions) (string, string) {
|
||||
// get host variable
|
||||
ha, err := options.getAllVariables()
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
|
||||
ta, err := newTemplateArgs(ctx, options.Args, ha)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
|
||||
// get connector
|
||||
conn, err := getConnector(ctx, options.Host, options.Variable)
|
||||
ha, ta, conn, err := prepareTemplate(ctx, options)
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
defer conn.Close(ctx)
|
||||
|
||||
dealAbsoluteFilePath := func() (string, string) {
|
||||
fileInfo, err := os.Stat(ta.src)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf(" get src file %s in local path error: %v", ta.src, err)
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() { // src is dir
|
||||
if err := ta.absDir(ctx, conn, ha); err != nil {
|
||||
return "", fmt.Sprintf("sync template absolute dir error %s", err)
|
||||
}
|
||||
} else { // src is file
|
||||
data, err := os.ReadFile(ta.src)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("read file error: %s", err)
|
||||
}
|
||||
if err := ta.readFile(ctx, string(data), fileInfo.Mode(), conn, ha); err != nil {
|
||||
return "", fmt.Sprintf("sync template absolute file error %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
dealRelativeFilePath := func() (string, string) {
|
||||
pj, err := project.New(ctx, options.Playbook, false)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("get project error: %v", err)
|
||||
}
|
||||
|
||||
fileInfo, err := pj.Stat(ta.src, project.GetFileOption{IsTemplate: true, Role: options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRole]})
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("get file %s from project error: %v", ta.src, err)
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
if err := ta.relDir(ctx, pj, options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRole], conn, ha); err != nil {
|
||||
return "", fmt.Sprintf("sync template relative dir error: %s", err)
|
||||
}
|
||||
} else {
|
||||
data, err := pj.ReadFile(ta.src, project.GetFileOption{IsTemplate: true, Role: options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRole]})
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("read file error: %s", err)
|
||||
}
|
||||
if err := ta.readFile(ctx, string(data), fileInfo.Mode(), conn, ha); err != nil {
|
||||
return "", fmt.Sprintf("sync template relative dir error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
if filepath.IsAbs(ta.src) {
|
||||
return dealAbsoluteFilePath()
|
||||
return handleAbsoluteTemplate(ctx, ta, conn, ha)
|
||||
}
|
||||
|
||||
return dealRelativeFilePath()
|
||||
return handleRelativeTemplate(ctx, ta, conn, ha, options)
|
||||
}
|
||||
|
||||
func prepareTemplate(ctx context.Context, options ExecOptions) (map[string]any, *templateArgs, connector.Connector, error) {
|
||||
ha, err := options.getAllVariables()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
ta, err := newTemplateArgs(ctx, options.Args, ha)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
conn, err := getConnector(ctx, options.Host, options.Variable)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return ha, ta, conn, nil
|
||||
}
|
||||
|
||||
func handleAbsoluteTemplate(ctx context.Context, ta *templateArgs, conn connector.Connector, vars map[string]any) (string, string) {
|
||||
fileInfo, err := os.Stat(ta.src)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf(" get src file %s in local path error: %v", ta.src, err)
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
if err := ta.absDir(ctx, conn, vars); err != nil {
|
||||
return "", fmt.Sprintf("sync template absolute dir error %s", err)
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(ta.src)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("read file error: %s", err)
|
||||
}
|
||||
if err := ta.readFile(ctx, string(data), fileInfo.Mode(), conn, vars); err != nil {
|
||||
return "", fmt.Sprintf("sync template absolute file error %s", err)
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
func handleRelativeTemplate(ctx context.Context, ta *templateArgs, conn connector.Connector, vars map[string]any, options ExecOptions) (string, string) {
|
||||
pj, err := project.New(ctx, options.Playbook, false)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("get project error: %v", err)
|
||||
}
|
||||
|
||||
relPath := filepath.Join(options.Task.Annotations[kkcorev1alpha1.TaskAnnotationRelativePath], _const.ProjectRolesTemplateDir, ta.src)
|
||||
fileInfo, err := pj.Stat(relPath)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("get file %s from project error: %v", ta.src, err)
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
if err := handleRelativeDir(ctx, pj, relPath, ta, conn, vars); err != nil {
|
||||
return "", fmt.Sprintf("sync template relative dir error: %s", err)
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
data, err := pj.ReadFile(relPath)
|
||||
if err != nil {
|
||||
return "", fmt.Sprintf("read file error: %s", err)
|
||||
}
|
||||
if err := ta.readFile(ctx, string(data), fileInfo.Mode(), conn, vars); err != nil {
|
||||
return "", fmt.Sprintf("sync template relative dir error: %s", err)
|
||||
}
|
||||
|
||||
return StdoutSuccess, ""
|
||||
}
|
||||
|
||||
func handleRelativeDir(ctx context.Context, pj project.Project, relPath string, ta *templateArgs, conn connector.Connector, vars map[string]any) error {
|
||||
return pj.WalkDir(relPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() { // only deal file
|
||||
return nil
|
||||
}
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get file %q info", path)
|
||||
}
|
||||
|
||||
mode := info.Mode()
|
||||
if ta.mode != nil {
|
||||
mode = os.FileMode(*ta.mode)
|
||||
}
|
||||
|
||||
data, err := pj.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read file %q", path)
|
||||
}
|
||||
result, err := tmpl.Parse(vars, string(data))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse file %q", path)
|
||||
}
|
||||
|
||||
dest := ta.dest
|
||||
if strings.HasSuffix(ta.dest, "/") {
|
||||
rel, err := pj.Rel(relPath, path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get relative filepath")
|
||||
}
|
||||
dest = filepath.Join(ta.dest, rel)
|
||||
}
|
||||
|
||||
return conn.PutFile(ctx, result, dest, mode)
|
||||
})
|
||||
}
|
||||
|
||||
// relFile when template.src is relative file, get file from project, parse it, and copy to remote.
|
||||
|
|
@ -167,48 +222,6 @@ func (ta templateArgs) readFile(ctx context.Context, data string, mode fs.FileMo
|
|||
return conn.PutFile(ctx, result, dest, mode)
|
||||
}
|
||||
|
||||
// relDir when template.src is relative dir, get all files from project, parse it, and copy to remote.
|
||||
func (ta templateArgs) relDir(ctx context.Context, pj project.Project, role string, conn connector.Connector, vars map[string]any) error {
|
||||
return pj.WalkDir(ta.src, project.GetFileOption{IsTemplate: true, Role: role}, func(path string, d fs.DirEntry, err error) error {
|
||||
if d.IsDir() { // only copy file
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get file %q info", path)
|
||||
}
|
||||
|
||||
mode := info.Mode()
|
||||
if ta.mode != nil {
|
||||
mode = os.FileMode(*ta.mode)
|
||||
}
|
||||
|
||||
data, err := pj.ReadFile(path, project.GetFileOption{IsTemplate: true, Role: role})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read file %q", path)
|
||||
}
|
||||
result, err := tmpl.Parse(vars, string(data))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse file %q", path)
|
||||
}
|
||||
|
||||
dest := ta.dest
|
||||
if strings.HasSuffix(ta.dest, "/") {
|
||||
rel, err := pj.Rel(ta.src, path, project.GetFileOption{IsTemplate: true, Role: role})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get relative filepath")
|
||||
}
|
||||
dest = filepath.Join(ta.dest, rel)
|
||||
}
|
||||
|
||||
return conn.PutFile(ctx, result, dest, mode)
|
||||
})
|
||||
}
|
||||
|
||||
// absDir when template.src is absolute dir, get all files by os, parse it, and copy to remote.
|
||||
func (ta templateArgs) absDir(ctx context.Context, conn connector.Connector, vars map[string]any) error {
|
||||
if err := filepath.WalkDir(ta.src, func(path string, d fs.DirEntry, err error) error {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ limitations under the License.
|
|||
package project
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
|
@ -29,7 +27,6 @@ import (
|
|||
kkprojectv1 "github.com/kubesphere/kubekey/api/project/v1"
|
||||
|
||||
"github.com/kubesphere/kubekey/v4/builtin/core"
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -42,70 +39,10 @@ func init() {
|
|||
return nil, errors.New("playbook should be relative path base on project.addr")
|
||||
}
|
||||
|
||||
return &builtinProject{Playbook: playbook, FS: core.BuiltinPlaybook, playbook: playbook.Spec.Playbook}, nil
|
||||
return &project{
|
||||
FS: core.BuiltinPlaybook,
|
||||
basePlaybook: playbook.Spec.Playbook,
|
||||
Playbook: &kkprojectv1.Playbook{},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type builtinProject struct {
|
||||
kkcorev1.Playbook
|
||||
|
||||
fs.FS
|
||||
// playbook relpath base on projectDir
|
||||
playbook string
|
||||
}
|
||||
|
||||
func (p builtinProject) getFilePath(path string, o GetFileOption) string {
|
||||
var find []string
|
||||
switch {
|
||||
case o.IsFile:
|
||||
if o.Role != "" {
|
||||
// find from project/roles/roleName
|
||||
find = append(find, filepath.Join(_const.ProjectRolesDir, o.Role, _const.ProjectRolesFilesDir, path))
|
||||
// find from pbPath dir like: current_playbook/roles/roleName
|
||||
find = append(find, filepath.Join(p.playbook, _const.ProjectRolesDir, o.Role, _const.ProjectRolesFilesDir, path))
|
||||
}
|
||||
find = append(find, filepath.Join(_const.ProjectRolesFilesDir, path))
|
||||
case o.IsTemplate:
|
||||
// find from project/roles/roleName
|
||||
if o.Role != "" {
|
||||
find = append(find, filepath.Join(_const.ProjectRolesDir, o.Role, _const.ProjectRolesTemplateDir, path))
|
||||
// find from pbPath dir like: current_playbook/roles/roleName
|
||||
find = append(find, filepath.Join(p.playbook, _const.ProjectRolesDir, o.Role, _const.ProjectRolesTemplateDir, path))
|
||||
}
|
||||
find = append(find, filepath.Join(_const.ProjectRolesTemplateDir, path))
|
||||
default:
|
||||
find = append(find, path)
|
||||
}
|
||||
for _, s := range find {
|
||||
if _, err := fs.Stat(p.FS, s); err == nil {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// MarshalPlaybook project file to playbook.
|
||||
func (p builtinProject) MarshalPlaybook() (*kkprojectv1.Playbook, error) {
|
||||
return marshalPlaybook(p.FS, p.playbook)
|
||||
}
|
||||
|
||||
// Stat role/file/template file or dir in project
|
||||
func (p builtinProject) Stat(path string, option GetFileOption) (os.FileInfo, error) {
|
||||
return fs.Stat(p.FS, p.getFilePath(path, option))
|
||||
}
|
||||
|
||||
// WalkDir role/file/template dir in project
|
||||
func (p builtinProject) WalkDir(path string, option GetFileOption, f fs.WalkDirFunc) error {
|
||||
return fs.WalkDir(p.FS, p.getFilePath(path, option), f)
|
||||
}
|
||||
|
||||
// ReadFile role/file/template file or dir in project
|
||||
func (p builtinProject) ReadFile(path string, option GetFileOption) ([]byte, error) {
|
||||
return fs.ReadFile(p.FS, p.getFilePath(path, option))
|
||||
}
|
||||
|
||||
// Rel path for role/file/template file or dir in project
|
||||
func (p builtinProject) Rel(root string, path string, option GetFileOption) (string, error) {
|
||||
return filepath.Rel(p.getFilePath(root, option), path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package project
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -48,134 +47,65 @@ func newGitProject(ctx context.Context, playbook kkcorev1.Playbook, update bool)
|
|||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get %q in config", _const.ProjectsDir)
|
||||
}
|
||||
|
||||
// git clone to project dir
|
||||
if playbook.Spec.Project.Name == "" {
|
||||
playbook.Spec.Project.Name = strings.TrimSuffix(playbook.Spec.Project.Addr[strings.LastIndex(playbook.Spec.Project.Addr, "/")+1:], ".git")
|
||||
}
|
||||
|
||||
p := &gitProject{
|
||||
Playbook: playbook,
|
||||
projectDir: filepath.Join(projectDir, playbook.Spec.Project.Name),
|
||||
playbook: playbook.Spec.Playbook,
|
||||
}
|
||||
|
||||
if _, err := os.Stat(p.projectDir); os.IsNotExist(err) {
|
||||
projectDir = filepath.Join(projectDir, playbook.Spec.Project.Name)
|
||||
if _, err := os.Stat(projectDir); os.IsNotExist(err) {
|
||||
// git clone
|
||||
if err := p.gitClone(ctx); err != nil {
|
||||
if err := gitClone(ctx, projectDir, playbook.Spec.Project); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if update {
|
||||
// git pull
|
||||
if err := p.gitPull(ctx); err != nil {
|
||||
if err := gitPull(ctx, projectDir, playbook.Spec.Project); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
return &project{
|
||||
FS: os.DirFS(filepath.Join(projectDir, playbook.Spec.Project.Name)),
|
||||
basePlaybook: playbook.Spec.Playbook,
|
||||
Playbook: &kkprojectv1.Playbook{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// gitProject from git
|
||||
type gitProject struct {
|
||||
kkcorev1.Playbook
|
||||
|
||||
//location
|
||||
projectDir string
|
||||
// playbook relpath base on projectDir
|
||||
playbook string
|
||||
}
|
||||
|
||||
func (p gitProject) getFilePath(path string, o GetFileOption) string {
|
||||
var find []string
|
||||
switch {
|
||||
case o.IsFile:
|
||||
if o.Role != "" {
|
||||
// find from project/roles/roleName
|
||||
find = append(find, filepath.Join(p.projectDir, _const.ProjectRolesDir, o.Role, _const.ProjectRolesFilesDir, path))
|
||||
// find from pbPath dir like: current_playbook/roles/roleName
|
||||
find = append(find, filepath.Join(p.projectDir, p.playbook, _const.ProjectRolesDir, o.Role, _const.ProjectRolesFilesDir, path))
|
||||
}
|
||||
find = append(find, filepath.Join(p.projectDir, _const.ProjectRolesFilesDir, path))
|
||||
case o.IsTemplate:
|
||||
// find from project/roles/roleName
|
||||
if o.Role != "" {
|
||||
find = append(find, filepath.Join(p.projectDir, _const.ProjectRolesDir, o.Role, _const.ProjectRolesTemplateDir, path))
|
||||
// find from pbPath dir like: current_playbook/roles/roleName
|
||||
find = append(find, filepath.Join(p.projectDir, p.playbook, _const.ProjectRolesDir, o.Role, _const.ProjectRolesTemplateDir, path))
|
||||
}
|
||||
find = append(find, filepath.Join(p.projectDir, _const.ProjectRolesTemplateDir, path))
|
||||
default:
|
||||
find = append(find, filepath.Join(p.projectDir, path))
|
||||
}
|
||||
for _, s := range find {
|
||||
if _, err := os.Stat(s); err == nil {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p gitProject) gitClone(ctx context.Context) error {
|
||||
if _, err := git.PlainCloneContext(ctx, p.projectDir, false, &git.CloneOptions{
|
||||
URL: p.Playbook.Spec.Project.Addr,
|
||||
func gitClone(ctx context.Context, localDir string, project kkcorev1.PlaybookProject) error {
|
||||
if _, err := git.PlainCloneContext(ctx, localDir, false, &git.CloneOptions{
|
||||
URL: project.Addr,
|
||||
Progress: nil,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(p.Playbook.Spec.Project.Branch),
|
||||
ReferenceName: plumbing.NewBranchReferenceName(project.Branch),
|
||||
SingleBranch: true,
|
||||
Auth: &http.TokenAuth{Token: p.Playbook.Spec.Project.Token},
|
||||
Auth: &http.TokenAuth{Token: project.Token},
|
||||
InsecureSkipTLS: false,
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to clone project %q", p.Playbook.Spec.Project.Addr)
|
||||
return errors.Wrapf(err, "failed to clone project %q", project.Addr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p gitProject) gitPull(ctx context.Context) error {
|
||||
open, err := git.PlainOpen(p.projectDir)
|
||||
func gitPull(ctx context.Context, localDir string, project kkcorev1.PlaybookProject) error {
|
||||
open, err := git.PlainOpen(localDir)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open git project %a", p.projectDir)
|
||||
return errors.Wrapf(err, "failed to open git project %a", localDir)
|
||||
}
|
||||
|
||||
wt, err := open.Worktree()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open git project %q worktree", p.projectDir)
|
||||
return errors.Wrapf(err, "failed to open git project %q worktree", localDir)
|
||||
}
|
||||
|
||||
if err := wt.PullContext(ctx, &git.PullOptions{
|
||||
RemoteURL: p.Playbook.Spec.Project.Addr,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(p.Playbook.Spec.Project.Branch),
|
||||
RemoteURL: project.Addr,
|
||||
ReferenceName: plumbing.NewBranchReferenceName(project.Branch),
|
||||
SingleBranch: true,
|
||||
Auth: &http.TokenAuth{Token: p.Playbook.Spec.Project.Token},
|
||||
Auth: &http.TokenAuth{Token: project.Token},
|
||||
InsecureSkipTLS: false,
|
||||
}); err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
|
||||
return errors.Wrapf(err, "failed to pull git project %q", p.playbook)
|
||||
return errors.Wrapf(err, "failed to pull git project %q", project.Addr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalPlaybook project file to playbook.
|
||||
func (p gitProject) MarshalPlaybook() (*kkprojectv1.Playbook, error) {
|
||||
return marshalPlaybook(os.DirFS(p.projectDir), p.Playbook.Spec.Playbook)
|
||||
}
|
||||
|
||||
// Stat role/file/template file or dir in project
|
||||
func (p gitProject) Stat(path string, option GetFileOption) (os.FileInfo, error) {
|
||||
return os.Stat(p.getFilePath(path, option))
|
||||
}
|
||||
|
||||
// WalkDir role/file/template dir in project
|
||||
func (p gitProject) WalkDir(path string, option GetFileOption, f fs.WalkDirFunc) error {
|
||||
return filepath.WalkDir(p.getFilePath(path, option), f)
|
||||
}
|
||||
|
||||
// ReadFile role/file/template file or dir in project
|
||||
func (p gitProject) ReadFile(path string, option GetFileOption) ([]byte, error) {
|
||||
return os.ReadFile(p.getFilePath(path, option))
|
||||
}
|
||||
|
||||
// Rel path for role/file/template file or dir in project
|
||||
func (p gitProject) Rel(root string, path string, option GetFileOption) (string, error) {
|
||||
return filepath.Rel(p.getFilePath(root, option), path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,347 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 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 project
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
kkprojectv1 "github.com/kubesphere/kubekey/api/project/v1"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
||||
)
|
||||
|
||||
// marshalPlaybook kkprojectv1.Playbook from a playbook file
|
||||
func marshalPlaybook(baseFS fs.FS, pbPath string) (*kkprojectv1.Playbook, error) {
|
||||
// convert playbook to kkprojectv1.Playbook
|
||||
pb := &kkprojectv1.Playbook{}
|
||||
if err := loadPlaybook(baseFS, pbPath, pb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// convertRoles.
|
||||
if err := convertRoles(baseFS, pbPath, pb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// convertIncludeTasks
|
||||
if err := convertIncludeTasks(baseFS, pbPath, pb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// validate playbook
|
||||
if err := pb.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pb, nil
|
||||
}
|
||||
|
||||
// loadPlaybook with include_playbook. Join all playbooks into one playbook
|
||||
func loadPlaybook(baseFS fs.FS, pbPath string, pb *kkprojectv1.Playbook) error {
|
||||
// baseDir is the local ansible project dir which playbook belong to
|
||||
pbData, err := fs.ReadFile(baseFS, pbPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read playbook %q", pbPath)
|
||||
}
|
||||
var plays []kkprojectv1.Play
|
||||
if err := yaml.Unmarshal(pbData, &plays); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal playbook %q", pbPath)
|
||||
}
|
||||
|
||||
for _, p := range plays {
|
||||
if err := dealImportPlaybook(p, baseFS, pbPath, pb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := dealVarsFiles(&p, baseFS, pbPath); err != nil {
|
||||
return err
|
||||
}
|
||||
// fill block in roles
|
||||
if err := dealRoles(p, baseFS, pbPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pb.Play = append(pb.Play, p)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dealImportPlaybook "import_playbook" argument in play
|
||||
func dealImportPlaybook(p kkprojectv1.Play, baseFS fs.FS, pbPath string, pb *kkprojectv1.Playbook) error {
|
||||
if p.ImportPlaybook != "" {
|
||||
importPlaybook := getPlaybookBaseFromPlaybook(baseFS, pbPath, p.ImportPlaybook)
|
||||
if importPlaybook == "" {
|
||||
return errors.Errorf("import_playbook %q path is empty, it's maybe [project-dir/playbooks/import_playbook_file, playbook-dir/playbooks/import_playbook-file, playbook-dir/import_playbook-file]", p.ImportPlaybook)
|
||||
}
|
||||
if err := loadPlaybook(baseFS, importPlaybook, pb); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dealVarsFiles "var_files" argument in play
|
||||
func dealVarsFiles(p *kkprojectv1.Play, baseFS fs.FS, pbPath string) error {
|
||||
for _, file := range p.VarsFiles {
|
||||
// load vars from vars_files
|
||||
if _, err := fs.Stat(baseFS, filepath.Join(filepath.Dir(pbPath), file)); err != nil {
|
||||
return errors.Wrapf(err, "failed to stat file %q", file)
|
||||
}
|
||||
data, err := fs.ReadFile(baseFS, filepath.Join(filepath.Dir(pbPath), file))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read file %q", filepath.Join(filepath.Dir(pbPath), file))
|
||||
}
|
||||
var newVars map[string]any
|
||||
// Unmarshal the YAML document into a root node.
|
||||
if err := yaml.Unmarshal(data, &newVars); err != nil {
|
||||
return errors.Wrap(err, "failed to failed to unmarshal YAML")
|
||||
}
|
||||
// store vars in play. the vars defined in file should not be repeated.
|
||||
p.Vars = variable.CombineVariables(newVars, p.Vars)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dealRoles "roles" argument in play
|
||||
func dealRoles(p kkprojectv1.Play, baseFS fs.FS, pbPath string) error {
|
||||
for i, r := range p.Roles {
|
||||
roleBase := getRoleBaseFromPlaybook(baseFS, pbPath, r.Role)
|
||||
if roleBase == "" {
|
||||
return errors.Errorf("cannot found Role %q", r.Role)
|
||||
}
|
||||
|
||||
mainTask := getYamlFile(baseFS, filepath.Join(roleBase, _const.ProjectRolesTasksDir, _const.ProjectRolesTasksMainFile))
|
||||
if mainTask == "" {
|
||||
return errors.Errorf("cannot found main task for Role %q", r.Role)
|
||||
}
|
||||
|
||||
rdata, err := fs.ReadFile(baseFS, mainTask)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read file %q", mainTask)
|
||||
}
|
||||
var blocks []kkprojectv1.Block
|
||||
if err := yaml.Unmarshal(rdata, &blocks); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal yaml file %q", filepath.Join(filepath.Dir(pbPath), mainTask))
|
||||
}
|
||||
p.Roles[i].Block = blocks
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertRoles convert roleName to block
|
||||
func convertRoles(baseFS fs.FS, pbPath string, pb *kkprojectv1.Playbook) error {
|
||||
for i, p := range pb.Play {
|
||||
for i, r := range p.Roles {
|
||||
roleBase := getRoleBaseFromPlaybook(baseFS, pbPath, r.Role)
|
||||
if roleBase == "" {
|
||||
return errors.Errorf("cannot found Role %q in playbook %q", r.Role, pbPath)
|
||||
}
|
||||
|
||||
var err error
|
||||
if p.Roles[i].Block, err = convertRoleBlocks(baseFS, pbPath, roleBase); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = convertRoleVars(baseFS, roleBase, &p.Roles[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pb.Play[i] = p
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertRoleVars(baseFS fs.FS, roleBase string, role *kkprojectv1.Role) error {
|
||||
// load defaults (optional)
|
||||
defaultsFile := getYamlFile(baseFS, filepath.Join(roleBase, _const.ProjectRolesDefaultsDir, _const.ProjectRolesDefaultsMainFile))
|
||||
if defaultsFile != "" {
|
||||
data, err := fs.ReadFile(baseFS, defaultsFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read defaults variable file %q", defaultsFile)
|
||||
}
|
||||
|
||||
var newVars map[string]any
|
||||
// Unmarshal the YAML document into a root node.
|
||||
if err := yaml.Unmarshal(data, &newVars); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal YAML")
|
||||
}
|
||||
// store vars in play. the vars defined in file should not be repeated.
|
||||
role.Vars = variable.CombineVariables(newVars, role.Vars)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertRoleBlocks roles/task/main.yaml to []kkprojectv1.Block
|
||||
func convertRoleBlocks(baseFS fs.FS, pbPath string, roleBase string) ([]kkprojectv1.Block, error) {
|
||||
mainTask := getYamlFile(baseFS, filepath.Join(roleBase, _const.ProjectRolesTasksDir, _const.ProjectRolesTasksMainFile))
|
||||
if mainTask == "" {
|
||||
return nil, errors.Errorf("cannot found main task for Role %q", roleBase)
|
||||
}
|
||||
|
||||
rdata, err := fs.ReadFile(baseFS, mainTask)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read file %q", mainTask)
|
||||
}
|
||||
var blocks []kkprojectv1.Block
|
||||
if err := yaml.Unmarshal(rdata, &blocks); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal yaml file %q", filepath.Join(filepath.Dir(pbPath), mainTask))
|
||||
}
|
||||
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
// convertIncludeTasks from file to blocks
|
||||
func convertIncludeTasks(baseFS fs.FS, pbPath string, pb *kkprojectv1.Playbook) error {
|
||||
var pbBase = filepath.Dir(filepath.Dir(pbPath))
|
||||
for _, play := range pb.Play {
|
||||
if err := fileToBlock(baseFS, pbBase, play.PreTasks); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileToBlock(baseFS, pbBase, play.Tasks); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileToBlock(baseFS, pbBase, play.PostTasks); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range play.Roles {
|
||||
roleBase := getRoleBaseFromPlaybook(baseFS, pbPath, r.Role)
|
||||
if err := fileToBlock(baseFS, filepath.Join(roleBase, _const.ProjectRolesTasksDir), r.Block); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileToBlock(baseFS fs.FS, baseDir string, blocks []kkprojectv1.Block) error {
|
||||
for i, b := range blocks {
|
||||
if b.IncludeTasks != "" {
|
||||
data, err := fs.ReadFile(baseFS, filepath.Join(baseDir, b.IncludeTasks))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read includeTask file %q", filepath.Join(baseDir, b.IncludeTasks))
|
||||
}
|
||||
var bs []kkprojectv1.Block
|
||||
if err := yaml.Unmarshal(data, &bs); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal includeTask file %q", filepath.Join(baseDir, b.IncludeTasks))
|
||||
}
|
||||
|
||||
b.Block = bs
|
||||
blocks[i] = b
|
||||
}
|
||||
|
||||
if err := fileToBlock(baseFS, baseDir, b.Block); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileToBlock(baseFS, baseDir, b.Rescue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fileToBlock(baseFS, baseDir, b.Always); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPlaybookBaseFromPlaybook find import_playbook path base on the current_playbook
|
||||
// find from project/playbooks/playbook if exists.
|
||||
// find from current_playbook/playbooks/playbook if exists.
|
||||
// find current_playbook/playbook
|
||||
func getPlaybookBaseFromPlaybook(baseFS fs.FS, pbPath string, playbook string) string {
|
||||
var find []string
|
||||
// find from project/playbooks/playbook
|
||||
find = append(find, filepath.Join(filepath.Dir(filepath.Dir(pbPath)), _const.ProjectPlaybooksDir, playbook))
|
||||
// find from pbPath dir like: current_playbook/playbooks/playbook
|
||||
find = append(find, filepath.Join(filepath.Dir(pbPath), _const.ProjectPlaybooksDir, playbook))
|
||||
// find from pbPath dir like: current_playbook/playbook
|
||||
find = append(find, filepath.Join(filepath.Dir(pbPath), playbook))
|
||||
for _, s := range find {
|
||||
if baseFS != nil {
|
||||
if _, err := fs.Stat(baseFS, s); err == nil {
|
||||
return s
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(s); err == nil {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// getRoleBaseFromPlaybook
|
||||
// find from project/roles/roleName if exists.
|
||||
// find from current_playbook/roles/roleName if exists.
|
||||
// find current_playbook/playbook
|
||||
func getRoleBaseFromPlaybook(baseFS fs.FS, pbPath string, roleName string) string {
|
||||
var find []string
|
||||
// find from project/roles/roleName
|
||||
find = append(find, filepath.Join(filepath.Dir(filepath.Dir(pbPath)), _const.ProjectRolesDir, roleName))
|
||||
// find from pbPath dir like: current_playbook/roles/roleName
|
||||
find = append(find, filepath.Join(filepath.Dir(pbPath), _const.ProjectRolesDir, roleName))
|
||||
|
||||
for _, s := range find {
|
||||
if baseFS != nil {
|
||||
if _, err := fs.Stat(baseFS, s); err == nil {
|
||||
return s
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(s); err == nil {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// getYamlFile
|
||||
// return *.yaml if exists
|
||||
// return *.yml if exists.
|
||||
func getYamlFile(baseFS fs.FS, base string) string {
|
||||
var find []string
|
||||
find = append(find, base+".yaml", base+".yml")
|
||||
|
||||
for _, s := range find {
|
||||
if baseFS != nil {
|
||||
if _, err := fs.Stat(baseFS, s); err == nil {
|
||||
return s
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(s); err == nil {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 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 project
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
kkprojectv1 "github.com/kubesphere/kubekey/api/project/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetPlaybookBaseFromAbsPlaybook(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
basePlaybook string
|
||||
playbook string
|
||||
except string
|
||||
}{
|
||||
{
|
||||
name: "find from project/playbooks/playbook",
|
||||
basePlaybook: filepath.Join("playbooks", "playbook1.yaml"),
|
||||
playbook: "playbook2.yaml",
|
||||
except: filepath.Join("playbooks", "playbook2.yaml"),
|
||||
},
|
||||
{
|
||||
name: "find from current_playbook/playbooks/playbook",
|
||||
basePlaybook: filepath.Join("playbooks", "playbook1.yaml"),
|
||||
playbook: "playbook3.yaml",
|
||||
except: filepath.Join("playbooks", "playbooks", "playbook3.yaml"),
|
||||
},
|
||||
{
|
||||
name: "cannot find",
|
||||
basePlaybook: filepath.Join("playbooks", "playbook1.yaml"),
|
||||
playbook: "playbook4.yaml",
|
||||
except: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.except, getPlaybookBaseFromPlaybook(os.DirFS("testdata"), tc.basePlaybook, tc.playbook))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRoleBaseFromAbsPlaybook(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
basePlaybook string
|
||||
roleName string
|
||||
except string
|
||||
}{
|
||||
{
|
||||
name: "find from project/roles/roleName",
|
||||
basePlaybook: filepath.Join("playbooks", "playbook1.yaml"),
|
||||
roleName: "role1",
|
||||
except: filepath.Join("roles", "role1"),
|
||||
},
|
||||
{
|
||||
name: "find from current_playbook/roles/roleName",
|
||||
basePlaybook: filepath.Join("playbooks", "playbook1.yaml"),
|
||||
roleName: "role2",
|
||||
except: filepath.Join("playbooks", "roles", "role2"),
|
||||
},
|
||||
{
|
||||
name: "cannot find",
|
||||
basePlaybook: filepath.Join("playbooks", "playbook1.yaml"),
|
||||
roleName: "role3",
|
||||
except: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.except, getRoleBaseFromPlaybook(os.DirFS("testdata"), tc.basePlaybook, tc.roleName))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetYamlFile(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
base string
|
||||
except string
|
||||
}{
|
||||
{
|
||||
name: "get yaml",
|
||||
base: filepath.Join("playbooks", "playbook2"),
|
||||
except: filepath.Join("playbooks", "playbook2.yaml"),
|
||||
},
|
||||
{
|
||||
name: "get yml",
|
||||
base: filepath.Join("playbooks", "playbook3"),
|
||||
except: filepath.Join("playbooks", "playbook3.yml"),
|
||||
},
|
||||
{
|
||||
name: "cannot find",
|
||||
base: filepath.Join("playbooks", "playbook4"),
|
||||
except: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.except, getYamlFile(os.DirFS("testdata"), tc.base))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalPlaybook(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
file string
|
||||
except *kkprojectv1.Playbook
|
||||
}{
|
||||
{
|
||||
name: "marshal playbook",
|
||||
file: "playbooks/playbook1.yaml",
|
||||
except: &kkprojectv1.Playbook{Play: []kkprojectv1.Play{
|
||||
{
|
||||
Base: kkprojectv1.Base{Name: "play1"},
|
||||
PlayHost: kkprojectv1.PlayHost{Hosts: []string{"localhost"}},
|
||||
Roles: []kkprojectv1.Role{
|
||||
{
|
||||
RoleInfo: kkprojectv1.RoleInfo{
|
||||
Role: "role1",
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{Base: kkprojectv1.Base{Name: "role1 | block1"}},
|
||||
Task: kkprojectv1.Task{UnknownField: map[string]any{
|
||||
"debug": map[string]any{
|
||||
"msg": "echo \"hello world\"",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Handlers: nil,
|
||||
PreTasks: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{Base: kkprojectv1.Base{Name: "play1 | pre_block1"}},
|
||||
Task: kkprojectv1.Task{UnknownField: map[string]any{
|
||||
"debug": map[string]any{
|
||||
"msg": "echo \"hello world\"",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
PostTasks: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{Base: kkprojectv1.Base{Name: "play1 | post_block1"}},
|
||||
Task: kkprojectv1.Task{UnknownField: map[string]any{
|
||||
"debug": map[string]any{
|
||||
"msg": "echo \"hello world\"",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
Tasks: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{Base: kkprojectv1.Base{Name: "play1 | block1"}},
|
||||
BlockInfo: kkprojectv1.BlockInfo{Block: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{Base: kkprojectv1.Base{Name: "play1 | block1 | block1"}},
|
||||
Task: kkprojectv1.Task{UnknownField: map[string]any{
|
||||
"debug": map[string]any{
|
||||
"msg": "echo \"hello world\"",
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{Base: kkprojectv1.Base{Name: "play1 | block1 | block2"}},
|
||||
Task: kkprojectv1.Task{UnknownField: map[string]any{
|
||||
"debug": map[string]any{
|
||||
"msg": "echo \"hello world\"",
|
||||
},
|
||||
}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{Base: kkprojectv1.Base{Name: "play1 | block2"}},
|
||||
Task: kkprojectv1.Task{UnknownField: map[string]any{
|
||||
"debug": map[string]any{
|
||||
"msg": "echo \"hello world\"",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Base: kkprojectv1.Base{Name: "play2"},
|
||||
PlayHost: kkprojectv1.PlayHost{Hosts: []string{"localhost"}},
|
||||
Tasks: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{Base: kkprojectv1.Base{Name: "play2 | block1"}},
|
||||
Task: kkprojectv1.Task{UnknownField: map[string]any{
|
||||
"debug": map[string]any{
|
||||
"msg": "echo \"hello world\"",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
pb, err := marshalPlaybook(os.DirFS("testdata"), tc.file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, tc.except, pb)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -17,15 +17,12 @@ limitations under the License.
|
|||
package project
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
kkcorev1 "github.com/kubesphere/kubekey/api/core/v1"
|
||||
kkprojectv1 "github.com/kubesphere/kubekey/api/project/v1"
|
||||
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
)
|
||||
|
||||
func newLocalProject(playbook kkcorev1.Playbook) (Project, error) {
|
||||
|
|
@ -43,84 +40,15 @@ func newLocalProject(playbook kkcorev1.Playbook) (Project, error) {
|
|||
if _, err := os.Stat(playbook.Spec.Playbook); err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot find playbook %q", playbook.Spec.Playbook)
|
||||
}
|
||||
|
||||
if filepath.Base(filepath.Dir(playbook.Spec.Playbook)) != _const.ProjectPlaybooksDir {
|
||||
// the format of playbook is not correct
|
||||
return nil, errors.New("playbook should be projectDir/playbooks/playbookfile")
|
||||
}
|
||||
|
||||
projectDir := filepath.Dir(filepath.Dir(playbook.Spec.Playbook))
|
||||
pb, err := filepath.Rel(projectDir, playbook.Spec.Playbook)
|
||||
projectPath := GetProjectPath(playbook.Spec.Playbook)
|
||||
relPath, err := filepath.Rel(projectPath, playbook.Spec.Playbook)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get rel path for playbook %q", playbook.Spec.Playbook)
|
||||
return nil, errors.Wrapf(err, "failed to get relative path for playbook %q", playbook.Spec.Playbook)
|
||||
}
|
||||
|
||||
return &localProject{Playbook: playbook, projectDir: projectDir, playbook: pb}, nil
|
||||
}
|
||||
|
||||
type localProject struct {
|
||||
kkcorev1.Playbook
|
||||
|
||||
projectDir string
|
||||
// playbook relpath base on projectDir
|
||||
playbook string
|
||||
}
|
||||
|
||||
func (p localProject) getFilePath(path string, o GetFileOption) string {
|
||||
if filepath.IsAbs(path) {
|
||||
return path
|
||||
}
|
||||
var find []string
|
||||
switch {
|
||||
case o.IsFile:
|
||||
if o.Role != "" {
|
||||
// find from project/roles/roleName
|
||||
find = append(find, filepath.Join(p.projectDir, _const.ProjectRolesDir, o.Role, _const.ProjectRolesFilesDir, path))
|
||||
// find from pbPath dir like: current_playbook/roles/roleName
|
||||
find = append(find, filepath.Join(p.projectDir, p.playbook, _const.ProjectRolesDir, o.Role, _const.ProjectRolesFilesDir, path))
|
||||
}
|
||||
find = append(find, filepath.Join(p.projectDir, _const.ProjectRolesFilesDir, path))
|
||||
case o.IsTemplate:
|
||||
// find from project/roles/roleName
|
||||
if o.Role != "" {
|
||||
find = append(find, filepath.Join(p.projectDir, _const.ProjectRolesDir, o.Role, _const.ProjectRolesTemplateDir, path))
|
||||
// find from pbPath dir like: current_playbook/roles/roleName
|
||||
find = append(find, filepath.Join(p.projectDir, p.playbook, _const.ProjectRolesDir, o.Role, _const.ProjectRolesTemplateDir, path))
|
||||
}
|
||||
find = append(find, filepath.Join(p.projectDir, _const.ProjectRolesTemplateDir, path))
|
||||
default:
|
||||
find = append(find, filepath.Join(p.projectDir, path))
|
||||
}
|
||||
for _, s := range find {
|
||||
if _, err := os.Stat(s); err == nil {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// MarshalPlaybook project file to playbook.
|
||||
func (p localProject) MarshalPlaybook() (*kkprojectv1.Playbook, error) {
|
||||
return marshalPlaybook(os.DirFS(p.projectDir), p.playbook)
|
||||
}
|
||||
|
||||
// Stat role/file/template file or dir in project
|
||||
func (p localProject) Stat(path string, option GetFileOption) (os.FileInfo, error) {
|
||||
return os.Stat(p.getFilePath(path, option))
|
||||
}
|
||||
|
||||
// WalkDir role/file/template dir in project
|
||||
func (p localProject) WalkDir(path string, option GetFileOption, f fs.WalkDirFunc) error {
|
||||
return filepath.WalkDir(p.getFilePath(path, option), f)
|
||||
}
|
||||
|
||||
// ReadFile role/file/template file or dir in project
|
||||
func (p localProject) ReadFile(path string, option GetFileOption) ([]byte, error) {
|
||||
return os.ReadFile(p.getFilePath(path, option))
|
||||
}
|
||||
|
||||
// Rel path for role/file/template file or dir in project
|
||||
func (p localProject) Rel(root string, path string, option GetFileOption) (string, error) {
|
||||
return filepath.Rel(p.getFilePath(root, option), path)
|
||||
return &project{
|
||||
FS: os.DirFS(projectPath),
|
||||
basePlaybook: relPath,
|
||||
Playbook: &kkprojectv1.Playbook{},
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
package project
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
)
|
||||
|
||||
const (
|
||||
// PathFormatImportPlaybook defines the directory structure for importing playbooks
|
||||
// The structure supports three formats:
|
||||
// 1. Import playbook in same directory as base playbook
|
||||
// 2. Import playbook from playbooks/ subdirectory
|
||||
// 3. Import playbook from project root playbooks/ directory
|
||||
PathFormatImportPlaybook = `
|
||||
|-- base-playbook.yaml
|
||||
|-- [import_playbook]
|
||||
|
||||
|-- playbook.yaml
|
||||
|-- playbooks/
|
||||
| |-- [import_playbook]
|
||||
|
||||
|-- [projectDir]
|
||||
|-- playbooks/
|
||||
| |-- [import_playbook]
|
||||
`
|
||||
|
||||
// PathFormatVarsFile defines the directory structure for vars files
|
||||
// Vars files are expected to be in the same directory as the base playbook
|
||||
PathFormatVarsFile = `
|
||||
|-- base-playbook.yaml
|
||||
|-- [VarsFile]
|
||||
`
|
||||
// PathFormatRole defines the directory structure for roles
|
||||
// Roles can be found in three locations:
|
||||
// 1. roles/ directory at project root
|
||||
// 2. roles/ directory next to playbook
|
||||
// 3. roles/ directory at project root
|
||||
PathFormatRole = `
|
||||
|-- playbooks/
|
||||
| |-- playbook.yaml
|
||||
|-- roles/
|
||||
| |-- [role]/
|
||||
|
||||
|-- playbook.yaml
|
||||
|-- roles/
|
||||
| |-- [role]/
|
||||
|
||||
|-- [projectDir]
|
||||
|-- roles/
|
||||
| |-- [role]/
|
||||
`
|
||||
|
||||
// PathFormatRoleTask defines the directory structure for role tasks
|
||||
// Role tasks are expected to be in tasks/main.yaml or tasks/main.yml under the role directory
|
||||
PathFormatRoleTask = `
|
||||
|-- baseRole/
|
||||
| |-- tasks/
|
||||
| | |-- main.yaml
|
||||
|
||||
|-- baseRole/
|
||||
| |-- tasks/
|
||||
| | |-- main.yml
|
||||
`
|
||||
|
||||
// PathFormatIncludeTask defines the directory structure for included tasks
|
||||
// Tasks can be included from:
|
||||
// 1. Direct path under source role
|
||||
// 2. tasks/ directory under source role
|
||||
// 3. Direct path under top level role
|
||||
// 4. tasks/ directory under top level role
|
||||
PathFormatIncludeTask = `
|
||||
|-- [source_task]
|
||||
| |-- tasks/
|
||||
| | |-- [include_tasks]
|
||||
|
||||
|-- [source_task]
|
||||
| |-- [include_tasks]
|
||||
|
||||
|-- [top_source_task]
|
||||
| |-- tasks/
|
||||
| | |-- [include_tasks]
|
||||
|
||||
|-- [top_source_task]
|
||||
| |-- [include_tasks]
|
||||
`
|
||||
)
|
||||
|
||||
// GetProjectPath from basePlaybook. the Project directory structure can be either:
|
||||
/*
|
||||
|-- projectFS/
|
||||
| |-- basePlaybook.yaml
|
||||
|
||||
|-- projectFS/
|
||||
| |-- playbooks/
|
||||
| | |-- basePlaybook.yaml
|
||||
*/
|
||||
func GetProjectPath(basePlaybook string) string {
|
||||
if filepath.Base(filepath.Dir(basePlaybook)) == _const.ProjectPlaybooksDir {
|
||||
return filepath.Dir(filepath.Dir(basePlaybook))
|
||||
}
|
||||
|
||||
return filepath.Dir(basePlaybook)
|
||||
}
|
||||
|
||||
// GetImportPlaybookRelPath returns possible relative paths for an imported playbook based on the base playbook location
|
||||
// The format follows PathFormatImportPlaybook structure
|
||||
func GetImportPlaybookRelPath(basePlaybook string, includePlaybook string) []string {
|
||||
return []string{
|
||||
filepath.Join(filepath.Dir(basePlaybook), includePlaybook),
|
||||
filepath.Join(filepath.Dir(basePlaybook), _const.ProjectPlaybooksDir, includePlaybook),
|
||||
// should support find playbooks from projectDir
|
||||
filepath.Join(_const.ProjectPlaybooksDir, includePlaybook),
|
||||
}
|
||||
}
|
||||
|
||||
// GetVarsFilesRelPath returns possible relative paths for vars files based on the base playbook location
|
||||
// The format follows PathFormatVarsFile structure
|
||||
func GetVarsFilesRelPath(basePlaybook string, varsFile string) []string {
|
||||
return []string{
|
||||
filepath.Join(filepath.Dir(basePlaybook), varsFile),
|
||||
}
|
||||
}
|
||||
|
||||
// GetRoleRelPath returns possible relative paths for a role based on the base playbook location
|
||||
// The format follows PathFormatRole structure
|
||||
func GetRoleRelPath(basePlaybook string, role string) []string {
|
||||
return []string{
|
||||
filepath.Join(filepath.Dir(filepath.Dir(basePlaybook)), _const.ProjectRolesDir, role),
|
||||
filepath.Join(filepath.Dir(basePlaybook), _const.ProjectRolesDir, role),
|
||||
filepath.Join(_const.ProjectRolesDir, role),
|
||||
}
|
||||
}
|
||||
|
||||
// GetRoleTaskRelPath returns possible relative paths for a role's main task file
|
||||
// The format follows PathFormatRoleTask structure
|
||||
func GetRoleTaskRelPath(baseRole string) []string {
|
||||
return []string{
|
||||
filepath.Join(baseRole, _const.ProjectRolesTasksDir, "main.yaml"),
|
||||
filepath.Join(baseRole, _const.ProjectRolesTasksDir, "main.yml"),
|
||||
}
|
||||
}
|
||||
|
||||
// GetRoleDefaultsRelPath returns possible relative paths for a role's defaults file
|
||||
// The format follows similar structure to role tasks
|
||||
func GetRoleDefaultsRelPath(baseRole string) []string {
|
||||
return []string{
|
||||
filepath.Join(baseRole, _const.ProjectRolesDefaultsDir, "main.yaml"),
|
||||
filepath.Join(baseRole, _const.ProjectRolesDefaultsDir, "main.yml"),
|
||||
}
|
||||
}
|
||||
|
||||
// GetIncludeTaskRelPath returns possible relative paths for included task files
|
||||
// The format follows PathFormatIncludeTask structure
|
||||
func GetIncludeTaskRelPath(top string, source string, includeTask string) []string {
|
||||
return []string{
|
||||
filepath.Join(source, includeTask),
|
||||
filepath.Join(source, _const.ProjectRolesTasksDir, includeTask),
|
||||
filepath.Join(top, includeTask),
|
||||
filepath.Join(top, _const.ProjectRolesTasksDir, includeTask),
|
||||
}
|
||||
}
|
||||
|
||||
// Standard project directory structure when playbook does not define a role and directly defines tasks:
|
||||
/*
|
||||
|-- projects_dir/
|
||||
| |-- project1/
|
||||
| | |-- playbook.yaml
|
||||
| | |-- files/
|
||||
| | |-- templates/
|
||||
|
||||
|-- projects_dir/
|
||||
| |-- project1/
|
||||
| | |-- playbooks/
|
||||
| | | |-- playbook.yaml
|
||||
| | | |-- files/
|
||||
| | | |-- templates/
|
||||
*/
|
||||
|
||||
// Standard roles directory structure:
|
||||
/*
|
||||
|-- roles/
|
||||
| |-- defaults/
|
||||
| |-- files/
|
||||
| |-- tasks/
|
||||
| |-- templates/
|
||||
*/
|
||||
|
|
@ -14,18 +14,28 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package project provides functionality for managing Ansible-like projects in KubeKey.
|
||||
// It handles project file operations, playbook parsing, and task execution.
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
kkcorev1 "github.com/kubesphere/kubekey/api/core/v1"
|
||||
kkcorev1alpha1 "github.com/kubesphere/kubekey/api/core/v1alpha1"
|
||||
kkprojectv1 "github.com/kubesphere/kubekey/api/project/v1"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
||||
)
|
||||
|
||||
// builtinProjectFunc is a function that creates a Project from a built-in playbook
|
||||
var builtinProjectFunc func(kkcorev1.Playbook) (Project, error)
|
||||
|
||||
// Project represent location of actual project.
|
||||
|
|
@ -34,26 +44,19 @@ type Project interface {
|
|||
// MarshalPlaybook project file to playbook.
|
||||
MarshalPlaybook() (*kkprojectv1.Playbook, error)
|
||||
// Stat file or dir in project
|
||||
Stat(path string, option GetFileOption) (os.FileInfo, error)
|
||||
Stat(path string) (os.FileInfo, error)
|
||||
// WalkDir dir in project
|
||||
WalkDir(path string, option GetFileOption, f fs.WalkDirFunc) error
|
||||
WalkDir(path string, f fs.WalkDirFunc) error
|
||||
// ReadFile file or dir in project
|
||||
ReadFile(path string, option GetFileOption) ([]byte, error)
|
||||
ReadFile(path string) ([]byte, error)
|
||||
// Rel path file or dir in project
|
||||
Rel(root string, path string, option GetFileOption) (string, error)
|
||||
Rel(root string, path string) (string, error)
|
||||
}
|
||||
|
||||
// GetFileOption for file.
|
||||
type GetFileOption struct {
|
||||
Role string
|
||||
IsTemplate bool
|
||||
IsFile bool
|
||||
}
|
||||
|
||||
// New project.
|
||||
// If project address is git format. newGitProject
|
||||
// If playbook has BuiltinsProjectAnnotation. builtinProjectFunc
|
||||
// Default newLocalProject
|
||||
// New creates a new Project instance based on the provided playbook.
|
||||
// If project address is git format, it creates a git project.
|
||||
// If playbook has BuiltinsProjectAnnotation, it uses builtinProjectFunc.
|
||||
// Otherwise, it creates a local project.
|
||||
func New(ctx context.Context, playbook kkcorev1.Playbook, update bool) (Project, error) {
|
||||
if strings.HasPrefix(playbook.Spec.Project.Addr, "https://") ||
|
||||
strings.HasPrefix(playbook.Spec.Project.Addr, "http://") ||
|
||||
|
|
@ -67,3 +70,241 @@ func New(ctx context.Context, playbook kkcorev1.Playbook, update bool) (Project,
|
|||
|
||||
return newLocalProject(playbook)
|
||||
}
|
||||
|
||||
// project implements the Project interface using an fs.FS
|
||||
type project struct {
|
||||
fs.FS
|
||||
|
||||
basePlaybook string
|
||||
|
||||
*kkprojectv1.Playbook
|
||||
}
|
||||
|
||||
// ReadFile reads and returns the contents of the file at the given path
|
||||
func (f *project) ReadFile(path string) ([]byte, error) {
|
||||
return fs.ReadFile(f.FS, path)
|
||||
}
|
||||
|
||||
// Rel returns a relative path that is lexically equivalent to targpath when joined to basepath
|
||||
func (f *project) Rel(root string, path string) (string, error) {
|
||||
return filepath.Rel(root, path)
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo for the file at the given path
|
||||
func (f *project) Stat(path string) (os.FileInfo, error) {
|
||||
return fs.Stat(f.FS, path)
|
||||
}
|
||||
|
||||
// WalkDir walks the file tree rooted at path, calling fn for each file or directory
|
||||
func (f *project) WalkDir(path string, fn fs.WalkDirFunc) error {
|
||||
return fs.WalkDir(f.FS, path, fn)
|
||||
}
|
||||
|
||||
// MarshalPlaybook converts a playbook file into a kkprojectv1.Playbook
|
||||
func (f *project) MarshalPlaybook() (*kkprojectv1.Playbook, error) {
|
||||
f.Playbook = &kkprojectv1.Playbook{}
|
||||
// convert playbook to kkprojectv1.Playbook
|
||||
if err := f.loadPlaybook(f.basePlaybook); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// convertIncludeTasks
|
||||
if err := f.convertIncludeTasks(f.basePlaybook); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// validate playbook
|
||||
if err := f.Playbook.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f.Playbook, nil
|
||||
}
|
||||
|
||||
// loadPlaybook loads a playbook and all its included playbooks into a single playbook
|
||||
func (f *project) loadPlaybook(basePlaybook string) error {
|
||||
// baseDir is the local ansible project dir which playbook belong to
|
||||
pbData, err := fs.ReadFile(f.FS, basePlaybook)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read playbook %q", basePlaybook)
|
||||
}
|
||||
var plays []kkprojectv1.Play
|
||||
if err := yaml.Unmarshal(pbData, &plays); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal playbook %q", basePlaybook)
|
||||
}
|
||||
|
||||
for _, p := range plays {
|
||||
if err := f.dealImportPlaybook(p, basePlaybook); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.dealVarsFiles(&p, basePlaybook); err != nil {
|
||||
return err
|
||||
}
|
||||
// fill block in roles
|
||||
if err := f.dealRoles(p, basePlaybook); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Playbook.Play = append(f.Playbook.Play, p)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dealImportPlaybook handles the "import_playbook" argument in a play
|
||||
func (f *project) dealImportPlaybook(p kkprojectv1.Play, basePlaybook string) error {
|
||||
if p.ImportPlaybook != "" {
|
||||
importPlaybook := f.getPath(GetImportPlaybookRelPath(basePlaybook, p.ImportPlaybook))
|
||||
if importPlaybook == "" {
|
||||
return errors.Errorf("failed to find import_playbook %q base on %q. it's should be:\n %s", p.ImportPlaybook, basePlaybook, PathFormatImportPlaybook)
|
||||
}
|
||||
if err := f.loadPlaybook(importPlaybook); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dealVarsFiles handles the "var_files" argument in a play
|
||||
func (f *project) dealVarsFiles(p *kkprojectv1.Play, basePlaybook string) error {
|
||||
for _, varsFile := range p.VarsFiles {
|
||||
// load vars from vars_files
|
||||
file := f.getPath(GetVarsFilesRelPath(basePlaybook, varsFile))
|
||||
if file == "" {
|
||||
return errors.Errorf("failed to find vars_files %q base on %q. it's should be:\n %s", varsFile, basePlaybook, PathFormatVarsFile)
|
||||
}
|
||||
|
||||
data, err := fs.ReadFile(f.FS, file)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read file %q", file)
|
||||
}
|
||||
var newVars map[string]any
|
||||
// Unmarshal the YAML document into a root node.
|
||||
if err := yaml.Unmarshal(data, &newVars); err != nil {
|
||||
return errors.Wrap(err, "failed to failed to unmarshal YAML")
|
||||
}
|
||||
// store vars in play. the vars defined in file should not be repeated.
|
||||
p.Vars = variable.CombineVariables(newVars, p.Vars)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dealRoles handles the "roles" argument in a play
|
||||
func (f *project) dealRoles(p kkprojectv1.Play, basePlaybook string) error {
|
||||
for i, r := range p.Roles {
|
||||
baseRole := f.getPath(GetRoleRelPath(basePlaybook, r.Role))
|
||||
if baseRole == "" {
|
||||
return errors.Errorf("failed to find role %q base on %q. it's should be:\n %s", r.Role, basePlaybook, PathFormatRole)
|
||||
}
|
||||
// deal tasks
|
||||
task := f.getPath(GetRoleTaskRelPath(baseRole))
|
||||
if task == "" {
|
||||
return errors.Errorf("cannot found main task for Role %q. it's should be: \n %s", r.Role, PathFormatRoleTask)
|
||||
}
|
||||
rdata, err := fs.ReadFile(f.FS, task)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read file %q", task)
|
||||
}
|
||||
var blocks []kkprojectv1.Block
|
||||
if err := yaml.Unmarshal(rdata, &blocks); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal yaml file %q", task)
|
||||
}
|
||||
p.Roles[i].Block = blocks
|
||||
// deal defaults (optional)
|
||||
if defaults := f.getPath(GetRoleDefaultsRelPath(baseRole)); defaults != "" {
|
||||
data, err := fs.ReadFile(f.FS, defaults)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read defaults variable file %q", defaults)
|
||||
}
|
||||
|
||||
var newVars map[string]any
|
||||
// Unmarshal the YAML document into a root node.
|
||||
if err := yaml.Unmarshal(data, &newVars); err != nil {
|
||||
return errors.Wrap(err, "failed to unmarshal YAML")
|
||||
}
|
||||
// store vars in play. the vars defined in file should not be repeated.
|
||||
p.Roles[i].Vars = variable.CombineVariables(newVars, p.Roles[i].Vars)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertIncludeTasks converts tasks from files into blocks
|
||||
func (f *project) convertIncludeTasks(basePlaybook string) error {
|
||||
for _, play := range f.Playbook.Play {
|
||||
if err := f.fileToBlock(filepath.Dir(basePlaybook), filepath.Dir(basePlaybook), play.PreTasks); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.fileToBlock(filepath.Dir(basePlaybook), filepath.Dir(basePlaybook), play.Tasks); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.fileToBlock(filepath.Dir(basePlaybook), filepath.Dir(basePlaybook), play.PostTasks); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range play.Roles {
|
||||
baseRole := f.getPath(GetRoleRelPath(basePlaybook, r.Role))
|
||||
if baseRole == "" {
|
||||
return errors.Errorf("failed to find role %q base on %q. it's should be:\n %s", r.Role, basePlaybook, PathFormatRole)
|
||||
}
|
||||
if err := f.fileToBlock(baseRole, filepath.Join(baseRole, _const.ProjectRolesTasksDir), r.Block); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fileToBlock converts task files into blocks, handling include_tasks directives
|
||||
func (f *project) fileToBlock(top string, source string, blocks []kkprojectv1.Block) error {
|
||||
for i, block := range blocks {
|
||||
switch {
|
||||
case len(block.Block) != 0: // it blocks
|
||||
if err := f.fileToBlock(top, source, block.Block); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.fileToBlock(top, source, block.Rescue); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.fileToBlock(top, source, block.Always); err != nil {
|
||||
return err
|
||||
}
|
||||
case block.IncludeTasks != "": // it's include_tasks
|
||||
includeTask := f.getPath(GetIncludeTaskRelPath(top, source, block.IncludeTasks))
|
||||
if includeTask == "" {
|
||||
return errors.Errorf("failed to find include_task %q base on %q. it's should be:\n %s", block.IncludeTasks, source, PathFormatIncludeTask)
|
||||
}
|
||||
data, err := fs.ReadFile(f.FS, includeTask)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read includeTask file %q", includeTask)
|
||||
}
|
||||
var includeBlocks []kkprojectv1.Block
|
||||
if err := yaml.Unmarshal(data, &includeBlocks); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal includeTask file %q", includeTask)
|
||||
}
|
||||
if err := f.fileToBlock(top, filepath.Dir(includeTask), includeBlocks); err != nil {
|
||||
return err
|
||||
}
|
||||
blocks[i].Block = includeBlocks
|
||||
default: // it tasks
|
||||
blocks[i].UnknownField["annotations"] = map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: top,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPath returns the first valid path from a list of possible paths
|
||||
func (f *project) getPath(paths []string) string {
|
||||
for _, path := range paths {
|
||||
if _, err := fs.Stat(f.FS, path); err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,313 @@
|
|||
package project
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
kkcorev1 "github.com/kubesphere/kubekey/api/core/v1"
|
||||
kkcorev1alpha1 "github.com/kubesphere/kubekey/api/core/v1alpha1"
|
||||
kkprojectv1 "github.com/kubesphere/kubekey/api/project/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMarshalPlaybook(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
playbook kkcorev1.Playbook
|
||||
except *kkprojectv1.Playbook
|
||||
}{
|
||||
{
|
||||
name: "test_playbook1",
|
||||
playbook: kkcorev1.Playbook{
|
||||
Spec: kkcorev1.PlaybookSpec{
|
||||
Playbook: "testdata/playbook1.yaml",
|
||||
},
|
||||
},
|
||||
except: &kkprojectv1.Playbook{
|
||||
Play: []kkprojectv1.Play{
|
||||
{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "playbook1",
|
||||
},
|
||||
PlayHost: kkprojectv1.PlayHost{
|
||||
Hosts: []string{"node1"},
|
||||
},
|
||||
PreTasks: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "task1",
|
||||
},
|
||||
},
|
||||
Task: kkprojectv1.Task{
|
||||
UnknownField: map[string]any{
|
||||
"annotations": map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: ".",
|
||||
},
|
||||
"debug": map[string]any{
|
||||
"msg": "im task1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_playbook2",
|
||||
playbook: kkcorev1.Playbook{
|
||||
Spec: kkcorev1.PlaybookSpec{
|
||||
Playbook: "testdata/playbook2.yaml",
|
||||
},
|
||||
},
|
||||
except: &kkprojectv1.Playbook{
|
||||
Play: []kkprojectv1.Play{
|
||||
{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "playbook2",
|
||||
},
|
||||
PlayHost: kkprojectv1.PlayHost{
|
||||
Hosts: []string{"node1"},
|
||||
},
|
||||
Roles: []kkprojectv1.Role{
|
||||
{
|
||||
RoleInfo: kkprojectv1.RoleInfo{
|
||||
Role: "role1",
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "task1",
|
||||
},
|
||||
},
|
||||
Task: kkprojectv1.Task{
|
||||
UnknownField: map[string]any{
|
||||
"annotations": map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: "roles/role1",
|
||||
},
|
||||
"debug": map[string]any{
|
||||
"msg": "im task1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_playbook3",
|
||||
playbook: kkcorev1.Playbook{
|
||||
Spec: kkcorev1.PlaybookSpec{
|
||||
Playbook: "testdata/playbook3.yaml",
|
||||
},
|
||||
},
|
||||
except: &kkprojectv1.Playbook{
|
||||
Play: []kkprojectv1.Play{
|
||||
{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "playbook3",
|
||||
},
|
||||
PlayHost: kkprojectv1.PlayHost{
|
||||
Hosts: []string{"node1"},
|
||||
},
|
||||
Tasks: []kkprojectv1.Block{
|
||||
{
|
||||
IncludeTasks: "include_task1.yaml",
|
||||
BlockInfo: kkprojectv1.BlockInfo{
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "task1",
|
||||
},
|
||||
},
|
||||
Task: kkprojectv1.Task{
|
||||
UnknownField: map[string]any{
|
||||
"annotations": map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: ".",
|
||||
},
|
||||
"debug": map[string]any{
|
||||
"msg": "im task1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
IncludeTasks: "include_task1_1.yaml",
|
||||
BlockInfo: kkprojectv1.BlockInfo{
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "task2",
|
||||
},
|
||||
},
|
||||
Task: kkprojectv1.Task{
|
||||
UnknownField: map[string]any{
|
||||
"annotations": map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: ".",
|
||||
},
|
||||
"debug": map[string]any{
|
||||
"msg": "im task2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
IncludeTasks: "include_task1/include_task1_2.yaml",
|
||||
BlockInfo: kkprojectv1.BlockInfo{
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "task3",
|
||||
},
|
||||
},
|
||||
Task: kkprojectv1.Task{
|
||||
UnknownField: map[string]any{
|
||||
"annotations": map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: ".",
|
||||
},
|
||||
"debug": map[string]any{
|
||||
"msg": "im task3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_playbook4",
|
||||
playbook: kkcorev1.Playbook{
|
||||
Spec: kkcorev1.PlaybookSpec{
|
||||
Playbook: "testdata/playbooks/playbook4.yaml",
|
||||
},
|
||||
},
|
||||
except: &kkprojectv1.Playbook{
|
||||
Play: []kkprojectv1.Play{
|
||||
{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "playbook4_1",
|
||||
},
|
||||
PlayHost: kkprojectv1.PlayHost{
|
||||
Hosts: []string{"node1"},
|
||||
},
|
||||
Roles: []kkprojectv1.Role{
|
||||
{
|
||||
RoleInfo: kkprojectv1.RoleInfo{
|
||||
Role: "role2",
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
IncludeTasks: "include_task1/include_task1.yaml",
|
||||
BlockInfo: kkprojectv1.BlockInfo{
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
IncludeTasks: "include_task2.yaml",
|
||||
BlockInfo: kkprojectv1.BlockInfo{
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "task2",
|
||||
},
|
||||
},
|
||||
Task: kkprojectv1.Task{
|
||||
UnknownField: map[string]any{
|
||||
"annotations": map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: "roles/role2",
|
||||
},
|
||||
"debug": map[string]any{
|
||||
"msg": "im task2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
IncludeTasks: "include_task3.yaml",
|
||||
BlockInfo: kkprojectv1.BlockInfo{
|
||||
Block: []kkprojectv1.Block{
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "task3",
|
||||
},
|
||||
},
|
||||
Task: kkprojectv1.Task{
|
||||
UnknownField: map[string]any{
|
||||
"annotations": map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: "roles/role2",
|
||||
},
|
||||
"debug": map[string]any{
|
||||
"msg": "im task3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
BlockBase: kkprojectv1.BlockBase{
|
||||
Base: kkprojectv1.Base{
|
||||
Name: "task1",
|
||||
},
|
||||
},
|
||||
Task: kkprojectv1.Task{
|
||||
UnknownField: map[string]any{
|
||||
"annotations": map[string]string{
|
||||
kkcorev1alpha1.TaskAnnotationRelativePath: "roles/role2",
|
||||
},
|
||||
"debug": map[string]any{
|
||||
"msg": "im task1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
project, err := newLocalProject(tc.playbook)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual, err := project.MarshalPlaybook()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, tc.except, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
- name: playbook1
|
||||
hosts:
|
||||
- node1
|
||||
pre_tasks:
|
||||
- name: task1
|
||||
debug:
|
||||
msg: "im task1"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- name: playbook2
|
||||
hosts:
|
||||
- node1
|
||||
roles:
|
||||
- role1
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
- name: playbook3
|
||||
hosts:
|
||||
- node1
|
||||
tasks:
|
||||
- include_tasks: include_task1.yaml
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- name: playbook4_1
|
||||
hosts:
|
||||
- node1
|
||||
roles:
|
||||
- role2
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
- name: play1
|
||||
hosts: localhost
|
||||
pre_tasks:
|
||||
- name: play1 | pre_block1
|
||||
debug:
|
||||
msg: echo "hello world"
|
||||
tasks:
|
||||
- name: play1 | block1
|
||||
block:
|
||||
- name: play1 | block1 | block1
|
||||
debug:
|
||||
msg: echo "hello world"
|
||||
- name: play1 | block1 | block2
|
||||
debug:
|
||||
msg: echo "hello world"
|
||||
- name: play1 | block2
|
||||
debug:
|
||||
msg: echo "hello world"
|
||||
post_tasks:
|
||||
- name: play1 | post_block1
|
||||
debug:
|
||||
msg: echo "hello world"
|
||||
roles:
|
||||
- role1
|
||||
- name: play2
|
||||
hosts: localhost
|
||||
tasks:
|
||||
- name: play2 | block1
|
||||
debug:
|
||||
msg: echo "hello world"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
- import_playbook: import_playbook1/import_playbook1.yaml
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
- name: role1 | block1
|
||||
debug:
|
||||
msg: echo "hello world"
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
- name: role1 | block1
|
||||
---
|
||||
- name: task1
|
||||
debug:
|
||||
msg: echo "hello world"
|
||||
msg: "im task1"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
- include_tasks: include_task2.yaml
|
||||
|
||||
- include_tasks: include_task3.yaml
|
||||
|
||||
- name: task1
|
||||
debug:
|
||||
msg: "im task1"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
- name: task2
|
||||
debug:
|
||||
msg: "im task2"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
- name: task3
|
||||
debug:
|
||||
msg: "im task3"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
- include_tasks: include_task1/include_task1.yaml
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
- name: task1
|
||||
debug:
|
||||
msg: "im task1"
|
||||
|
||||
- include_tasks: include_task1_1.yaml
|
||||
|
||||
- include_tasks: include_task1/include_task1_2.yaml
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
- name: task3
|
||||
debug:
|
||||
msg: "im task3"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
- name: task2
|
||||
debug:
|
||||
msg: "im task2"
|
||||
|
|
@ -53,11 +53,12 @@ import (
|
|||
|
||||
// RestConfig replace the restconfig transport to proxy transport
|
||||
func RestConfig(runtimedir string, restconfig *rest.Config) error {
|
||||
restconfig.TLSClientConfig = rest.TLSClientConfig{}
|
||||
transport, err := newProxyTransport(runtimedir, restconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
restconfig.TLSClientConfig = rest.TLSClientConfig{}
|
||||
|
||||
restconfig.Transport = transport
|
||||
|
||||
return nil
|
||||
|
|
|
|||
Loading…
Reference in New Issue