Merge branch 'development' into gendersTemplate

This commit is contained in:
Christian Goll
2023-09-06 14:08:09 +02:00
committed by GitHub
100 changed files with 5211 additions and 2106 deletions

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go: [ '1.17', '1.18', '1.19' ]
go: [ '1.18', '1.19', '1.20' ]
steps:
- uses: actions/checkout@v3
@@ -41,4 +41,4 @@ jobs:
run: make vet
- name: test
run: make test-it
run: make test

20
.gitignore vendored
View File

@@ -5,38 +5,28 @@
/vendor
# binaries
/warewulfd
/wwbuild
/wwclient
/wwctl
/bash_completion
/man_page
/config_defaults
/update_configuration
/wwapic
/wwapid
/wwapird
/print_defaults
# other created files
/man_pages
/bash_completion.d
/usr/share/man/man1/
/docs/man/man1
/docs/man/man5/*.5.gz
/etc/bash_completion.d/
warewulf.spec
internal/pkg/buildconfig/setconfigs.go
internal/pkg/config/buildconfig.go
include/systemd/warewulfd.service
_dist/
/config
warewulf-*.tar.gz
Defaults.mk
/etc/wwapid.config
/etc/wwapic.config
/etc/wwapird.config
/etc/wwapid.conf
/etc/wwapic.conf
/etc/wwapird.conf
/etc/defaults.conf
.dist/
userdocs/_*
userdocs/reference/*
/warewulf-?.?.?/
*.coverprofile

1
API.md
View File

@@ -19,6 +19,7 @@ wwctl container delete
wwctl container import
wwctl container list
wwctl container show
wwctl container copy
Some notes on the files:

View File

@@ -7,8 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Fixed
### Added
- New documentation for the hostlist syntax. #611
- New documentation for development environment (Vagrant)
- Ability to duplicate an image with `wwctl container copy` or the API
- New documentation for container duplication procedure
### Fixed
- Fix hard CPU architecture on proto's installation in the Makefile
- More aggressive `make clean`.
- Replace deprecated `io.utils` functions with new `os` functions.
- The correct header is now displayed when `-al` flags are specified to overlay
list.
- Added a missing `.ww` extension to the `70-ww4-netname.rules` template in the
@@ -18,24 +27,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add support for resolving absolute path automatically. #493
- The network device "OnBoot" parameter correctly configures the ONBOOT ifcfg
parameter. (#644)
- Add support for listing profile/node via comma-separated values. #739
- Sort the node list returned entries by name.
- 'wwctl node edit' inconsistent state with warewulfd. #691
- Add `--parents` option to `overlay import` subcommand to create necessary
parent folder. #608
- Fix kernelargs are not printing properly in node list output. #828
- Fix build configuration on Quickstart guide #847
- Add Quickstart guide for EL9
- Add EL9 Quickstart guide to index.rst
- Container file gids are now updated properly during syncuser. #840
- Fix build for API.
### Changed
- The primary hostname and warewulf server fqdn are now the canonical name in
`/etc/hosts`
- new subcommand `wwctl genconf` is available with following subcommands:
* `completions` which will create the files used for bash-completion. Also
fish an zsh completions can be generated
* `defaults` which will generate a valid `defaults.conf`
* `man` which will generate the man pages in the specified directory
* `reference` which will generate a reference documentation for the wwctl commands
* `warwulfconf print` which will print the used `warewulf.conf`. If there is no valid
`warewulf.conf` a valid configuration is provided, prefilled with default values
and an IP configuration derived from the network configuration of the host
- All paths can now be configured in `warewulf.conf`, check the paths section of of
`wwctl --emptyconf genconfig warewulfconf print` for the available paths.
- Added experimental dnsmasq support.
- Refactored `profile add` command to make it alike `node add`. #658 #659
- The ifcfg ONBOOT parameter is no longer statically `true`, so unconfigured
interfaces may not be enabled by default. (#644)
@@ -48,25 +58,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `warwulfconf print` which will print the used `warewulf.conf`. If there is no valid
`warewulf.conf` a valid configuration is provided, prefilled with default values
and an IP configuration derived from the network configuration of the host
- All paths can now be configured in `warewulf.conf`, check the paths section of of
`wwctl --emptyconf genconfig warewulfconf print` for the available paths.
- Added experimental dnsmasq support.
- new subcommand `wwctl genconf` is available with following subcommands:
* `completions` which will create the files used for bash-completion. Also
fish an zsh completions can be generated
* `defaults` which will generate a valid `defaults.conf`
* `man` which will generate the man pages in the specified directory
* `reference` which will generate a reference documentation for the wwctl commands
* `warwulfconf print` which will print the used `warewulf.conf`. If there is no valid
`warewulf.conf` a valid configuration is provided, prefilled with default values
and an IP configuration derived from the network configuration of the host
- All paths can now be configured in `warewulf.conf`, check the paths section of of
`wwctl --emptyconf genconfig warewulfconf print` for the available paths.
- Added experimental dnsmasq support.
- Check for formal correct IP and MAC addresses for command line options and
when reading in the configurations
- Added template to create genders database
- Write log messages to stderr rather than stdout. #768
- Updates to Makefile for clarity, notably removing genconfig and replacing
test-it with test. #890
- realy reboot also without systemd
- Specify primary network device per-node rather than per-netdev
- refactored output `wwctl node/profile list` so that `-a` will only show all the
set values and `-A` will show all fields included the ones without a set value
- Added support for file systems, partitions and disks. Values for these objects can
be set with `wwctl profile/node set/add`. The format of this objects is inspired by
butane/ignition, but where butane/ignition uses lists for holding disks, partitions
and file systems, warewulf uses maps instead. For disks the map key is the underlying
block device, for partitions its the partition label and for file systems its the path
to the partitions (e.g. `/dev/disk/by-partlabel/scratch`).
Not all available options of butane/ignition are exposed to the commandline, but are
available via `wwctl node/profile edit`.
- Added the template function `{{ createIgnitionJson }}` which will create a json object
compatible with ignition.
- Container images need ignition and sgdisk installed in order to the disk management.
- Added boootup services based on ignition which will manage the disks, partitions and file
systems. The services are systemd services as sgdisk needs systemd in order to work
correctly. All service use the existence of `/warewulf/ignition.json` as perquisite so
that they can be place in the `wwinit` overlay and will only become active if disk management
is configured for this node. The service `ignition-disks-ww4.service` will partition and
format and create the file systems on the disks. For every a file system a systemd mount unit
file is create and will executed after the `ignition-disks-ww4.service` has finished.
Entries in `/etc/fstab` for every file system are created with the `noauto` option.
- wwclient has now a commandline switch for the location of warewulf.conf
## [4.4.0] 2023-01-18

View File

@@ -5,8 +5,8 @@
* Jeremy Siadal <jeremy.c.siadal@intel.com> @jcsiadal
* Niko Kivel <niko.kivel@psi.ch> @kivel
* Shannon V. Davidson <svdavidson@charter.net>
* Brian Clemens <brian@ctrliq.com> @brianclemens
* Michael L. Young <myoung@ciq.co> @ajyounge
* Brian Clemens <brian@ciq.com> @brianclemens
* Michael L. Young <myoung@ciq.com> @ajyounge
* Ian Kaneshiro <iankane@umich.edu> @ikaneshiro
* Carter Dodd <cdodd@nanohmics.com> @kcdodd
* MatthewHink <matthew_hink@yahoo.com> @MatthewHink
@@ -22,7 +22,8 @@
* David McFarlane @Prepultrue
* Jean-Baptiste Denis <jbd@jbdenis.net>
* TerranWorks <tdunn3@uco.edu>
* Jonathon Anderson <janderson@ciq.co> @anderbubble
* Brian Phan <bphan@ciq.co>
* Jonathon Anderson <janderson@ciq.com> @anderbubble
* Brian Phan <bphan@ciq.com>
* Jeffrey Frey @jtfrey
* Xu Yang(Jason Yang) <jasonyangshadow@gmail.com> @JasonYangShadow
* Arnaud LECOMTE <contact@arnaud-lcm.com>

View File

@@ -7,8 +7,7 @@ RUN zypper -n install --no-recommends git go1.18 libgpgme-devel &&\
COPY . /warewulf-src
RUN cd /warewulf-src &&\
make contclean &&\
make genconfig \
make contclean Defaults.mk \
PREFIX=/usr \
BINDIRa=/usr/bin \
SYSCONFDIR=/etc \

View File

@@ -1,13 +1,36 @@
Copyright (c) 2001-2020, Gregory M. Kurtzer. All rights reserved.
Copyright (c) 2003-2011, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved.. All rights reserved.
Copyright (c) 2003-2011, The Regents of the University of California,
through Lawrence Berkeley National Laboratory (subject to receipt of
any required approvals from the U.S. Dept. of Energy). All rights
reserved.. All rights reserved.
Copyright (c) 2020, Ctrl IQ, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

334
Makefile
View File

@@ -1,175 +1,88 @@
.PHONY: all clean contclean
include Variables.mk
-include Defaults.mk
.PHONY: all
all: config vendor wwctl wwclient man_pages wwapid wwapic wwapird etc/defaults.conf etc/bash_completion.d/wwctl
# Linux distro (try and set to /etc/os-release ID)
OS_REL := $(shell sed -n "s/^ID\s*=\s*['"\""]\(.*\)['"\""]/\1/p" /etc/os-release)
OS ?= $(OS_REL)
.PHONY: build
build: lint test vet all
# List of variables to save and replace in files
VARLIST := OS
.PHONY: setup_tools
setup_tools: $(GO_TOOLS_BIN) $(GOLANGCI_LINT) $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC)
# Project Information
VARLIST += WAREWULF VERSION RELEASE
WAREWULF ?= warewulf
VERSION ?= 4.4.0
GIT_TAG := $(shell test -e .git && git log -1 --format="%h")
$(GO_TOOLS_BIN):
GOBIN="$(PWD)/$(TOOLS_BIN)" go install -mod=vendor $(GO_TOOLS)
ifdef GIT_TAG
ifdef $(filter $(OS),ubuntu debian)
RELEASE ?= 1.git_$(subst -,+,$(GIT_TAG))
else
RELEASE ?= 1.git_$(subst -,_,$(GIT_TAG))
endif
else
RELEASE ?= 1
endif
# Use LSB-compliant paths if OS is known
ifneq ($(OS),)
USE_LSB_PATHS := true
endif
# Always default to GNU autotools default paths if PREFIX has been redefined
ifdef PREFIX
USE_LSB_PATHS := false
endif
# System directory paths
VARLIST += PREFIX BINDIR SYSCONFDIR SRVDIR DATADIR MANDIR DOCDIR LOCALSTATEDIR
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
SYSCONFDIR ?= $(PREFIX)/etc
DATADIR ?= $(PREFIX)/share
MANDIR ?= $(DATADIR)/man
DOCDIR ?= $(DATADIR)/doc
ifeq ($(USE_LSB_PATHS),true)
SRVDIR ?= /srv
LOCALSTATEDIR ?= /var/local
else
SRVDIR ?= $(PREFIX)/srv
LOCALSTATEDIR ?= $(PREFIX)/var
endif
# OS-Specific Service Locations
VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR
SYSTEMDDIR ?= /usr/lib/systemd/system
BASHCOMPDIR ?= /etc/bash_completion.d
FIREWALLDDIR ?= /usr/lib/firewalld/services
ifeq ($(OS),suse)
TFTPDIR ?= /srv/tftpboot
endif
ifeq ($(OS),ubuntu)
TFTPDIR ?= /srv/tftp
endif
# Default to Red Hat / Rocky Linux
TFTPDIR ?= /var/lib/tftpboot
# Warewulf directory paths
VARLIST += WWCLIENTDIR WWCONFIGDIR WWPROVISIONDIR WWOVERLAYDIR WWCHROOTDIR WWTFTPDIR WWDOCDIR WWDATADIR
WWCONFIGDIR := $(SYSCONFDIR)/$(WAREWULF)
WWPROVISIONDIR := $(SRVDIR)/$(WAREWULF)
WWOVERLAYDIR := $(LOCALSTATEDIR)/$(WAREWULF)/overlays
WWCHROOTDIR := $(LOCALSTATEDIR)/$(WAREWULF)/chroots
WWTFTPDIR := $(TFTPDIR)/$(WAREWULF)
WWDOCDIR := $(DOCDIR)/$(WAREWULF)
WWDATADIR := $(DATADIR)/$(WAREWULF)
WWCLIENTDIR ?= /warewulf
# auto installed tooling
TOOLS_DIR := .tools
TOOLS_BIN := $(TOOLS_DIR)/bin
CONFIG := $(shell pwd)
# tools
GO_TOOLS_BIN := $(addprefix $(TOOLS_BIN)/, $(notdir $(GO_TOOLS)))
GO_TOOLS_VENDOR := $(addprefix vendor/, $(GO_TOOLS))
GOLANGCI_LINT := $(TOOLS_BIN)/golangci-lint
GOLANGCI_LINT_VERSION := v1.50.0
# helper functions
godeps=$(shell go list -deps -f '{{if not .Standard}}{{ $$dep := . }}{{range .GoFiles}}{{$$dep.Dir}}/{{.}} {{end}}{{end}}' $(1) | sed "s%${PWD}/%%g")
WWCTL_DEPS:=$(call godeps,cmd/wwctl/main.go)
WWCLIENT_DEPS:=$(call godeps,cmd/wwclient/main.go)
# use GOPROXY for older git clients and speed up downloads
GOPROXY ?= https://proxy.golang.org
export GOPROXY
# built tags needed for wwbuild binary
WW_GO_BUILD_TAGS := containers_image_openpgp containers_image_ostree
# Default target
all: config vendor wwctl wwclient man_pages wwapid wwapic wwapird
# Validate source and build all packages
build: lint test-it vet all
# set the go tools into the tools bin.
setup_tools: $(GO_TOOLS_BIN) $(GOLANGCI_LINT)
# install go tools into TOOLS_BIN
$(GO_TOOLS_BIN):
@GOBIN="$(PWD)/$(TOOLS_BIN)" go install -mod=vendor $(GO_TOOLS)
# install golangci-lint into TOOLS_BIN
$(GOLANGCI_LINT):
@curl -qq -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TOOLS_BIN) $(GOLANGCI_LINT_VERSION)
curl -qq -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TOOLS_BIN) $(GOLANGCI_LINT_VERSION)
$(PROTOC):
cd $(PWD)/$(TOOLS_DIR) && curl -LO $(PROTOC_URL) && unzip protoc-24.0-linux-$(ARCHITECTURE_CPU).zip
$(PROTOC_GEN_GRPC_GATEWAY):
curl -L $(PROTOC_GEN_GRPC_GATEWAY_URL) -o $(PROTOC_GEN_GRPC_GATEWAY)
chmod +x $(PROTOC_GEN_GRPC_GATEWAY)
$(PROTOC_GEN_GO):
GOBIN="$(PWD)/$(TOOLS_BIN)" go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$(PROTOC_GEN_GO_GRPC):
GOBIN="$(PWD)/$(TOOLS_BIN)" go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
.PHONY: setup
setup: vendor $(TOOLS_DIR) setup_tools
vendor:
ifndef OFFLINE_BUILD
go mod tidy -v
go mod vendor
go mod tidy -v
go mod vendor
endif
$(TOOLS_DIR):
@mkdir -p $@
mkdir -p $@
# Pre-build steps for source, such as "go generate"
config:
# Store configuration for subsequent runs
printf " $(foreach V,$(VARLIST),$V := $(strip $($V))\n)" > Defaults.mk
# Global variable search and replace for all *.in files
find . -type f -name "*.in" -not -path "./vendor/*" \
-exec sh -c 'sed -ne "$(foreach V,$(VARLIST),s,@$V@,$(strip $($V)),g;)p" $${0} > $${0%.in}' {} \;
touch config
.PHONY: config
config: etc/wwapic.conf \
etc/wwapid.conf \
etc/wwapird.conf \
include/systemd/warewulfd.service \
internal/pkg/config/buildconfig.go \
warewulf.spec
rm_config:
rm -f config
etc/defaults.conf: wwctl
./wwctl --emptyconf genconfig defaults >etc/defaults.conf
genconfig: rm_config config
%: %.in
sed -ne "$(foreach V,$(VARLIST),s,@$V@,$(strip $($V)),g;)p" $@.in >$@
# Lint
.PHONY: lint
lint: setup_tools
@echo Running golangci-lint...
@$(GOLANGCI_LINT) run --build-tags "$(WW_GO_BUILD_TAGS)" --skip-dirs internal/pkg/staticfiles ./...
$(GOLANGCI_LINT) run --build-tags "$(WW_GO_BUILD_TAGS)" --skip-dirs internal/pkg/staticfiles ./...
vet:
.PHONY: vet
vet: config
go vet ./...
test-it:
go test -v ./...
.PHONY: test
test: config
go test ./...
# Generate test coverage
test-cover: ## Run test coverage and generate html report
rm -fr coverage
.PHONY: test-cover
test-cover: config
rm -rf coverage
mkdir coverage
go list -f '{{if gt (len .TestGoFiles) 0}}"go test -covermode count -coverprofile {{.Name}}.coverprofile -coverpkg ./... {{.ImportPath}}"{{end}}' ./... | xargs -I {} bash -c {}
echo "mode: count" > coverage/cover.out
grep -h -v "^mode:" *.coverprofile >> "coverage/cover.out"
echo "mode: count" >coverage/cover.out
grep -h -v "^mode:" *.coverprofile >>"coverage/cover.out"
rm *.coverprofile
go tool cover -html=coverage/cover.out -o=coverage/cover.html
debian: all
files: all
.PHONY: install
install: all
install -d -m 0755 $(DESTDIR)$(BINDIR)
install -d -m 0755 $(DESTDIR)$(WWCHROOTDIR)
install -d -m 0755 $(DESTDIR)$(WWPROVISIONDIR)
install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR)
install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples
install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe
install -d -m 0755 $(DESTDIR)$(BASHCOMPDIR)
install -d -m 0755 $(DESTDIR)$(MANDIR)/man1
@@ -178,23 +91,22 @@ files: all
install -d -m 0755 $(DESTDIR)$(FIREWALLDDIR)
install -d -m 0755 $(DESTDIR)$(SYSTEMDDIR)
install -d -m 0755 $(DESTDIR)$(WWDATADIR)/ipxe
test -f $(DESTDIR)$(WWCONFIGDIR)/warewulf.conf || install -m 644 etc/warewulf.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(WWCONFIGDIR)/nodes.conf || install -m 644 etc/nodes.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(WWCONFIGDIR)/wwapic.conf || install -m 644 etc/wwapic.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(WWCONFIGDIR)/wwapid.conf || install -m 644 etc/wwapid.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(WWCONFIGDIR)/wwapird.conf || install -m 644 etc/wwapird.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(DATADIR)/warewulf/defaults.conf || ./wwctl --emptyconf genconfig defaults > $(DESTDIR)$(DATADIR)/warewulf/defaults.conf
cp -r etc/examples $(DESTDIR)$(WWCONFIGDIR)/
cp -r etc/ipxe $(DESTDIR)$(WWCONFIGDIR)/
cp -r overlays/* $(DESTDIR)$(WWOVERLAYDIR)/
chmod 755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/init
find $(DESTDIR)$(WWOVERLAYDIR) -type f -name "*.in" -exec rm -f {} \;
chmod 755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR)/wwinit
chmod 600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/ssh/ssh*
chmod 600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/NetworkManager/system-connections/ww4-managed.ww
chmod 644 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/ssh/ssh*.pub.ww
chmod 600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/warewulf/config.ww
chmod 750 $(DESTDIR)$(WWOVERLAYDIR)/host
test -f $(DESTDIR)$(WWCONFIGDIR)/warewulf.conf || install -m 0644 etc/warewulf.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(WWCONFIGDIR)/nodes.conf || install -m 0644 etc/nodes.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(WWCONFIGDIR)/wwapic.conf || install -m 0644 etc/wwapic.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(WWCONFIGDIR)/wwapid.conf || install -m 0644 etc/wwapid.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(WWCONFIGDIR)/wwapird.conf || install -m 0644 etc/wwapird.conf $(DESTDIR)$(WWCONFIGDIR)
test -f $(DESTDIR)$(DATADIR)/warewulf/defaults.conf || install -m 0644 etc/defaults.conf $(DESTDIR)$(DATADIR)/warewulf/defaults.conf
for f in etc/examples/*.ww; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/examples/; done
for f in etc/ipxe/*.ipxe; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/ipxe/; done
(cd overlays && find * -type f -exec install -D -m 0644 {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;)
chmod 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/init
chmod 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR)/wwinit
chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/ssh/ssh*
chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/NetworkManager/system-connections/ww4-managed.ww
chmod 0644 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/etc/ssh/ssh*.pub.ww
chmod 0600 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/warewulf/config.ww
chmod 0750 $(DESTDIR)$(WWOVERLAYDIR)/host
install -m 0755 wwctl $(DESTDIR)$(BINDIR)
install -m 0755 wwclient $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR)/wwclient
install -m 0755 wwapic $(DESTDIR)$(BINDIR)
@@ -203,98 +115,94 @@ files: all
install -m 0644 include/firewalld/warewulf.xml $(DESTDIR)$(FIREWALLDDIR)
install -m 0644 include/systemd/warewulfd.service $(DESTDIR)$(SYSTEMDDIR)
install -m 0644 LICENSE.md $(DESTDIR)$(WWDOCDIR)
./wwctl genconfig completions > $(DESTDIR)$(BASHCOMPDIR)/wwctl
cp man_pages/*.1* $(DESTDIR)$(MANDIR)/man1/
cp man_pages/*.5* $(DESTDIR)$(MANDIR)/man5/
install -m 0644 etc/bash_completion.d/wwctl $(DESTDIR)$(BASHCOMPDIR)/wwctl
for f in docs/man/man1/*.1.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man1/; done
for f in docs/man/man5/*.5.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man5/; done
install -m 0644 staticfiles/README-ipxe.md $(DESTDIR)$(WWDATADIR)/ipxe
install -m 0644 staticfiles/arm64.efi $(DESTDIR)$(WWDATADIR)/ipxe
install -m 0644 staticfiles/x86_64.efi $(DESTDIR)$(WWDATADIR)/ipxe
install -m 0644 staticfiles/x86_64.kpxe $(DESTDIR)$(WWDATADIR)/ipxe
etc/bash_completion.d/wwctl: wwctl
mkdir -p etc/bash_completion.d/
./wwctl --emptyconf genconfig completions >etc/bash_completion.d/wwctl
.PHONY: init
init:
systemctl daemon-reload
cp -r tftpboot/* $(WWTFTPDIR)/ipxe/
restorecon -r $(WWTFTPDIR)
wwctl: $(WWCTL_DEPS)
@echo Building "$@"
@cd cmd/wwctl; GOOS=linux go build -mod vendor -tags "$(WW_GO_BUILD_TAGS)" \
-o ../../wwctl
wwctl: config vendor $(call godeps,cmd/wwctl/main.go)
GOOS=linux go build -mod vendor -tags "$(WW_GO_BUILD_TAGS)" -o wwctl cmd/wwctl/main.go
wwclient: $(WWCLIENT_DEPS)
@echo Building "$@"
@cd cmd/wwclient; CGO_ENABLED=0 GOOS=linux go build -mod vendor -a -ldflags "-extldflags -static" \
-o ../../wwclient
wwclient: config vendor $(call godeps,cmd/wwclient/main.go)
CGO_ENABLED=0 GOOS=linux go build -mod vendor -a -ldflags "-extldflags -static" -o wwclient cmd/wwclient/main.go
man_pages: wwctl
@install -d man_pages
@./wwctl --emptyconf genconfig man man_pages
@cp docs/man/man5/*.5 ./man_pages/
@echo -n "Compressing manpage: "
@cd man_pages; for i in wwctl*1 *.5; do gzip --force $$i; echo -n "$$i "; done; echo
.PHONY: man_pages
man_pages: wwctl $(wildcard docs/man/man5/*.5)
mkdir -p docs/man/man1
./wwctl --emptyconf genconfig man docs/man/man1
gzip --force docs/man/man1/*.1
gzip --force --keep docs/man/man5/*.5
update_configuration: vendor cmd/update_configuration/update_configuration.go
cd cmd/update_configuration && go build \
-X 'github.com/hpcng/warewulf/internal/pkg/node.ConfigFile=./etc/nodes.conf'"\
-mod vendor -tags "$(WW_GO_BUILD_TAGS)" -o ../../update_configuration
update_configuration: vendor $(call godeps,cmd/update_configuration/update_configuration.go)
go build -X 'github.com/hpcng/warewulf/internal/pkg/node.ConfigFile=./etc/nodes.conf'" \
-mod vendor -tags "$(WW_GO_BUILD_TAGS)" -o update_configuration cmd/update_configuration/update_configuration.go
wwapid: $(call godeps,internal/app/api/wwapid/wwapid.go)
go build -o ./wwapid internal/app/api/wwapid/wwapid.go
wwapic: $(call godeps,internal/app/api/wwapic/wwapic.go)
go build -o ./wwapic internal/app/api/wwapic/wwapic.go
wwapird: $(call godeps,internal/app/api/wwapird/wwapird.go)
go build -o ./wwapird internal/app/api/wwapird/wwapird.go
.PHONY: dist
dist: vendor config
rm -rf .dist/$(WAREWULF)-$(VERSION) $(WAREWULF)-$(VERSION).tar.gz
rm -rf .dist/ $(WAREWULF)-$(VERSION).tar.gz
mkdir -p .dist/$(WAREWULF)-$(VERSION)
rsync -a --exclude=".*" --exclude "*~" * .dist/$(WAREWULF)-$(VERSION)/
cd .dist; tar -czf ../$(WAREWULF)-$(VERSION).tar.gz $(WAREWULF)-$(VERSION)
rm -rf .dist
.PHONY: reference
reference: wwctl
mkdir -p userdocs/reference
./wwctl --emptyconf genconfig reference userdocs/reference/
latexpdf: reference
make -C userdocs latexpdf
## wwapi generate code from protobuf. Requires protoc and protoc-grpc-gen-gateway to generate code.
## To setup latest protoc:
## Download the protobuf-all-[VERSION].tar.gz from https://github.com/protocolbuffers/protobuf/releases
## Extract the contents and change in the directory
## ./configure
## make
## make check
## sudo make install
## sudo ldconfig # refresh shared library cache.
## To setup protoc-gen-grpc-gateway, see https://github.com/grpc-ecosystem/grpc-gateway
proto:
rm -rf internal/pkg/api/routes/wwapiv1/
protoc -I internal/pkg/api/routes/v1 -I=. \
.PHONY: proto
proto: $(PROTOC) $(PROTOC_GEN_GRPC_GATEWAY) $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC)
PATH=$(TOOLS_BIN):$(PATH) $(PROTOC) -I /usr/include -I internal/pkg/api/routes/v1 -I=. \
--grpc-gateway_out=. \
--grpc-gateway_opt logtostderr=true \
--go_out=. \
--go-grpc_out=. \
routes.proto
wwapid: ## Build the grpc api server.
go build -o ./wwapid internal/app/api/wwapid/wwapid.go
wwapic: ## Build the sample wwapi client.
go build -o ./wwapic internal/app/api/wwapic/wwapic.go
wwapird: ## Build the rest api server (revese proxy to the grpc api server).
go build -o ./wwapird internal/app/api/wwapird/wwapird.go
.PHONY: contclean
contclean:
rm -f Defaults.mk
rm -f $(WAREWULF)-$(VERSION).tar.gz
rm -f wwapi{c,d,rd} etc/wwapi{c,d,rd}.conf
rm -f include/systemd/warewulfd.service
rm -f internal/pkg/config/buildconfig.go
rm -f update_configuration
rm -f warewulf.spec
rm -f wwclient
rm -f wwctl
rm -rf .dist
rm -f $(WAREWULF)-$(VERSION).tar.gz
rm -rf man_pages
rm -f warewulf.spec
rm -f config
rm -f Defaults.mk
rm -rf $(TOOLS_DIR)
rm -f update_configuration
rm -f etc/wwapi{c,d,rd}.conf
rm -rf .dist/
rm -rf etc/bash_completion.d/
rm -rf userdocs/_*
rm -rf userdocs/reference/*
rm -rf etc/defaults.conf
rm -rf *.coverprofile
.PHONY: clean
clean: contclean
rm -rf vendor
install: files
debinstall: files debfiles

View File

@@ -8,7 +8,7 @@
* [Documentation](https://warewulf.org/docs)
* [Download / Releases](http://github.com/hpcng/warewulf/releases)
* [Slack](http://hpcng.slack.com/#warewulf)
* [Support](http://www.ciq.co/warewulf)
* [Support](https://ciq.com/products/warewulf)
## About Warewulf

110
Variables.mk Normal file
View File

@@ -0,0 +1,110 @@
-include Defaults.mk
# Linux distro (try and set to /etc/os-release ID)
OS_REL := $(shell sed -n "s/^ID\s*=\s*['"\""]\(.*\)['"\""]/\1/p" /etc/os-release)
OS ?= $(OS_REL)
# List of variables to save and replace in files
VARLIST := OS
# Project Information
VARLIST += WAREWULF VERSION RELEASE
WAREWULF ?= warewulf
VERSION ?= 4.5.x
GIT_TAG := $(shell test -e .git && git log -1 --format="%h")
ifdef GIT_TAG
ifdef $(filter $(OS),ubuntu debian)
RELEASE ?= 1.git_$(subst -,+,$(GIT_TAG))
else
RELEASE ?= 1.git_$(subst -,_,$(GIT_TAG))
endif
else
RELEASE ?= 1
endif
# Use LSB-compliant paths if OS is known
ifneq ($(OS),)
USE_LSB_PATHS := true
endif
# Always default to GNU autotools default paths if PREFIX has been redefined
ifdef PREFIX
USE_LSB_PATHS := false
endif
# System directory paths
VARLIST += PREFIX BINDIR SYSCONFDIR SRVDIR DATADIR MANDIR DOCDIR LOCALSTATEDIR
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
SYSCONFDIR ?= $(PREFIX)/etc
DATADIR ?= $(PREFIX)/share
MANDIR ?= $(DATADIR)/man
DOCDIR ?= $(DATADIR)/doc
ifeq ($(USE_LSB_PATHS),true)
SRVDIR ?= /srv
LOCALSTATEDIR ?= /var/local
else
SRVDIR ?= $(PREFIX)/srv
LOCALSTATEDIR ?= $(PREFIX)/var
endif
# OS-Specific Service Locations
VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR
SYSTEMDDIR ?= /usr/lib/systemd/system
BASHCOMPDIR ?= /etc/bash_completion.d
FIREWALLDDIR ?= /usr/lib/firewalld/services
ifeq ($(OS),suse)
TFTPDIR ?= /srv/tftpboot
endif
ifeq ($(OS),ubuntu)
TFTPDIR ?= /srv/tftp
endif
# Default to Red Hat / Rocky Linux
TFTPDIR ?= /var/lib/tftpboot
# Warewulf directory paths
VARLIST += WWCLIENTDIR WWCONFIGDIR WWPROVISIONDIR WWOVERLAYDIR WWCHROOTDIR WWTFTPDIR WWDOCDIR WWDATADIR
WWCONFIGDIR := $(SYSCONFDIR)/$(WAREWULF)
WWPROVISIONDIR := $(SRVDIR)/$(WAREWULF)
WWOVERLAYDIR := $(LOCALSTATEDIR)/$(WAREWULF)/overlays
WWCHROOTDIR := $(LOCALSTATEDIR)/$(WAREWULF)/chroots
WWTFTPDIR := $(TFTPDIR)/$(WAREWULF)
WWDOCDIR := $(DOCDIR)/$(WAREWULF)
WWDATADIR := $(DATADIR)/$(WAREWULF)
WWCLIENTDIR ?= /warewulf
# auto installed tooling
TOOLS_DIR := .tools
TOOLS_BIN := $(TOOLS_DIR)/bin
CONFIG := $(shell pwd)
# tools
GO_TOOLS_BIN := $(addprefix $(TOOLS_BIN)/, $(notdir $(GO_TOOLS)))
GO_TOOLS_VENDOR := $(addprefix vendor/, $(GO_TOOLS))
GOLANGCI_LINT := $(TOOLS_BIN)/golangci-lint
GOLANGCI_LINT_VERSION := v1.53.2
PROTOC_GEN_GO := $(TOOLS_BIN)/protoc-gen-go
PROTOC_GEN_GO_GRPC := $(TOOLS_BIN)/protoc-gen-go-grpc
PROTOC := $(TOOLS_BIN)/protoc
PROTOC_URL := https://github.com/protocolbuffers/protobuf/releases/download/v24.0/protoc-24.0-linux-x86_64.zip
#PROTOC_URL := https://github.com/protocolbuffers/protobuf/releases/download/v24.0/protoc-24.0-linux-aarch_64.zip
PROTOC_GEN_GRPC_GATEWAY := $(TOOLS_BIN)/protoc-gen-grpc-gateway
PROTOC_GEN_GRPC_GATEWAY_URL := https://github.com/grpc-ecosystem/grpc-gateway/releases/download/v2.16.2/protoc-gen-grpc-gateway-v2.16.2-linux-x86_64
#PROTOC_GEN_GRPC_GATEWAY_URL := https://github.com/grpc-ecosystem/grpc-gateway/releases/download/v2.16.2/protoc-gen-grpc-gateway-v2.16.2-linux-arm64
# helper functions
godeps=$(shell go list -deps -f '{{if not .Standard}}{{ $$dep := . }}{{range .GoFiles}}{{$$dep.Dir}}/{{.}} {{end}}{{end}}' $(1) | sed "s%${PWD}/%%g")
ARCHITECTURE_CPU=$(shell lscpu | grep 'Architecture' | cut -d':' -f2 | xargs)
# use GOPROXY for older git clients and speed up downloads
GOPROXY ?= https://proxy.golang.org
export GOPROXY
# built tags needed for wwbuild binary
WW_GO_BUILD_TAGS := containers_image_openpgp containers_image_ostree
Defaults.mk:
printf " $(foreach V,$(VARLIST),$V := $(strip $($V))\n)" >Defaults.mk

View File

@@ -1,22 +0,0 @@
nodeprofiles:
default:
comment: This profile is automatically included for each node
runtime overlay: "generic"
discoverable: false
leap:
comment: openSUSE leap
kernel version: "5.14.21"
ipmi netmask: "255.255.255.0"
keys:
foo: baar
network devices:
lan1:
gateway: 1.1.1.1
nodes:
node01:
system overlay: "nodeoverlay"
discoverable: true
network devices:
eth0:
ipaddr: 1.2.3.4
default: true

11
go.mod
View File

@@ -6,10 +6,11 @@ require (
github.com/containers/image/v5 v5.7.0
github.com/containers/storage v1.30.0
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/coreos/ignition/v2 v2.15.0
github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687
github.com/creasty/defaults v1.7.0
github.com/fatih/color v1.15.0
github.com/golang/glog v1.0.0
github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
github.com/manifoldco/promptui v0.9.0
@@ -18,7 +19,7 @@ require (
github.com/opencontainers/umoci v0.4.6
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.2
github.com/talos-systems/go-smbios v0.1.1
golang.org/x/term v0.5.0
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923
@@ -40,6 +41,8 @@ require (
github.com/containerd/containerd v1.5.0-beta.4 // indirect
github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b // indirect
github.com/containers/ocicrypt v1.1.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -52,6 +55,7 @@ require (
github.com/docker/go-units v0.4.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/gorilla/mux v1.7.4 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
@@ -76,13 +80,14 @@ require (
github.com/rootless-containers/proto v0.1.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/urfave/cli v1.22.4 // indirect
github.com/vbatts/go-mtree v0.5.0 // indirect
github.com/vbauerster/mpb/v5 v5.3.0 // indirect
github.com/vincent-petithory/dataurl v1.0.0 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect

30
go.sum
View File

@@ -350,6 +350,7 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
@@ -447,7 +448,9 @@ github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3st
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.44.204/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c/go.mod h1:0yGO2rna3S9DkITDWHY1bMtcY4IJ4w+4S+EooZUR0bE=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -572,17 +575,26 @@ github.com/containers/storage v1.23.6/go.mod h1:haFs0HRowKwyzvWEx9EgI3WsL8XCSnBD
github.com/containers/storage v1.30.0 h1:KS6zmoPyy0Qcx1HCCiseQ0ysSckRvtiuoVpIGh9iwQA=
github.com/containers/storage v1.30.0/go.mod h1:M/xn0pg6ReYFrLtWl5YELI/a4Xjq+Z3e5GJxQrJCcDI=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/coreos/go-json v0.0.0-20230131223807-18775e0fb4fb/go.mod h1:rcFZM3uxVvdyNmsAV2jopgPD1cs5SPWJWU5dOz2LUnw=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/ignition/v2 v2.15.0 h1:v2fQ6QvkcAF+La5PHHpnpBS1eGZo+LYL1wTOPvDKAcs=
github.com/coreos/ignition/v2 v2.15.0/go.mod h1:+7BiKurzCFg3P427Ml0wqnKzIuhLimnil6LhFV2DkJM=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687 h1:uSmlDgJGbUB0bwQBcZomBTottKwEDF5fF8UjSwKSzWM=
github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687/go.mod h1:Salmysdw7DAVuobBW/LwsKKgpyCPHUhjyJoMJD+ZJiI=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
@@ -681,6 +693,7 @@ github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblf
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -767,6 +780,7 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -831,6 +845,8 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -975,6 +991,7 @@ github.com/opencontainers/umoci v0.4.6 h1:nUULYM+jSLLJCVN2gd4wldm8/yuVMahC36UXna
github.com/opencontainers/umoci v0.4.6/go.mod h1:ZyTwgJPvYl9xv1Cf3ykU41BxMSCxW3AtoueA5Bmxs1E=
github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pin/tftp v2.1.0+incompatible/go.mod h1:xVpZOMCXTy+A5QMjEVN0Glwa1sUvaJhFXbr/aAxuxGY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -1059,8 +1076,9 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA=
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -1078,8 +1096,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
@@ -1110,10 +1129,13 @@ github.com/vbatts/go-mtree v0.5.0/go.mod h1:7JbaNHyBMng+RP8C3Q4E+4Ca8JnGQA2R/MB+
github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
github.com/vbauerster/mpb/v5 v5.3.0 h1:vgrEJjUzHaSZKDRRxul5Oh4C72Yy/5VEMb0em+9M0mQ=
github.com/vbauerster/mpb/v5 v5.3.0/go.mod h1:4yTkvAb8Cm4eylAp6t0JRq6pXDkFJ4krUlDqWYkakAs=
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3/go.mod h1:CSBTxrhePCm0cmXNKDGeu+6bOQzpaEklfCqEpn89JWk=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -1272,6 +1294,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -1425,6 +1448,7 @@ golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1432,6 +1456,7 @@ golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
@@ -1580,6 +1605,7 @@ google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91
google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=
google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=

View File

@@ -15,8 +15,8 @@ import (
"github.com/hpcng/warewulf/internal/pkg/api/container"
apinode "github.com/hpcng/warewulf/internal/pkg/api/node"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/hpcng/warewulf/internal/pkg/version"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/version"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
@@ -157,6 +157,18 @@ func (s *apiServer) ContainerBuild(ctx context.Context, request *wwapiv1.Contain
return
}
// ContainerCopy duplicates a container.
func (s *apiServer) ContainerCopy(ctx context.Context, request *wwapiv1.ContainerCopyParameter) (response *emptypb.Empty, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
err = container.ContainerCopy(request)
return
}
// ContainerDelete deletes one or more containers from Warewulf.
func (s *apiServer) ContainerDelete(ctx context.Context, request *wwapiv1.ContainerDeleteParameter) (response *emptypb.Empty, err error) {

View File

@@ -16,8 +16,8 @@ import (
"github.com/coreos/go-systemd/daemon"
"github.com/google/uuid"
"github.com/hpcng/warewulf/internal/pkg/pidfile"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/pidfile"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
"github.com/talos-systems/go-smbios/smbios"
@@ -31,14 +31,16 @@ var (
RunE: CobraRunE,
SilenceUsage: true,
}
DebugFlag bool
PIDFile string
Webclient *http.Client
DebugFlag bool
PIDFile string
Webclient *http.Client
WarewulfConfArg string
)
func init() {
rootCmd.PersistentFlags().BoolVarP(&DebugFlag, "debug", "d", false, "Run with debugging messages enabled.")
rootCmd.PersistentFlags().StringVarP(&PIDFile, "pidfile", "p", "/var/run/wwclient.pid", "PIDFile to use")
rootCmd.PersistentFlags().StringVar(&WarewulfConfArg, "warewulfconf", "", "Set the warewulf configuration file")
}
@@ -48,9 +50,18 @@ func GetRootCommand() *cobra.Command {
return rootCmd
}
func CobraRunE(cmd *cobra.Command, args []string) error {
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
conf := warewulfconf.Get()
if WarewulfConfArg != "" {
err = conf.Read(WarewulfConfArg)
} else if os.Getenv("WAREWULFCONF") != "" {
err = conf.Read(os.Getenv("WAREWULFCONF"))
} else {
err = conf.Read(warewulfconf.ConfigFile)
}
if err != nil {
return
}
pid, err := pidfile.Write(PIDFile)
if err != nil && pid == -1 {
wwlog.Warn("%v. starting new wwclient", err)

View File

@@ -0,0 +1,48 @@
package copy
import (
"fmt"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
if len(args) > 2 {
wwlog.Warn("copy only requires 2 arguments but you provided %d arguments. Hence, they will be ignored.", len(args))
}
cdp := &wwapiv1.ContainerCopyParameter{
ContainerSource: args[0],
ContainerDestination: args[1],
}
if !container.DoesSourceExist(cdp.ContainerSource) {
wwlog.Error("Container's source doesn't exists: %s", cdp.ContainerSource)
return
}
if !container.ValidName(cdp.ContainerDestination) {
wwlog.Error("Container name contains illegal characters : %s", cdp.ContainerDestination)
return
}
if container.DoesSourceExist(cdp.ContainerDestination) {
wwlog.Error("An other container with name: %s already exists in sources.", cdp.ContainerDestination)
return
}
err = container.Duplicate(cdp.ContainerSource, cdp.ContainerDestination)
if err != nil {
err = fmt.Errorf("could not duplicate image: %s", err.Error())
wwlog.Error(err.Error())
return
}
wwlog.Info("Container %s successfully duplicated as %s", cdp.ContainerSource, cdp.ContainerDestination)
return
}

View File

@@ -0,0 +1,34 @@
package copy
import (
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/spf13/cobra"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "copy CONTAINER NEW_NAME",
Aliases: []string{"cp"},
Short: "Copy an existing container",
Long: "This command will duplicate an imported container image.",
RunE: CobraRunE,
Args: cobra.MinimumNArgs(2),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
}
)
func init() {
// Nothing to do here
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -14,7 +14,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
ContainerNames: args,
}
if !SetYes {
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to container %s", args))
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args))
if !yes {
return
}

View File

@@ -2,6 +2,7 @@ package container
import (
"github.com/hpcng/warewulf/internal/app/wwctl/container/build"
"github.com/hpcng/warewulf/internal/app/wwctl/container/copy"
"github.com/hpcng/warewulf/internal/app/wwctl/container/delete"
"github.com/hpcng/warewulf/internal/app/wwctl/container/exec"
"github.com/hpcng/warewulf/internal/app/wwctl/container/imprt"
@@ -33,6 +34,7 @@ func init() {
baseCmd.AddCommand(delete.GetCommand())
baseCmd.AddCommand(show.GetCommand())
baseCmd.AddCommand(syncuser.GetCommand())
baseCmd.AddCommand(copy.GetCommand())
}

View File

@@ -1,10 +1,14 @@
package add
import (
"fmt"
"strings"
"gopkg.in/yaml.v2"
apinode "github.com/hpcng/warewulf/internal/pkg/api/node"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
)
@@ -22,17 +26,36 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) error {
return err
}
}
// remove the default network as all network values are assigned
// remove the UNDEF network as all network values are assigned
// to this network
if _, ok := vars.nodeConf.NetDevs["default"]; ok && vars.netName != "" {
netDev := *vars.nodeConf.NetDevs["default"]
if !node.ObjectIsEmpty(vars.nodeConf.NetDevs["UNDEF"]) {
netDev := *vars.nodeConf.NetDevs["UNDEF"]
vars.nodeConf.NetDevs[vars.netName] = &netDev
delete(vars.nodeConf.NetDevs, "default")
} else {
if vars.nodeConf.NetDevs["default"].Empty() {
delete(vars.nodeConf.NetDevs, "default")
}
}
delete(vars.nodeConf.NetDevs, "UNDEF")
if vars.fsName != "" {
if !strings.HasPrefix(vars.fsName, "/dev") {
if vars.fsName == vars.partName {
vars.fsName = "/dev/disk/by-partlabel/" + vars.partName
} else {
return fmt.Errorf("filesystems need to have a underlying blockdev")
}
}
fs := *vars.nodeConf.FileSystems["UNDEF"]
vars.nodeConf.FileSystems[vars.fsName] = &fs
}
delete(vars.nodeConf.FileSystems, "UNDEF")
if vars.diskName != "" && vars.partName != "" {
prt := *vars.nodeConf.Disks["UNDEF"].Partitions["UNDEF"]
vars.nodeConf.Disks["UNDEF"].Partitions[vars.partName] = &prt
delete(vars.nodeConf.Disks["UNDEF"].Partitions, "UNDEF")
dsk := *vars.nodeConf.Disks["UNDEF"]
vars.nodeConf.Disks[vars.diskName] = &dsk
}
if (vars.diskName != "") != (vars.partName != "") {
return fmt.Errorf("partition and disk must be specified")
}
delete(vars.nodeConf.Disks, "UNDEF")
buffer, err := yaml.Marshal(vars.nodeConf)
if err != nil {
wwlog.Error("Can't marshall nodeInfo", err)

View File

@@ -5,8 +5,8 @@ import (
"os"
"testing"
"github.com/hpcng/warewulf/internal/pkg/node"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/stretchr/testify/assert"
)
@@ -234,6 +234,65 @@ nodes:
network devices:
foo:
ipaddr: 10.10.0.3
`},
{name: "one node with filesystem",
args: []string{"--fsname=/dev/vda1", "--fspath=/var", "n01"},
wantErr: false,
stdout: "",
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes:
n01:
profiles:
- default
filesystems:
/dev/vda1:
path: /var
`},
{name: "one node with filesystem",
args: []string{"--fsname=dev/vda1", "--fspath=/var", "n01"},
wantErr: true,
stdout: "",
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes: {}
`},
{name: "one node with filesystem and partition ",
args: []string{"--fsname=var", "--fspath=/var", "--partname=var", "--diskname=/dev/vda", "n01"},
wantErr: false,
stdout: "",
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
filesystems:
/dev/disk/by-partlabel/var:
path: /var
`},
{name: "one node with filesystem with btrfs and partition ",
args: []string{"--fsname=var", "--fspath=/var", "--fsformat=btrfs", "--partname=var", "--diskname=/dev/vda", "n01"},
wantErr: false,
stdout: "",
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`},
}
conf_yml := `WW_INTERNAL: 0`

View File

@@ -13,6 +13,9 @@ import (
// Holds the variables which are needed in CobraRunE
type variables struct {
netName string
fsName string
partName string
diskName string
nodeConf node.NodeConf
converters []func() error
}
@@ -30,7 +33,10 @@ func GetCommand() *cobra.Command {
Args: cobra.MinimumNArgs(1),
}
vars.converters = vars.nodeConf.CreateFlags(baseCmd, []string{"tagdel", "nettagdel", "ipmitagdel"})
baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "", "Set network name for network options")
baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "default", "Set network name for network options")
baseCmd.PersistentFlags().StringVar(&vars.fsName, "fsname", "", "set the file system name which must match a partition name")
baseCmd.PersistentFlags().StringVar(&vars.partName, "partname", "", "set the partition name so it can be used by a file system")
baseCmd.PersistentFlags().StringVar(&vars.diskName, "diskname", "", "set disk device name for the partition")
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()

View File

@@ -13,7 +13,9 @@ import (
apiutil "github.com/hpcng/warewulf/internal/pkg/api/util"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)
@@ -43,8 +45,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
wwlog.Error("Could not create temp file:%s \n", err)
}
defer os.Remove(file.Name())
nodeConf := node.NewConf()
yamlTemplate := nodeConf.UnmarshalConf([]string{"tagsdel", "default", "profiles"})
yamlTemplate := node.UnmarshalConf(node.NodeConf{}, []string{"tagsdel", "default", "profiles"})
for {
_ = file.Truncate(0)
_, _ = file.Seek(0, 0)
@@ -131,5 +132,10 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
}
}
err = warewulfd.DaemonReload()
if err != nil {
return errors.Wrap(err, "failed to reload warewulf daemon")
}
return nil
}

View File

@@ -11,6 +11,9 @@ import (
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
return func(cmd *cobra.Command, args []string) (err error) {
if len(args) > 0 && strings.Contains(args[0], ",") {
args = strings.FieldsFunc(args[0], func(r rune) bool { return r == ',' })
}
req := wwapiv1.GetNodeList{
Nodes: args,
Type: wwapiv1.GetNodeList_Simple,
@@ -23,12 +26,14 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err
req.Type = wwapiv1.GetNodeList_Network
} else if vars.showLong {
req.Type = wwapiv1.GetNodeList_Long
} else if vars.showFullAll {
req.Type = wwapiv1.GetNodeList_FullAll
}
nodeInfo, err := apinode.NodeList(&req)
if len(nodeInfo.Output) > 0 {
ph := helper.NewPrintHelper(strings.Split(nodeInfo.Output[0], "="))
ph := helper.NewPrintHelper(strings.Split(nodeInfo.Output[0], ":=:"))
for _, val := range nodeInfo.Output[1:] {
ph.Append(strings.Split(val, "="))
ph.Append(strings.Split(val, ":=:"))
}
ph.Render()
}

View File

@@ -4,10 +4,11 @@ import (
"bytes"
"io"
"os"
"strings"
"testing"
"github.com/hpcng/warewulf/internal/pkg/node"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/stretchr/testify/assert"
)
@@ -20,7 +21,8 @@ func Test_List(t *testing.T) {
stdout string
inDb string
}{
{name: "single node list",
{
name: "single node list",
args: []string{},
wantErr: false,
stdout: ` NODE NAME PROFILES NETWORK
@@ -33,7 +35,96 @@ nodes:
n01:
profiles:
- default
`},
`,
},
{
name: "multiple nodes list",
args: []string{},
wantErr: false,
stdout: ` NODE NAME PROFILES NETWORK
n01 default
n02 default
`,
inDb: `WW_INTERNAL: 43
nodeprofiles:
default: {}
nodes:
n01:
profiles:
- default
n02:
profiles:
- default
`,
},
{
name: "node list returns multiple nodes",
args: []string{"n01,n02"},
wantErr: false,
stdout: ` NODE NAME PROFILES NETWORK
n01 default
n02 default
`,
inDb: `WW_INTERNAL: 43
nodeprofiles:
default: {}
nodes:
n01:
profiles:
- default
n02:
profiles:
- default
`,
},
{
name: "node list returns multiple nodes (case 2)",
args: []string{"n01,n03"},
wantErr: false,
stdout: ` NODE NAME PROFILES NETWORK
n01 default
n03 default
`,
inDb: `WW_INTERNAL: 43
nodeprofiles:
default: {}
nodes:
n01:
profiles:
- default
n02:
profiles:
- default
n03:
profiles:
- default
n04:
profiles:
- default
n05:
profiles:
- default
`,
},
{
name: "node list returns one node",
args: []string{"n01,"},
wantErr: false,
stdout: ` NODE NAME PROFILES NETWORK
n01 default
`,
inDb: `WW_INTERNAL: 43
nodeprofiles:
default: {}
nodes:
n01:
profiles:
- default
n02:
profiles:
- default
`,
},
}
conf_yml := `WW_INTERNAL: 0`
tempWarewulfConf, warewulfConfErr := os.CreateTemp("", "warewulf.conf-")
@@ -82,8 +173,8 @@ nodes:
w.Close()
os.Stdout = old // restoring the real stdout
out := <-outC
if out != tt.stdout {
t.Errorf("Got wrong output, got:'%s'\nwant:'%s'", out, tt.stdout)
if strings.ReplaceAll(out, " ", "") != strings.ReplaceAll(tt.stdout, " ", "") {
t.Errorf("Got wrong output, got:\n'%s'\nwant:\n'%s'", out, tt.stdout)
t.FailNow()
}
})

View File

@@ -6,10 +6,11 @@ import (
)
type variables struct {
showNet bool
showIpmi bool
showAll bool
showLong bool
showNet bool
showIpmi bool
showAll bool
showLong bool
showFullAll bool
}
func GetCommand() *cobra.Command {
@@ -39,6 +40,7 @@ func GetCommand() *cobra.Command {
baseCmd.PersistentFlags().BoolVarP(&vars.showNet, "net", "n", false, "Show node network configurations")
baseCmd.PersistentFlags().BoolVarP(&vars.showIpmi, "ipmi", "i", false, "Show node IPMI configurations")
baseCmd.PersistentFlags().BoolVarP(&vars.showAll, "all", "a", false, "Show all node configurations")
baseCmd.PersistentFlags().BoolVarP(&vars.showFullAll, "fullall", "A", false, "Show all node configurations inclusive empty entries")
baseCmd.PersistentFlags().BoolVarP(&vars.showLong, "long", "l", false, "Show long or wide format")
return baseCmd

View File

@@ -3,54 +3,86 @@ package set
import (
"fmt"
"os"
"strings"
apinode "github.com/hpcng/warewulf/internal/pkg/api/node"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/hpcng/warewulf/internal/pkg/api/util"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
// run converters for different types
for _, c := range Converters {
if err := c(); err != nil {
return err
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
return func(cmd *cobra.Command, args []string) error {
// run converters for different types
for _, c := range vars.converters {
if err := c(); err != nil {
return err
}
}
}
// remove the default network as the all network values are assigned
// to this network
if NetName != "default" {
NodeConf.NetDevs[NetName] = NodeConf.NetDevs["default"]
delete(NodeConf.NetDevs, "default")
}
buffer, err := yaml.Marshal(NodeConf)
if err != nil {
wwlog.Error("Can't marshall nodeInfo", err)
os.Exit(1)
}
set := wwapiv1.NodeSetParameter{
NodeConfYaml: string(buffer[:]),
NetdevDelete: SetNetDevDel,
AllNodes: SetNodeAll,
Force: SetForce,
NodeNames: args,
}
if !SetYes {
var nodeCount uint
// The checks run twice in the prompt case.
// Avoiding putting in a blocking prompt in an API.
_, nodeCount, err = apinode.NodeSetParameterCheck(&set, false)
// remove the default network as the all network values are assigned
// to this network
if !node.ObjectIsEmpty(vars.nodeConf.NetDevs["UNDEF"]) {
netDev := *vars.nodeConf.NetDevs["UNDEF"]
vars.nodeConf.NetDevs[vars.netName] = &netDev
}
delete(vars.nodeConf.NetDevs, "UNDEF")
if vars.fsName != "" {
if !strings.HasPrefix(vars.fsName, "/dev") {
if vars.fsName == vars.partName {
vars.fsName = "/dev/disk/by-partlabel/" + vars.partName
} else {
return fmt.Errorf("filesystems need to have a underlying blockdev")
}
}
fs := *vars.nodeConf.FileSystems["UNDEF"]
vars.nodeConf.FileSystems[vars.fsName] = &fs
}
delete(vars.nodeConf.FileSystems, "UNDEF")
if vars.diskName != "" && vars.partName != "" {
prt := *vars.nodeConf.Disks["UNDEF"].Partitions["UNDEF"]
vars.nodeConf.Disks["UNDEF"].Partitions[vars.partName] = &prt
delete(vars.nodeConf.Disks["UNDEF"].Partitions, "UNDEF")
dsk := *vars.nodeConf.Disks["UNDEF"]
vars.nodeConf.Disks[vars.diskName] = &dsk
}
if (vars.diskName != "") != (vars.partName != "") {
return fmt.Errorf("partition and disk must be specified")
}
delete(vars.nodeConf.Disks, "UNDEF")
buffer, err := yaml.Marshal(vars.nodeConf)
if err != nil {
return
wwlog.Error("Can't marshall nodeInfo", err)
os.Exit(1)
}
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes(s)", nodeCount))
if !yes {
return
wwlog.Debug("sending following values: %s", string(buffer))
set := wwapiv1.NodeSetParameter{
NodeConfYaml: string(buffer[:]),
NetdevDelete: vars.setNetDevDel,
PartitionDelete: vars.setPartDel,
DiskDelete: vars.setDiskDel,
FilesystemDelete: vars.setFsDel,
AllNodes: vars.setNodeAll,
Force: vars.setForce,
NodeNames: args,
}
if !vars.setYes {
var nodeCount uint
// The checks run twice in the prompt case.
// Avoiding putting in a blocking prompt in an API.
_, nodeCount, err = apinode.NodeSetParameterCheck(&set, false)
if err != nil {
return nil
}
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes(s)", nodeCount))
if !yes {
return nil
}
}
return apinode.NodeSet(&set)
}
return apinode.NodeSet(&set)
}

View File

@@ -0,0 +1,348 @@
package set
import (
"bytes"
"os"
"testing"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/stretchr/testify/assert"
)
func Test_Add(t *testing.T) {
tests := []struct {
name string
args []string
wantErr bool
stdout string
chkout bool
outDb string
inDB string
}{
{name: "single node change profile",
args: []string{"--profile=foo", "n01"},
wantErr: false,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- foo
`},
{name: "multiple nodes change profile",
args: []string{"--profile=foo", "n0[1-2]"},
wantErr: false,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
n02:
profiles:
- default`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- foo
n02:
profiles:
- foo
`},
{name: "single node set ipmitag",
args: []string{"--ipmitagadd", "foo=baar", "n01"},
wantErr: false,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
ipmi:
tags:
foo: baar
profiles:
- default
`},
{name: "single node delete tag",
args: []string{"--tagdel", "tag1", "n01"},
wantErr: false,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
tags:
tag1: value1
tag2: value2`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
tags:
tag2: value2
`},
{name: "single node set fs,part and disk",
args: []string{"--fsname=var", "--fspath=/var", "--fsformat=btrfs", "--partname=var", "--diskname=/dev/vda", "n01"},
wantErr: false,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`},
{name: "single delete not existing fs",
args: []string{"--fsdel=var", "n01"},
wantErr: true,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
path: /var
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`},
{name: "single node delete existing fs",
args: []string{"--fsdel=/dev/disk/by-partlabel/var", "n01"},
wantErr: false,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
path: /var
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
`},
{name: "single node delete existing partition",
args: []string{"--partdel=var", "n01"},
wantErr: false,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
path: /var
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
disks:
/dev/vda: {}
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`},
{name: "single node delete existing disk",
args: []string{"--diskdel=/dev/vda", "n01"},
wantErr: false,
stdout: "",
inDB: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
disks:
/dev/vda:
partitions:
var: {}
path: /var
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`,
outDb: `WW_INTERNAL: 43
nodeprofiles:
default:
comment: testit
nodes:
n01:
profiles:
- default
filesystems:
/dev/disk/by-partlabel/var:
format: btrfs
path: /var
`},
}
conf_yml := `WW_INTERNAL: 0`
tempWarewulfConf, warewulfConfErr := os.CreateTemp("", "warewulf.conf-")
assert.NoError(t, warewulfConfErr)
defer os.Remove(tempWarewulfConf.Name())
_, warewulfConfErr = tempWarewulfConf.Write([]byte(conf_yml))
assert.NoError(t, warewulfConfErr)
assert.NoError(t, tempWarewulfConf.Sync())
assert.NoError(t, warewulfconf.New().Read(tempWarewulfConf.Name()))
tempNodeConf, nodesConfErr := os.CreateTemp("", "nodes.conf-")
assert.NoError(t, nodesConfErr)
defer os.Remove(tempNodeConf.Name())
node.ConfigFile = tempNodeConf.Name()
warewulfd.SetNoDaemon()
for _, tt := range tests {
var err error
_, err = tempNodeConf.Seek(0, 0)
assert.NoError(t, err)
assert.NoError(t, tempNodeConf.Truncate(0))
_, err = tempNodeConf.Write([]byte(tt.inDB))
assert.NoError(t, err)
assert.NoError(t, tempNodeConf.Sync())
t.Logf("Running test: %s\n", tt.name)
t.Run(tt.name, func(t *testing.T) {
baseCmd := GetCommand()
tt.args = append(tt.args, "--yes")
baseCmd.SetArgs(tt.args)
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
err = baseCmd.Execute()
if (err != nil) != tt.wantErr {
t.Errorf("Got unwanted error: %s", err)
t.FailNow()
}
config, configErr := node.New()
assert.NoError(t, configErr)
dumpBytes, _ := config.Dump()
dump := string(dumpBytes)
if dump != tt.outDb {
t.Errorf("DB dump is wrong, got:'%s'\nwant:'%s'", dump, tt.outDb)
t.FailNow()
}
if tt.chkout && buf.String() != tt.stdout {
t.Errorf("Got wrong output, got:'%s'\nwant:'%s'", buf.String(), tt.stdout)
t.FailNow()
}
})
}
}

View File

@@ -10,14 +10,32 @@ import (
"github.com/spf13/cobra"
)
var (
baseCmd = &cobra.Command{
type variables struct {
setNetDevDel string
setDiskDel string
setPartDel string
setFsDel string
netName string
partName string
diskName string
fsName string
setNodeAll bool
setYes bool
setForce bool
nodeConf node.NodeConf
converters []func() error
}
func GetCommand() *cobra.Command {
vars := variables{}
vars.nodeConf = node.NewConf()
baseCmd := &cobra.Command{
DisableFlagsInUseLine: true,
Use: "set [OPTIONS] PATTERN [PATTERN ...]",
Short: "Configure node properties",
Long: "This command sets configuration properties for nodes matching PATTERN.\n\nNote: use the string 'UNSET' to remove a configuration",
Args: cobra.MinimumNArgs(0),
RunE: CobraRunE,
RunE: CobraRunE(&vars),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
@@ -32,23 +50,19 @@ var (
return node_names, cobra.ShellCompDirectiveNoFileComp
},
}
SetNetDevDel string
NetName string
SetNodeAll bool
SetYes bool
SetForce bool
NodeConf node.NodeConf
Converters []func() error
)
func init() {
NodeConf = node.NewConf()
Converters = NodeConf.CreateFlags(baseCmd, []string{})
baseCmd.PersistentFlags().StringVarP(&SetNetDevDel, "netdel", "D", "", "Delete the node's network device")
baseCmd.PersistentFlags().StringVar(&NetName, "netname", "default", "Set network name for network options")
baseCmd.PersistentFlags().BoolVarP(&SetNodeAll, "all", "a", false, "Set all nodes")
baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked")
baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force configuration (even on error)")
vars.converters = vars.nodeConf.CreateFlags(baseCmd, []string{})
baseCmd.PersistentFlags().StringVarP(&vars.setNetDevDel, "netdel", "D", "", "Delete the node's network device")
baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "default", "Set network name for network options")
baseCmd.PersistentFlags().BoolVarP(&vars.setNodeAll, "all", "a", false, "Set all nodes")
baseCmd.PersistentFlags().StringVar(&vars.fsName, "fsname", "", "set the file system name which must match a partition name")
baseCmd.PersistentFlags().StringVar(&vars.partName, "partname", "", "set the partition name so it can be used by a file system")
baseCmd.PersistentFlags().StringVar(&vars.diskName, "diskname", "", "set disk device name for the partition")
baseCmd.PersistentFlags().StringVar(&vars.setDiskDel, "diskdel", "", "delete the disk from the configuration")
baseCmd.PersistentFlags().StringVar(&vars.setPartDel, "partdel", "", "delete the partition from the configuration")
baseCmd.PersistentFlags().StringVar(&vars.setFsDel, "fsdel", "", "delete the partition from the configuration")
baseCmd.PersistentFlags().BoolVarP(&vars.setYes, "yes", "y", false, "Set 'yes' to all questions asked")
baseCmd.PersistentFlags().BoolVarP(&vars.setForce, "force", "f", false, "Force configuration (even on error)")
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()
@@ -86,9 +100,5 @@ func init() {
log.Println(err)
}
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -3,6 +3,7 @@ package imprt
import (
"os"
"path"
"path/filepath"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/overlay"
@@ -42,6 +43,23 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
os.Exit(1)
}
if CreateDirs {
parent := filepath.Dir(path.Join(overlaySource, dest))
if _, err := os.Stat(parent); os.IsNotExist(err) {
wwlog.Debug("Create dir: %s", parent)
srcInfo, err := os.Stat(source)
if err != nil {
wwlog.Error("Could not retrieve the stat for file: %s", err)
return err
}
err = os.MkdirAll(parent, srcInfo.Mode())
if err != nil {
wwlog.Error("Could not create parent dif: %s: %v", parent, err)
return err
}
}
}
err := util.CopyFile(source, path.Join(overlaySource, dest))
if err != nil {
return errors.Wrap(err, "could not copy file into overlay")

View File

@@ -0,0 +1,94 @@
package imprt
import (
"fmt"
"os"
"testing"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/stretchr/testify/assert"
)
func Test_List(t *testing.T) {
tmpdir, err := os.MkdirTemp(os.TempDir(), "warewulf")
if err != nil {
t.Errorf("Could not create temp folder: %v", err)
t.FailNow()
}
defer os.RemoveAll(tmpdir)
overlayDir := fmt.Sprintf("%s/overlay", tmpdir)
err = os.MkdirAll(overlayDir, 0o755)
if err != nil {
t.Errorf("Could not create target folder: %s, err: %v", overlayDir, err)
t.FailNow()
}
importDir := fmt.Sprintf("%s/test", overlayDir)
err = os.MkdirAll(importDir, 0o755)
if err != nil {
t.Errorf("Could not create target folder: %s, err: %v", importDir, err)
t.FailNow()
}
file, err := os.CreateTemp(tmpdir, "file")
if err != nil {
t.Errorf("Could not create tempfile")
t.FailNow()
}
file.Close()
err = os.Chmod(file.Name(), 0o755)
if err != nil {
t.Errorf("Could not change the file %s mode: %v", file.Name(), err)
t.FailNow()
}
inDb := `WW_INTERNAL: 43
nodeprofiles:
default: {}
nodes: {}
`
conf_yml := `
WW_INTERNAL: 0
`
conf := warewulfconf.New()
err = conf.Parse([]byte(conf_yml))
assert.NoError(t, err)
warewulfd.SetNoDaemon()
conf.Paths.WWOverlaydir = overlayDir
_, err = node.Parse([]byte(inDb))
assert.NoError(t, err)
t.Logf("Running test: wwctl overlay import test\n")
t.Run("wwctl overlay import test", func(t *testing.T) {
baseCmd := GetCommand()
baseCmd.SetArgs([]string{"-n", "test", file.Name()})
baseCmd.SetOut(nil)
baseCmd.SetErr(nil)
err = baseCmd.Execute()
if err == nil {
t.Errorf("Should recieve error when running command")
t.FailNow()
}
if _, err = os.Stat(importDir + file.Name()); err == nil {
t.Errorf("Target file %s should not exist", importDir+file.Name())
t.FailNow()
}
baseCmd.SetArgs([]string{"-p", "-n", "test", file.Name()})
baseCmd.SetOut(nil)
baseCmd.SetErr(nil)
err = baseCmd.Execute()
if err != nil {
t.Errorf("Received error when running command, err: %v\n", err)
t.FailNow()
}
if _, err = os.Stat(importDir + file.Name()); os.IsNotExist(err) {
t.Errorf("Target file %s should exist", importDir+file.Name())
t.FailNow()
}
})
}

View File

@@ -23,10 +23,12 @@ var (
},
}
NoOverlayUpdate bool
CreateDirs bool
)
func init() {
baseCmd.PersistentFlags().BoolVarP(&NoOverlayUpdate, "noupdate", "n", false, "Don't update overlays")
baseCmd.PersistentFlags().BoolVarP(&CreateDirs, "parents", "p", false, "Create any necessary parent directories")
}
// GetRootCommand returns the root cobra.Command for the application.

View File

@@ -69,7 +69,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
wwlog.Error("%v does not identify a single node", NodeName)
os.Exit(1)
}
tstruct := overlay.InitStruct(filteredNodes[0])
tstruct := overlay.InitStruct(&filteredNodes[0])
tstruct.BuildSource = overlayFile
buffer, backupFile, writeFile, err := overlay.RenderTemplateFile(overlayFile, tstruct)
if err != nil {

View File

@@ -3,8 +3,10 @@ package add
import (
"fmt"
"os"
"strings"
apiprofile "github.com/hpcng/warewulf/internal/pkg/api/profile"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"gopkg.in/yaml.v2"
@@ -16,7 +18,7 @@ import (
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
return func(cmd *cobra.Command, args []string) (err error) {
// run converters for different types
for _, c := range Converters {
for _, c := range vars.Converters {
if err := c(); err != nil {
return err
}
@@ -24,25 +26,55 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err
// remove the default network as the all network values are assigned
// to this network
if vars.netName != "" {
netDev := *vars.profileConf.NetDevs["default"]
netDev := *vars.profileConf.NetDevs["UNDEF"]
vars.profileConf.NetDevs[vars.netName] = &netDev
delete(vars.profileConf.NetDevs, "default")
delete(vars.profileConf.NetDevs, "UNDEF")
}
// remove the UNDEF network as all network values are assigned
// to this network
if !node.ObjectIsEmpty(vars.profileConf.NetDevs["UNDEF"]) {
netDev := *vars.profileConf.NetDevs["UNDEF"]
vars.profileConf.NetDevs[vars.netName] = &netDev
}
delete(vars.profileConf.NetDevs, "UNDEF")
if vars.fsName != "" {
if !strings.HasPrefix(vars.fsName, "/dev") {
if vars.fsName == vars.partName {
vars.fsName = "/dev/disk/by-partlabel/" + vars.partName
} else {
return fmt.Errorf("filesystems need to have a underlying blockdev")
}
}
fs := *vars.profileConf.FileSystems["UNDEF"]
vars.profileConf.FileSystems[vars.fsName] = &fs
}
delete(vars.profileConf.FileSystems, "UNDEF")
if vars.diskName != "" && vars.partName != "" {
prt := *vars.profileConf.Disks["UNDEF"].Partitions["UNDEF"]
vars.profileConf.Disks["UNDEF"].Partitions[vars.partName] = &prt
delete(vars.profileConf.Disks["UNDEF"].Partitions, "UNDEF")
dsk := *vars.profileConf.Disks["UNDEF"]
vars.profileConf.Disks[vars.diskName] = &dsk
}
if (vars.diskName != "") != (vars.partName != "") {
return fmt.Errorf("partition and disk must be specified")
}
delete(vars.profileConf.Disks, "UNDEF")
buffer, err := yaml.Marshal(vars.profileConf)
if err != nil {
wwlog.Error("Cant marshall nodeInfo", err)
os.Exit(1)
}
set := wwapiv1.NodeSetParameter{
set := wwapiv1.ProfileSetParameter{
NodeConfYaml: string(buffer[:]),
NetdevDelete: SetNetDevDel,
AllNodes: SetNodeAll,
Force: SetForce,
NodeNames: args,
NetdevDelete: vars.SetNetDevDel,
AllProfiles: vars.SetNodeAll,
Force: vars.SetForce,
ProfileNames: args,
}
if !SetYes {
if !vars.SetYes {
// The checks run twice in the prompt case.
// Avoiding putting in a blocking prompt in an API.
_, _, err = apiprofile.ProfileSetParameterCheck(&set, false)

View File

@@ -5,8 +5,8 @@ import (
"os"
"testing"
"github.com/hpcng/warewulf/internal/pkg/node"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/stretchr/testify/assert"
)
@@ -26,9 +26,7 @@ func Test_Add(t *testing.T) {
stdout: "",
outDb: `WW_INTERNAL: 43
nodeprofiles:
p01:
network devices:
default: {}
p01: {}
nodes: {}
`,
},

View File

@@ -11,19 +11,18 @@ import (
)
type variables struct {
netName string
profileConf node.NodeConf
}
var (
netName string
profileConf node.NodeConf
SetNetDevDel string
SetNodeAll bool
SetYes bool
SetForce bool
NetName string
fsName string
partName string
diskName string
ProfileConf node.NodeConf
Converters []func() error
)
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
@@ -37,9 +36,12 @@ func GetCommand() *cobra.Command {
RunE: CobraRunE(&vars),
Args: cobra.ExactArgs(1),
}
Converters = vars.profileConf.CreateFlags(baseCmd,
vars.Converters = vars.profileConf.CreateFlags(baseCmd,
[]string{"ipaddr", "ipaddr6", "ipmiaddr", "profile"})
baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "", "Set network name for network options")
baseCmd.PersistentFlags().StringVar(&vars.fsName, "fsname", "", "set the file system name which must match a partition name")
baseCmd.PersistentFlags().StringVar(&vars.partName, "partname", "", "set the partition name so it can be used by a file system")
baseCmd.PersistentFlags().StringVar(&vars.diskName, "diskname", "", "set disk device name for the partition")
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()
@@ -65,7 +67,6 @@ func GetCommand() *cobra.Command {
}); err != nil {
log.Println(err)
}
baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked")
baseCmd.PersistentFlags().BoolVarP(&vars.SetYes, "yes", "y", false, "Set 'yes' to all questions asked")
return baseCmd
}

View File

@@ -45,8 +45,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
wwlog.Error("Could not create temp file:%s \n", err)
}
defer os.Remove(file.Name())
nodeConf := node.NewConf()
yamlTemplate := nodeConf.UnmarshalConf([]string{"tagsdel"})
yamlTemplate := node.UnmarshalConf(node.NodeConf{}, []string{"tagsdel"})
for {
_ = file.Truncate(0)
_, _ = file.Seek(0, 0)
@@ -112,7 +111,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
if yes {
err = apiprofile.ProfileDelete(&wwapiv1.NodeDeleteParameter{NodeNames: pList, Force: true})
if err != nil {
wwlog.Verbose("Problem deleting nodes before modification %s")
}

View File

@@ -12,9 +12,13 @@ import (
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
return func(cmd *cobra.Command, args []string) (err error) {
if len(args) > 0 && strings.Contains(args[0], ",") {
args = strings.FieldsFunc(args[0], func(r rune) bool { return r == ',' })
}
req := wwapiv1.GetProfileList{
ShowAll: vars.showAll,
Profiles: args,
ShowAll: vars.showAll,
ShowFullAll: vars.showFullAll,
Profiles: args,
}
profileInfo, err := apiprofile.ProfileList(&req)
if err != nil {

View File

@@ -32,6 +32,53 @@ nodes:
n01:
profiles:
- default
`,
},
{
name: "profile list returns multiple profiles",
args: []string{"default,test"},
stdout: `PROFILE NAME COMMENT/DESCRIPTION
default --
test --`,
inDb: `WW_INTERNAL: 43
nodeprofiles:
default: {}
test: {}
nodes:
n01:
profiles:
- default
`,
},
{
name: "profile list returns one profiles",
args: []string{"test,"},
stdout: `PROFILE NAME COMMENT/DESCRIPTION
test --`,
inDb: `WW_INTERNAL: 43
nodeprofiles:
default: {}
test: {}
nodes:
n01:
profiles:
- default
`,
},
{
name: "profile list returns all profiles",
args: []string{","},
stdout: `PROFILE NAME COMMENT/DESCRIPTION
default --
test --`,
inDb: `WW_INTERNAL: 43
nodeprofiles:
default: {}
test: {}
nodes:
n01:
profiles:
- default
`,
},
}

View File

@@ -3,7 +3,8 @@ package list
import "github.com/spf13/cobra"
type variables struct {
showAll bool
showAll bool
showFullAll bool
}
// GetRootCommand returns the root cobra.Command for the application.
@@ -17,7 +18,8 @@ func GetCommand() *cobra.Command {
RunE: CobraRunE(&vars),
Aliases: []string{"ls"},
}
baseCmd.PersistentFlags().BoolVarP(&vars.showAll, "all", "a", false, "Show all node configurations")
baseCmd.PersistentFlags().BoolVarP(&vars.showAll, "all", "a", false, "Show all profile configurations")
baseCmd.PersistentFlags().BoolVarP(&vars.showFullAll, "fullall", "A", false, "Show all profile configurations inclusive empty entries")
return baseCmd
}

View File

@@ -3,48 +3,80 @@ package set
import (
"fmt"
"os"
"strings"
apiprofile "github.com/hpcng/warewulf/internal/pkg/api/profile"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/hpcng/warewulf/internal/pkg/api/util"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) {
return func(cmd *cobra.Command, args []string) error {
// remove the default network as the all network values are assigned
// to this network
if NetName != "default" {
ProfileConf.NetDevs[NetName] = ProfileConf.NetDevs["default"]
delete(ProfileConf.NetDevs, "default")
}
buffer, err := yaml.Marshal(ProfileConf)
if err != nil {
wwlog.Error("Cant marshall nodeInfo", err)
os.Exit(1)
}
set := wwapiv1.NodeSetParameter{
NodeConfYaml: string(buffer[:]),
NetdevDelete: SetNetDevDel,
AllNodes: SetNodeAll,
Force: SetForce,
NodeNames: args,
}
if !SetYes {
var profileCount uint
// The checks run twice in the prompt case.
// Avoiding putting in a blocking prompt in an API.
_, profileCount, err = apiprofile.ProfileSetParameterCheck(&set, false)
// remove the default network as the all network values are assigned
// to this network
if !node.ObjectIsEmpty(vars.profileConf.NetDevs["UNDEF"]) {
netDev := *vars.profileConf.NetDevs["UNDEF"]
vars.profileConf.NetDevs[vars.netName] = &netDev
}
delete(vars.profileConf.NetDevs, "UNDEF")
if vars.fsName != "" {
if !strings.HasPrefix(vars.fsName, "/dev") {
if vars.fsName == vars.partName {
vars.fsName = "/dev/disk/by-partlabel/" + vars.partName
} else {
return fmt.Errorf("filesystems need to have a underlying blockdev")
}
}
fs := *vars.profileConf.FileSystems["UNDEF"]
vars.profileConf.FileSystems[vars.fsName] = &fs
}
delete(vars.profileConf.FileSystems, "UNDEF")
if vars.diskName != "" && vars.partName != "" {
prt := *vars.profileConf.Disks["UNDEF"].Partitions["UNDEF"]
vars.profileConf.Disks["UNDEF"].Partitions[vars.partName] = &prt
delete(vars.profileConf.Disks["UNDEF"].Partitions, "UNDEF")
dsk := *vars.profileConf.Disks["UNDEF"]
vars.profileConf.Disks[vars.diskName] = &dsk
}
if (vars.diskName != "") != (vars.partName != "") {
return fmt.Errorf("partition and disk must be specified")
}
delete(vars.profileConf.Disks, "UNDEF")
buffer, err := yaml.Marshal(vars.profileConf)
if err != nil {
return
wwlog.Error("Cant marshall nodeInfo", err)
os.Exit(1)
}
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d profile(s)", profileCount))
if !yes {
return
wwlog.Debug("sending following values: %s", string(buffer))
set := wwapiv1.ProfileSetParameter{
NodeConfYaml: string(buffer[:]),
NetdevDelete: vars.setNetDevDel,
PartitionDelete: vars.setPartDel,
DiskDelete: vars.setDiskDel,
FilesystemDelete: vars.setFsDel,
AllProfiles: vars.setNodeAll,
Force: vars.setForce,
ProfileNames: args,
}
if !vars.setYes {
var profileCount uint
// The checks run twice in the prompt case.
// Avoiding putting in a blocking prompt in an API.
_, profileCount, err = apiprofile.ProfileSetParameterCheck(&set, false)
if err != nil {
return err
}
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d profile(s)", profileCount))
if !yes {
return err
}
}
return apiprofile.ProfileSet(&set)
}
return apiprofile.ProfileSet(&set)
}

View File

@@ -10,14 +10,33 @@ import (
"github.com/spf13/cobra"
)
var (
baseCmd = &cobra.Command{
type variables struct {
setNetDevDel string
setDiskDel string
setPartDel string
setFsDel string
setNodeAll bool
setYes bool
setForce bool
netName string
partName string
diskName string
fsName string
profileConf node.NodeConf
converters []func() error
}
func GetCommand() *cobra.Command {
vars := variables{}
vars.profileConf = node.NewConf()
baseCmd := &cobra.Command{
Use: "set [OPTIONS] [PROFILE ...]",
Short: "Configure node profile properties",
Long: "This command sets configuration properties for the node PROFILE(s).\n\n" +
"Note: use the string 'UNSET' to remove a configuration",
Args: cobra.MinimumNArgs(0),
RunE: CobraRunE,
RunE: CobraRunE(&vars),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
@@ -32,24 +51,20 @@ var (
return p_names, cobra.ShellCompDirectiveNoFileComp
},
}
SetNetDevDel string
SetNodeAll bool
SetYes bool
SetForce bool
NetName string
ProfileConf node.NodeConf
Converters []func() error
)
func init() {
ProfileConf = node.NewConf()
Converters = ProfileConf.CreateFlags(baseCmd,
vars.profileConf = node.NewConf()
vars.converters = vars.profileConf.CreateFlags(baseCmd,
[]string{"ipaddr", "ipaddr6", "ipmiaddr", "profile"})
baseCmd.PersistentFlags().StringVar(&NetName, "netname", "default", "Set network name for network options")
baseCmd.PersistentFlags().StringVarP(&SetNetDevDel, "netdel", "D", "", "Delete the node's network device")
baseCmd.PersistentFlags().BoolVarP(&SetNodeAll, "all", "a", false, "Set all nodes")
baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked")
baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force configuration (even on error)")
baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "default", "Set network name for network options")
baseCmd.PersistentFlags().StringVarP(&vars.setNetDevDel, "netdel", "D", "", "Delete the node's network device")
baseCmd.PersistentFlags().BoolVarP(&vars.setNodeAll, "all", "a", false, "Set all nodes")
baseCmd.PersistentFlags().BoolVarP(&vars.setYes, "yes", "y", false, "Set 'yes' to all questions asked")
baseCmd.PersistentFlags().BoolVarP(&vars.setForce, "force", "f", false, "Force configuration (even on error)")
baseCmd.PersistentFlags().StringVar(&vars.fsName, "fsname", "", "set the file system name which must match a partition name")
baseCmd.PersistentFlags().StringVar(&vars.partName, "partname", "", "set the partition name so it can be used by a file system")
baseCmd.PersistentFlags().StringVar(&vars.diskName, "diskname", "", "set disk device name for the partition")
baseCmd.PersistentFlags().StringVar(&vars.setDiskDel, "diskdel", "", "delete the disk from the configuration")
baseCmd.PersistentFlags().StringVar(&vars.setPartDel, "partdel", "", "delete the partition from the configuration")
baseCmd.PersistentFlags().StringVar(&vars.setFsDel, "fsdel", "", "delete the from the configuration")
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()
@@ -75,10 +90,5 @@ func init() {
}); err != nil {
log.Println(err)
}
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -18,6 +18,31 @@ import (
"github.com/pkg/errors"
)
func ContainerCopy(cbp *wwapiv1.ContainerCopyParameter) (err error) {
if cbp == nil {
return fmt.Errorf("ContainerCopyParameter is nil")
}
if !container.DoesSourceExist(cbp.ContainerSource) {
return fmt.Errorf("Container %s does not exists.", cbp.ContainerSource)
}
if !container.ValidName(cbp.ContainerDestination) {
return fmt.Errorf("Container name contains illegal characters : %s", cbp.ContainerDestination)
}
if container.DoesSourceExist(cbp.ContainerDestination) {
return fmt.Errorf("An other container with the name %s already exists", cbp.ContainerDestination)
}
err = container.Duplicate(cbp.ContainerSource, cbp.ContainerDestination)
if err != nil {
return fmt.Errorf("could not duplicate image: %s", err.Error())
}
return fmt.Errorf("Container %s has been succesfully duplicated as %s", cbp.ContainerSource, cbp.ContainerDestination)
}
func ContainerBuild(cbp *wwapiv1.ContainerBuildParameter) (err error) {
if cbp == nil {
return fmt.Errorf("ContainerBuildParameter is nil")
@@ -203,11 +228,14 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin
return
}
err = container.SyncUids(cip.Name, !cip.SyncUser)
if err != nil && !cip.SyncUser {
SyncUserShowOnly := !cip.SyncUser
err = container.SyncUids(cip.Name, SyncUserShowOnly)
if err != nil {
err = fmt.Errorf("error in user sync, fix error and run 'syncuser' manually: %s", err)
wwlog.Error(err.Error())
return
if cip.SyncUser {
return
}
}
wwlog.Info("Building container: %s", cip.Name)

View File

@@ -2,7 +2,6 @@ package apinode
import (
"fmt"
"reflect"
"sort"
"strings"
@@ -29,7 +28,7 @@ func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err erro
sort.Strings(nodeGet.Nodes)
if nodeGet.Type == wwapiv1.GetNodeList_Simple {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s", "NODE NAME", "PROFILES", "NETWORK"))
fmt.Sprintf("%s:=:%s:=:%s", "NODE NAME", "PROFILES", "NETWORK"))
for _, n := range node.FilterByName(nodes, nodeGet.Nodes) {
var netNames []string
for k := range n.NetDevs {
@@ -37,31 +36,31 @@ func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err erro
}
sort.Strings(netNames)
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s", n.Id.Print(), n.Profiles.Print(), strings.Join(netNames, ", ")))
fmt.Sprintf("%s:=:%s:=:%s", n.Id.Print(), n.Profiles.Print(), strings.Join(netNames, ", ")))
}
} else if nodeGet.Type == wwapiv1.GetNodeList_Network {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s=%s=%s", "NODE NAME", "NAME", "HWADDR", "IPADDR", "GATEWAY", "DEVICE"))
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s:=:%s", "NODE NAME", "NAME", "HWADDR", "IPADDR", "GATEWAY", "DEVICE"))
for _, n := range node.FilterByName(nodes, nodeGet.Nodes) {
if len(n.NetDevs) > 0 {
for name := range n.NetDevs {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s=%s=%s", n.Id.Print(), name,
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s:=:%s", n.Id.Print(), name,
n.NetDevs[name].Hwaddr.Print(),
n.NetDevs[name].Ipaddr.Print(),
n.NetDevs[name].Gateway.Print(),
n.NetDevs[name].Device.Print()))
}
} else {
fmt.Printf("%s=%s=%s=%s=%s=%s", n.Id.Print(), "--", "--", "--", "--", "--")
fmt.Printf("%s:=:%s:=:%s:=:%s:=:%s:=:%s", n.Id.Print(), "--", "--", "--", "--", "--")
}
}
} else if nodeGet.Type == wwapiv1.GetNodeList_Ipmi {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s=%s", "NODE NAME", "IPMI IPADDR", "IPMI PORT", "IPMI USERNAME", "IPMI INTERFACE"))
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s", "NODE NAME", "IPMI IPADDR", "IPMI PORT", "IPMI USERNAME", "IPMI INTERFACE"))
for _, n := range node.FilterByName(nodes, nodeGet.Nodes) {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s=%s", n.Id.Print(),
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s", n.Id.Print(),
n.Ipmi.Ipaddr.Print(),
n.Ipmi.Port.Print(),
n.Ipmi.UserName.Print(),
@@ -69,108 +68,22 @@ func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err erro
}
} else if nodeGet.Type == wwapiv1.GetNodeList_Long {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", "NODE NAME", "KERNEL OVERRIDE", "CONTAINER", "OVERLAYS (S/R)"))
fmt.Sprintf("%s:=:%s:=:%s:=:%s", "NODE NAME", "KERNEL OVERRIDE", "CONTAINER", "OVERLAYS (S/R)"))
for _, n := range node.FilterByName(nodes, nodeGet.Nodes) {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", n.Id.Print(),
fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(),
n.Kernel.Override.Print(),
n.ContainerName.Print(),
n.SystemOverlay.Print()+"/"+n.RuntimeOverlay.Print()))
}
} else if nodeGet.Type == wwapiv1.GetNodeList_All {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", "NODE", "FIELD", "PROFILE", "VALUE"))
} else if nodeGet.Type == wwapiv1.GetNodeList_All || nodeGet.Type == wwapiv1.GetNodeList_FullAll {
for _, n := range node.FilterByName(nodes, nodeGet.Nodes) {
nType := reflect.TypeOf(n)
nVal := reflect.ValueOf(n)
nConfType := reflect.TypeOf(node.NodeConf{})
for i := 0; i < nType.NumField(); i++ {
var fieldName, fieldSource, fieldVal string
nConfField, ok := nConfType.FieldByName(nType.Field(i).Name)
if ok {
fieldName = nConfField.Tag.Get("lopt")
} else {
fieldName = nType.Field(i).Name
}
if nType.Field(i).Type == reflect.TypeOf(node.Entry{}) {
entr := nVal.Field(i).Interface().(node.Entry)
fieldSource = entr.Source()
fieldVal = entr.Print()
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", n.Id.Print(), fieldName, fieldSource, fieldVal))
} else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.Entry{}) {
entrMap := nVal.Field(i).Interface().(map[string]*node.Entry)
for key, val := range entrMap {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", n.Id.Print(), key, val.Source(), val.Print()))
}
} else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.NetDevEntry{}) {
netDevs := nVal.Field(i).Interface().(map[string]*node.NetDevEntry)
for netName, netWork := range netDevs {
netInfoType := reflect.TypeOf(*netWork)
netInfoVal := reflect.ValueOf(*netWork)
netConfType := reflect.TypeOf(node.NetDevs{})
for j := 0; j < netInfoType.NumField(); j++ {
netConfField, ok := netConfType.FieldByName(netInfoType.Field(j).Name)
if ok {
if netConfField.Tag.Get("lopt") != "nettagadd" {
fieldName = netName + ":" + netConfField.Tag.Get("lopt")
} else {
fieldName = netName + ":tag"
}
} else {
fieldName = netName + ":" + netInfoType.Field(j).Name
}
if netInfoType.Field(j).Type == reflect.TypeOf(node.Entry{}) {
entr := netInfoVal.Field(j).Interface().(node.Entry)
fieldSource = entr.Source()
fieldVal = entr.Print()
// only print fields with lopt
if netConfField.Tag.Get("lopt") != "" {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", n.Id.Print(), fieldName, fieldSource, fieldVal))
}
} else if netInfoType.Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) {
for key, val := range netInfoVal.Field(j).Interface().(map[string]*node.Entry) {
keyfieldName := fieldName + ":" + key
fieldSource = val.Source()
fieldVal = val.Print()
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", n.Id.Print(), keyfieldName, fieldSource, fieldVal))
}
}
}
}
} else if nType.Field(i).Type.Kind() == reflect.Ptr {
nestInfoType := reflect.TypeOf(nVal.Field(i).Interface())
nestInfoVal := reflect.ValueOf(nVal.Field(i).Interface())
// nestConfType := reflect.TypeOf(nConfField.Type.Elem().FieldByName())
for j := 0; j < nestInfoType.Elem().NumField(); j++ {
nestConfField, ok := nConfField.Type.Elem().FieldByName(nestInfoType.Elem().Field(j).Name)
if ok {
fieldName = nestConfField.Tag.Get("lopt")
} else {
fieldName = nestInfoType.Elem().Field(j).Name
}
if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(node.Entry{}) {
entr := nestInfoVal.Elem().Field(j).Interface().(node.Entry)
fieldSource = entr.Source()
fieldVal = entr.Print()
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", n.Id.Print(), fieldName, fieldSource, fieldVal))
} else if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) {
for key, val := range nestInfoVal.Elem().Field(j).Interface().(map[string]*node.Entry) {
fieldName = fieldName + ":" + key
fieldSource = val.Source()
fieldVal = val.Print()
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s=%s=%s=%s", n.Id.Print(), fieldName, fieldSource, fieldVal))
}
}
}
}
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s:=:%s:=:%s:=:%s", "NODE", "FIELD", "PROFILE", "VALUE"))
fields := n.GetFields(wwapiv1.GetNodeList_FullAll == nodeGet.Type)
for _, f := range fields {
nodeList.Output = append(nodeList.Output,
fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id.Print(), f.Field, f.Source, f.Value))
}
}
}

View File

@@ -1,79 +0,0 @@
package apinode
import (
"fmt"
"reflect"
"strings"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/hpcng/warewulf/internal/pkg/node"
)
/*
Get the all the fields of a NodeInfo, keys are the lopt of NodeConf.
func GetFields(n interface{}) map[string]*wwapiv1.NodeField {
return getFieldsOf(n, node.NodeConf{})
}
*/
func GetFields(n interface{}) map[string]*wwapiv1.NodeField {
nodeType := reflect.TypeOf(n)
nodeVal := reflect.ValueOf(n)
fieldMap := make(map[string]*wwapiv1.NodeField)
for i := 0; i < nodeType.NumField(); i++ {
switch nodeType.Field(i).Type {
case reflect.TypeOf(node.Entry{}):
var myField wwapiv1.NodeField
entry := nodeVal.Field(i).Interface().(node.Entry)
myField.Source = entry.Source()
myField.Value = entry.Get()
myField.Print = entry.Print()
fieldMap[nodeType.Field(i).Name] = &myField
case reflect.TypeOf([]string{}):
var myField wwapiv1.NodeField
entry := nodeVal.Field(i).Interface().([]string)
if len(entry) == 0 {
myField.Value = node.NoValue
myField.Print = node.NoValue
myField.Source = node.NoValue
} else {
myField.Value = strings.Join(entry, ",")
myField.Print = strings.Join(entry, ",")
myField.Source = node.NoValue
fieldMap[nodeType.Field(i).Name] = &myField
}
case reflect.TypeOf((*node.KernelEntry)(nil)):
entry := nodeVal.Field(i).Elem().Interface().(node.KernelEntry)
kernelMap := GetFields(entry)
for key, val := range kernelMap {
fieldMap["KernelEntry:"+key] = val
}
case reflect.TypeOf((*node.IpmiEntry)(nil)):
entry := nodeVal.Field(i).Elem().Interface().(node.IpmiEntry)
kernelMap := GetFields(entry)
for key, val := range kernelMap {
fieldMap["IpmiEntry:"+key] = val
}
case reflect.TypeOf(map[string]*node.Entry(nil)):
keyMap := nodeVal.Field(i).Interface().(map[string]*node.Entry)
for key, entr := range keyMap {
var myField wwapiv1.NodeField
myField.Source = entr.Source()
myField.Value = entr.Get()
myField.Print = entr.Print()
fieldMap["key:"+key] = &myField
}
case reflect.TypeOf(map[string]*node.NetDevEntry(nil)):
netMap := nodeVal.Field(i).Interface().(map[string]*node.NetDevEntry)
for net, netdev := range netMap {
netMapEntr := GetFields(*netdev)
for key, val := range netMapEntr {
fieldMap["NetDevEntry:"+net+":"+key] = val
}
}
default:
panic(fmt.Sprintf("Can't handle: %s\n", nodeType.Field(i).Type))
}
}
return fieldMap
}

View File

@@ -8,9 +8,9 @@ import (
"os"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/util"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/hpcng/warewulf/pkg/hostlist"
"github.com/pkg/errors"
@@ -270,15 +270,51 @@ func NodeSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (nodeDB
wwlog.Error(fmt.Sprintf("%v", err.Error()))
return
}
wwlog.Verbose("Node: %s, Deleting network device: %s", n.Id.Get(), set.NetdevDelete)
delete(n.NetDevs, set.NetdevDelete)
}
if set.PartitionDelete != "" {
deletedPart := false
for diskname, disk := range n.Disks {
if _, ok := disk.Partitions[set.PartitionDelete]; ok {
wwlog.Verbose("Node: %s, on disk %, deleting partition: %s", n.Id.Get(), diskname, set.PartitionDelete)
deletedPart = true
delete(disk.Partitions, set.PartitionDelete)
}
if !deletedPart {
wwlog.Error(fmt.Sprintf("%v", err.Error()))
err = fmt.Errorf("partition doesn't exist: %s", set.PartitionDelete)
return
}
}
}
if set.DiskDelete != "" {
if _, ok := n.Disks[set.DiskDelete]; !ok {
err = fmt.Errorf("disk doesn't exist: %s", set.DiskDelete)
wwlog.Error(fmt.Sprintf("%v", err.Error()))
return
}
wwlog.Verbose("Node: %s, deleting disk: %s", n.Id.Get(), set.DiskDelete)
delete(n.Disks, set.DiskDelete)
}
if set.FilesystemDelete != "" {
if _, ok := n.FileSystems[set.FilesystemDelete]; !ok {
err = fmt.Errorf("filesystem doesn't exist: %s", set.FilesystemDelete)
wwlog.Error(fmt.Sprintf("%v", err.Error()))
return
}
wwlog.Verbose("Node: %s, deleting filesystem: %s", n.Id.Get(), set.FilesystemDelete)
delete(n.FileSystems, set.FilesystemDelete)
}
for _, key := range nodeConf.TagsDel {
wwlog.Debug("deleting tag %s", key)
delete(n.Tags, key)
}
for _, key := range nodeConf.Ipmi.TagsDel {
delete(n.Ipmi.Tags, key)
if nodeConf.Ipmi != nil {
for _, key := range nodeConf.Ipmi.TagsDel {
wwlog.Debug("deleting Ipmi tag %s", key)
delete(n.Ipmi.Tags, key)
}
}
for net := range nodeConf.NetDevs {
for _, key := range nodeConf.NetDevs[net].TagsDel {

View File

@@ -2,7 +2,6 @@ package apiprofile
import (
"fmt"
"reflect"
"sort"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
@@ -30,99 +29,14 @@ func ProfileList(ShowOpt *wwapiv1.GetProfileList) (profileList wwapiv1.ProfileLi
sort.Slice(profiles, func(i, j int) bool {
return profiles[i].Id.Get() < profiles[j].Id.Get()
})
if ShowOpt.ShowAll {
if ShowOpt.ShowAll || ShowOpt.ShowFullAll {
for _, p := range profiles {
profileList.Output = append(profileList.Output,
fmt.Sprintf("%s=%s=%s=%s", "PROFILE", "FIELD", "PROFILE", "VALUE"))
nType := reflect.TypeOf(p)
nVal := reflect.ValueOf(p)
nConfType := reflect.TypeOf(node.NodeConf{})
for i := 0; i < nType.NumField(); i++ {
var fieldName, fieldSource, fieldVal string
nConfField, ok := nConfType.FieldByName(nType.Field(i).Name)
if ok {
fieldName = nConfField.Tag.Get("lopt")
} else {
fieldName = nType.Field(i).Name
}
if nType.Field(i).Type == reflect.TypeOf(node.Entry{}) {
entr := nVal.Field(i).Interface().(node.Entry)
fieldSource = entr.Source()
fieldVal = entr.Print()
profileList.Output = append(profileList.Output,
fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), fieldName, fieldSource, fieldVal))
} else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.Entry{}) {
entrMap := nVal.Field(i).Interface().(map[string]*node.Entry)
for key, val := range entrMap {
profileList.Output = append(profileList.Output,
fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), key, val.Source(), val.Print()))
}
} else if nType.Field(i).Type == reflect.TypeOf(map[string]*node.NetDevEntry{}) {
netDevs := nVal.Field(i).Interface().(map[string]*node.NetDevEntry)
for netName, netWork := range netDevs {
netInfoType := reflect.TypeOf(*netWork)
netInfoVal := reflect.ValueOf(*netWork)
netConfType := reflect.TypeOf(node.NetDevs{})
for j := 0; j < netInfoType.NumField(); j++ {
netConfField, ok := netConfType.FieldByName(netInfoType.Field(j).Name)
if ok {
if netConfField.Tag.Get("lopt") != "nettagadd" {
fieldName = netName + ":" + netConfField.Tag.Get("lopt")
} else {
fieldName = netName + ":tag"
}
} else {
fieldName = netName + ":" + netInfoType.Field(j).Name
}
if netInfoType.Field(j).Type == reflect.TypeOf(node.Entry{}) {
entr := netInfoVal.Field(j).Interface().(node.Entry)
fieldSource = entr.Source()
fieldVal = entr.Print()
// only print fields with lopt
if netConfField.Tag.Get("lopt") != "" {
profileList.Output = append(profileList.Output,
fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), fieldName, fieldSource, fieldVal))
}
} else if netInfoType.Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) {
for key, val := range netInfoVal.Field(j).Interface().(map[string]*node.Entry) {
keyfieldName := fieldName + ":" + key
fieldSource = val.Source()
fieldVal = val.Print()
profileList.Output = append(profileList.Output,
fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), keyfieldName, fieldSource, fieldVal))
}
}
}
}
} else if nType.Field(i).Type.Kind() == reflect.Ptr {
nestInfoType := reflect.TypeOf(nVal.Field(i).Interface())
nestInfoVal := reflect.ValueOf(nVal.Field(i).Interface())
// nestConfType := reflect.TypeOf(nConfField.Type.Elem().FieldByName())
for j := 0; j < nestInfoType.Elem().NumField(); j++ {
nestConfField, ok := nConfField.Type.Elem().FieldByName(nestInfoType.Elem().Field(j).Name)
if ok {
fieldName = nestConfField.Tag.Get("lopt")
} else {
fieldName = nestInfoType.Elem().Field(j).Name
}
if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(node.Entry{}) {
entr := nestInfoVal.Elem().Field(j).Interface().(node.Entry)
fieldSource = entr.Source()
fieldVal = entr.Print()
profileList.Output = append(profileList.Output,
fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), fieldName, fieldSource, fieldVal))
} else if nestInfoType.Elem().Field(j).Type == reflect.TypeOf(map[string]*node.Entry{}) {
for key, val := range nestInfoVal.Elem().Field(j).Interface().(map[string]*node.Entry) {
fieldName = fieldName + ":" + key
fieldSource = val.Source()
fieldVal = val.Print()
profileList.Output = append(profileList.Output,
fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), fieldName, fieldSource, fieldVal))
}
}
}
}
fields := p.GetFields(ShowOpt.ShowFullAll)
for _, f := range fields {
profileList.Output = append(profileList.Output,
fmt.Sprintf("%s=%s=%s=%s", p.Id.Print(), f.Field, f.Source, f.Value))
}
}
} else {

View File

@@ -14,7 +14,7 @@ import (
)
// NodeSet is the wwapiv1 implmentation for updating nodeinfo fields.
func ProfileSet(set *wwapiv1.NodeSetParameter) (err error) {
func ProfileSet(set *wwapiv1.ProfileSetParameter) (err error) {
if set == nil {
return fmt.Errorf("NodeAddParameter is nil")
}
@@ -31,7 +31,7 @@ func ProfileSet(set *wwapiv1.NodeSetParameter) (err error) {
// Output to the console if console is true.
// TODO: Determine if the console switch does wwlog or not.
// - console may end up being textOutput?
func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (nodeDB node.NodeYaml, profileCount uint, err error) {
func ProfileSetParameterCheck(set *wwapiv1.ProfileSetParameter, console bool) (nodeDB node.NodeYaml, profileCount uint, err error) {
if set == nil {
err = fmt.Errorf("profile set parameter is nil")
if console {
@@ -40,7 +40,7 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node
}
}
if set.NodeNames == nil {
if set.ProfileNames == nil {
err = fmt.Errorf("profile set parameter: ProfileNames is nil")
if console {
fmt.Printf("%v\n", err)
@@ -62,7 +62,7 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node
// Note: This does not do expansion on the nodes.
if set.AllNodes || (len(set.NodeNames) == 0) {
if set.AllProfiles || (len(set.ProfileNames) == 0) {
if console {
fmt.Printf("\n*** WARNING: This command will modify all profiles! ***\n\n")
}
@@ -82,12 +82,8 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node
}
for _, p := range profiles {
if util.InSlice(set.NodeNames, p.Id.Get()) {
if util.InSlice(set.ProfileNames, p.Id.Get()) {
wwlog.Verbose("Evaluating profile: %s", p.Id.Get())
// Fix issue: https://github.com/hpcng/warewulf/issues/661
if p.Id.Get() == "default" && len(p.NetDevs) == 0 {
set.NetdevDelete = p.Id.Get()
}
p.SetFrom(&pConf)
if set.NetdevDelete != "" {
if _, ok := p.NetDevs[set.NetdevDelete]; !ok {
@@ -98,6 +94,40 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node
wwlog.Verbose("Profile: %s, Deleting network device: %s", p.Id.Get(), set.NetdevDelete)
delete(p.NetDevs, set.NetdevDelete)
}
if set.PartitionDelete != "" {
deletedPart := false
for diskname, disk := range p.Disks {
if _, ok := disk.Partitions[set.PartitionDelete]; ok {
wwlog.Verbose("Node: %s, on disk %, deleting partition: %s", p.Id.Get(), diskname, set.PartitionDelete)
deletedPart = true
delete(disk.Partitions, set.PartitionDelete)
}
if !deletedPart {
wwlog.Error(fmt.Sprintf("%v", err.Error()))
err = fmt.Errorf("partition doesn't exist: %s", set.PartitionDelete)
return
}
}
}
if set.DiskDelete != "" {
if _, ok := p.Disks[set.DiskDelete]; !ok {
err = fmt.Errorf("disk doesn't exist: %s", set.DiskDelete)
wwlog.Error(fmt.Sprintf("%v", err.Error()))
return
}
wwlog.Verbose("Node: %s, deleting disk: %s", p.Id.Get(), set.DiskDelete)
delete(p.Disks, set.DiskDelete)
}
if set.FilesystemDelete != "" {
if _, ok := p.FileSystems[set.FilesystemDelete]; !ok {
err = fmt.Errorf("disk doesn't exist: %s", set.FilesystemDelete)
wwlog.Error(fmt.Sprintf("%v", err.Error()))
return
}
wwlog.Verbose("Node: %s, deleting filesystem: %s", p.Id.Get(), set.FilesystemDelete)
delete(p.FileSystems, set.FilesystemDelete)
}
for _, key := range pConf.TagsDel {
delete(p.Tags, key)
}
@@ -127,7 +157,7 @@ func ProfileSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (node
Adds a new profile with the given name
*/
func AddProfile(nsp *wwapiv1.NodeSetParameter) error {
func AddProfile(nsp *wwapiv1.ProfileSetParameter) error {
if nsp == nil {
return fmt.Errorf("NodeSetParameter is nill")
}
@@ -137,8 +167,8 @@ func AddProfile(nsp *wwapiv1.NodeSetParameter) error {
return errors.Wrap(err, "Could not open database")
}
if util.InSlice(nodeDB.ListAllProfiles(), nsp.NodeNames[0]) {
return errors.New(fmt.Sprintf("profile with name %s allready exists", nsp.NodeNames[0]))
if util.InSlice(nodeDB.ListAllProfiles(), nsp.ProfileNames[0]) {
return errors.New(fmt.Sprintf("profile with name %s allready exists", nsp.ProfileNames[0]))
}
var nodeConf node.NodeConf
@@ -147,7 +177,7 @@ func AddProfile(nsp *wwapiv1.NodeSetParameter) error {
return errors.Wrap(err, "failed to decode nodeConf")
}
n, err := nodeDB.AddProfile(nsp.NodeNames[0])
n, err := nodeDB.AddProfile(nsp.ProfileNames[0])
if err != nil {
return errors.Wrap(err, "failed to add node")
}

View File

@@ -32,6 +32,13 @@ message ContainerDeleteParameter {
repeated string containerNames = 1;
}
// ContainerCopyParameter contains 2 inputs : first one for the source container name and second one for the duplicated container name.
message ContainerCopyParameter {
string containerSource = 1;
string containerDestination = 2;
}
// ContainerImportParameter has all input for importing a container.
message ContainerImportParameter{
string source = 1; // container source uri
@@ -118,6 +125,7 @@ message GetNodeList {
Network = 2;
Long = 3;
All = 4;
FullAll = 5;
}
ListType type = 7;
repeated string Nodes = 8;
@@ -131,7 +139,8 @@ message NodeList {
// Request a profile list view
message GetProfileList {
bool ShowAll = 1;
repeated string Profiles = 2;
bool ShowFullAll = 2;
repeated string Profiles = 3;
}
// Get the formated output as string
message ProfileList {
@@ -169,13 +178,26 @@ message NodeDeleteParameter {
// by Warewulf.
message NodeSetParameter {
string nodeConfYaml = 1;
string container = 2;
string netdevDelete = 14;
bool allNodes = 27;
bool force = 31;
repeated string nodeNames = 39;
string netdevDelete = 2;
string diskDelete = 3;
string partitionDelete = 4;
string filesystemDelete = 5;
bool allNodes = 6;
bool force = 7;
repeated string nodeNames = 8;
}
// ProfileSetParameter contains all fields for updating aspects of profiles managed. Basically a copy of NodeSetParameters
// by Warewulf.
message ProfileSetParameter {
string nodeConfYaml = 1;
string netdevDelete = 2;
string diskDelete = 3;
string partitionDelete = 4;
string filesystemDelete = 5;
bool allProfiles = 6;
bool force = 7;
repeated string profileNames = 8;
}
// NodeStatus contains information about the imaging status per node.
message NodeStatus {
string nodeName = 1; // Name (Id) of the node.
@@ -224,6 +246,13 @@ service WWApi {
};
}
rpc ContainerCopy(ContainerCopyParameter) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/v1/containercopy"
body: "*"
};
}
// ContainerImport imports a container to Warewulf.
rpc ContainerImport(ContainerImportParameter) returns (ContainerListResponse) {
option(google.api.http) = {

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,6 @@ import (
"io"
"net/http"
"github.com/golang/protobuf/ptypes/empty"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
@@ -22,6 +21,7 @@ import (
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
)
// Suppress "imported and not used" errors
@@ -102,6 +102,40 @@ func local_request_WWApi_ContainerDelete_0(ctx context.Context, marshaler runtim
}
func request_WWApi_ContainerCopy_0(ctx context.Context, marshaler runtime.Marshaler, client WWApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ContainerCopyParameter
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ContainerCopy(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WWApi_ContainerCopy_0(ctx context.Context, marshaler runtime.Marshaler, server WWApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ContainerCopyParameter
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ContainerCopy(ctx, &protoReq)
return msg, metadata, err
}
func request_WWApi_ContainerImport_0(ctx context.Context, marshaler runtime.Marshaler, client WWApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ContainerImportParameter
var metadata runtime.ServerMetadata
@@ -137,7 +171,7 @@ func local_request_WWApi_ContainerImport_0(ctx context.Context, marshaler runtim
}
func request_WWApi_ContainerList_0(ctx context.Context, marshaler runtime.Marshaler, client WWApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq empty.Empty
var protoReq emptypb.Empty
var metadata runtime.ServerMetadata
msg, err := client.ContainerList(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
@@ -146,7 +180,7 @@ func request_WWApi_ContainerList_0(ctx context.Context, marshaler runtime.Marsha
}
func local_request_WWApi_ContainerList_0(ctx context.Context, marshaler runtime.Marshaler, server WWApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq empty.Empty
var protoReq emptypb.Empty
var metadata runtime.ServerMetadata
msg, err := server.ContainerList(ctx, &protoReq)
@@ -367,7 +401,7 @@ func local_request_WWApi_NodeStatus_0(ctx context.Context, marshaler runtime.Mar
}
func request_WWApi_Version_0(ctx context.Context, marshaler runtime.Marshaler, client WWApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq empty.Empty
var protoReq emptypb.Empty
var metadata runtime.ServerMetadata
msg, err := client.Version(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
@@ -376,7 +410,7 @@ func request_WWApi_Version_0(ctx context.Context, marshaler runtime.Marshaler, c
}
func local_request_WWApi_Version_0(ctx context.Context, marshaler runtime.Marshaler, server WWApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq empty.Empty
var protoReq emptypb.Empty
var metadata runtime.ServerMetadata
msg, err := server.Version(ctx, &protoReq)
@@ -396,20 +430,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerBuild", runtime.WithHTTPPathPattern("/v1/containerbuild"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerBuild", runtime.WithHTTPPathPattern("/v1/containerbuild"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_ContainerBuild_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_ContainerBuild_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerBuild_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerBuild_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -419,20 +455,47 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerDelete", runtime.WithHTTPPathPattern("/v1/container"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerDelete", runtime.WithHTTPPathPattern("/v1/container"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_ContainerDelete_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_ContainerDelete_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerDelete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerDelete_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WWApi_ContainerCopy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerCopy", runtime.WithHTTPPathPattern("/v1/containercopy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_ContainerCopy_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerCopy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -442,20 +505,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerImport", runtime.WithHTTPPathPattern("/v1/container"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerImport", runtime.WithHTTPPathPattern("/v1/container"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_ContainerImport_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_ContainerImport_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerImport_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerImport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -465,20 +530,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerList", runtime.WithHTTPPathPattern("/v1/container"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerList", runtime.WithHTTPPathPattern("/v1/container"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_ContainerList_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_ContainerList_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerList_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerList_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -488,20 +555,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerShow", runtime.WithHTTPPathPattern("/v1/containershow"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerShow", runtime.WithHTTPPathPattern("/v1/containershow"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_ContainerShow_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_ContainerShow_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerShow_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerShow_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -511,20 +580,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeAdd", runtime.WithHTTPPathPattern("/v1/node"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeAdd", runtime.WithHTTPPathPattern("/v1/node"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_NodeAdd_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_NodeAdd_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeAdd_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeAdd_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -534,20 +605,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeDelete", runtime.WithHTTPPathPattern("/v1/node"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeDelete", runtime.WithHTTPPathPattern("/v1/node"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_NodeDelete_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_NodeDelete_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeDelete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeDelete_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -557,20 +630,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeList", runtime.WithHTTPPathPattern("/v1/node"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeList", runtime.WithHTTPPathPattern("/v1/node"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_NodeList_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_NodeList_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeList_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeList_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -580,20 +655,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeSet", runtime.WithHTTPPathPattern("/v1/nodeset"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeSet", runtime.WithHTTPPathPattern("/v1/nodeset"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_NodeSet_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_NodeSet_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeSet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeSet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -603,20 +680,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeStatus", runtime.WithHTTPPathPattern("/v1/nodestatus"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeStatus", runtime.WithHTTPPathPattern("/v1/nodestatus"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_NodeStatus_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_NodeStatus_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -626,20 +705,22 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/Version", runtime.WithHTTPPathPattern("/version"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/Version", runtime.WithHTTPPathPattern("/version"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_Version_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_WWApi_Version_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_Version_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_Version_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -649,7 +730,7 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
// RegisterWWApiHandlerFromEndpoint is same as RegisterWWApiHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterWWApiHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
@@ -688,19 +769,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerBuild", runtime.WithHTTPPathPattern("/v1/containerbuild"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerBuild", runtime.WithHTTPPathPattern("/v1/containerbuild"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_ContainerBuild_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_ContainerBuild_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerBuild_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerBuild_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -708,19 +791,43 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerDelete", runtime.WithHTTPPathPattern("/v1/container"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerDelete", runtime.WithHTTPPathPattern("/v1/container"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_ContainerDelete_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_ContainerDelete_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerDelete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerDelete_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WWApi_ContainerCopy_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerCopy", runtime.WithHTTPPathPattern("/v1/containercopy"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_ContainerCopy_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerCopy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -728,19 +835,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerImport", runtime.WithHTTPPathPattern("/v1/container"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerImport", runtime.WithHTTPPathPattern("/v1/container"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_ContainerImport_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_ContainerImport_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerImport_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerImport_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -748,19 +857,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerList", runtime.WithHTTPPathPattern("/v1/container"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerList", runtime.WithHTTPPathPattern("/v1/container"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_ContainerList_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_ContainerList_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerList_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerList_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -768,19 +879,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerShow", runtime.WithHTTPPathPattern("/v1/containershow"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerShow", runtime.WithHTTPPathPattern("/v1/containershow"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_ContainerShow_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_ContainerShow_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerShow_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_ContainerShow_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -788,19 +901,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeAdd", runtime.WithHTTPPathPattern("/v1/node"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeAdd", runtime.WithHTTPPathPattern("/v1/node"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_NodeAdd_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_NodeAdd_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeAdd_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeAdd_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -808,19 +923,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeDelete", runtime.WithHTTPPathPattern("/v1/node"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeDelete", runtime.WithHTTPPathPattern("/v1/node"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_NodeDelete_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_NodeDelete_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeDelete_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeDelete_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -828,19 +945,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeList", runtime.WithHTTPPathPattern("/v1/node"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeList", runtime.WithHTTPPathPattern("/v1/node"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_NodeList_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_NodeList_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeList_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeList_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -848,19 +967,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeSet", runtime.WithHTTPPathPattern("/v1/nodeset"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeSet", runtime.WithHTTPPathPattern("/v1/nodeset"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_NodeSet_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_NodeSet_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeSet_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeSet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -868,19 +989,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeStatus", runtime.WithHTTPPathPattern("/v1/nodestatus"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/NodeStatus", runtime.WithHTTPPathPattern("/v1/nodestatus"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_NodeStatus_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_NodeStatus_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_NodeStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_NodeStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -888,19 +1011,21 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/Version", runtime.WithHTTPPathPattern("/version"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/Version", runtime.WithHTTPPathPattern("/version"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_Version_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_WWApi_Version_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_Version_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_WWApi_Version_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@@ -912,6 +1037,8 @@ var (
pattern_WWApi_ContainerDelete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "container"}, ""))
pattern_WWApi_ContainerCopy_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "containercopy"}, ""))
pattern_WWApi_ContainerImport_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "container"}, ""))
pattern_WWApi_ContainerList_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "container"}, ""))
@@ -936,6 +1063,8 @@ var (
forward_WWApi_ContainerDelete_0 = runtime.ForwardResponseMessage
forward_WWApi_ContainerCopy_0 = runtime.ForwardResponseMessage
forward_WWApi_ContainerImport_0 = runtime.ForwardResponseMessage
forward_WWApi_ContainerList_0 = runtime.ForwardResponseMessage

View File

@@ -1,17 +1,17 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.9.2
// - protoc v4.24.0
// source: routes.proto
package wwapiv1
import (
context "context"
empty "github.com/golang/protobuf/ptypes/empty"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
// This is a compile-time assertion to ensure that this generated file
@@ -26,18 +26,19 @@ type WWApiClient interface {
// ContainerBuild builds zero or more containers.
ContainerBuild(ctx context.Context, in *ContainerBuildParameter, opts ...grpc.CallOption) (*ContainerListResponse, error)
// ContainerDelete removes one or more container from Warewulf management.
ContainerDelete(ctx context.Context, in *ContainerDeleteParameter, opts ...grpc.CallOption) (*empty.Empty, error)
ContainerDelete(ctx context.Context, in *ContainerDeleteParameter, opts ...grpc.CallOption) (*emptypb.Empty, error)
ContainerCopy(ctx context.Context, in *ContainerCopyParameter, opts ...grpc.CallOption) (*emptypb.Empty, error)
// ContainerImport imports a container to Warewulf.
ContainerImport(ctx context.Context, in *ContainerImportParameter, opts ...grpc.CallOption) (*ContainerListResponse, error)
// ContainerList lists ContainerInfo for each container.
ContainerList(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*ContainerListResponse, error)
ContainerList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ContainerListResponse, error)
// ContainerShow lists ContainerShow for each container.
ContainerShow(ctx context.Context, in *ContainerShowParameter, opts ...grpc.CallOption) (*ContainerShowResponse, error)
// NodeAdd adds one or more nodes for management by Warewulf and returns
// the added nodes. Node fields may be shimmed in per profiles.
NodeAdd(ctx context.Context, in *NodeAddParameter, opts ...grpc.CallOption) (*NodeListResponse, error)
// NodeDelete removes one or more nodes from Warewulf management.
NodeDelete(ctx context.Context, in *NodeDeleteParameter, opts ...grpc.CallOption) (*empty.Empty, error)
NodeDelete(ctx context.Context, in *NodeDeleteParameter, opts ...grpc.CallOption) (*emptypb.Empty, error)
// NodeList lists some or all nodes managed by Warewulf.
NodeList(ctx context.Context, in *NodeNames, opts ...grpc.CallOption) (*NodeListResponse, error)
// NodeSet updates node fields for one or more nodes.
@@ -47,7 +48,7 @@ type WWApiClient interface {
NodeStatus(ctx context.Context, in *NodeNames, opts ...grpc.CallOption) (*NodeStatusResponse, error)
// Version returns the wwapi version, the api prefix, and the Warewulf
// version. This is also useful for testing if the service is up.
Version(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*VersionResponse, error)
Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*VersionResponse, error)
}
type wWApiClient struct {
@@ -67,8 +68,8 @@ func (c *wWApiClient) ContainerBuild(ctx context.Context, in *ContainerBuildPara
return out, nil
}
func (c *wWApiClient) ContainerDelete(ctx context.Context, in *ContainerDeleteParameter, opts ...grpc.CallOption) (*empty.Empty, error) {
out := new(empty.Empty)
func (c *wWApiClient) ContainerDelete(ctx context.Context, in *ContainerDeleteParameter, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/ContainerDelete", in, out, opts...)
if err != nil {
return nil, err
@@ -76,6 +77,15 @@ func (c *wWApiClient) ContainerDelete(ctx context.Context, in *ContainerDeletePa
return out, nil
}
func (c *wWApiClient) ContainerCopy(ctx context.Context, in *ContainerCopyParameter, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/ContainerCopy", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *wWApiClient) ContainerImport(ctx context.Context, in *ContainerImportParameter, opts ...grpc.CallOption) (*ContainerListResponse, error) {
out := new(ContainerListResponse)
err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/ContainerImport", in, out, opts...)
@@ -85,7 +95,7 @@ func (c *wWApiClient) ContainerImport(ctx context.Context, in *ContainerImportPa
return out, nil
}
func (c *wWApiClient) ContainerList(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*ContainerListResponse, error) {
func (c *wWApiClient) ContainerList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ContainerListResponse, error) {
out := new(ContainerListResponse)
err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/ContainerList", in, out, opts...)
if err != nil {
@@ -112,8 +122,8 @@ func (c *wWApiClient) NodeAdd(ctx context.Context, in *NodeAddParameter, opts ..
return out, nil
}
func (c *wWApiClient) NodeDelete(ctx context.Context, in *NodeDeleteParameter, opts ...grpc.CallOption) (*empty.Empty, error) {
out := new(empty.Empty)
func (c *wWApiClient) NodeDelete(ctx context.Context, in *NodeDeleteParameter, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/NodeDelete", in, out, opts...)
if err != nil {
return nil, err
@@ -148,7 +158,7 @@ func (c *wWApiClient) NodeStatus(ctx context.Context, in *NodeNames, opts ...grp
return out, nil
}
func (c *wWApiClient) Version(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*VersionResponse, error) {
func (c *wWApiClient) Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*VersionResponse, error) {
out := new(VersionResponse)
err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/Version", in, out, opts...)
if err != nil {
@@ -164,18 +174,19 @@ type WWApiServer interface {
// ContainerBuild builds zero or more containers.
ContainerBuild(context.Context, *ContainerBuildParameter) (*ContainerListResponse, error)
// ContainerDelete removes one or more container from Warewulf management.
ContainerDelete(context.Context, *ContainerDeleteParameter) (*empty.Empty, error)
ContainerDelete(context.Context, *ContainerDeleteParameter) (*emptypb.Empty, error)
ContainerCopy(context.Context, *ContainerCopyParameter) (*emptypb.Empty, error)
// ContainerImport imports a container to Warewulf.
ContainerImport(context.Context, *ContainerImportParameter) (*ContainerListResponse, error)
// ContainerList lists ContainerInfo for each container.
ContainerList(context.Context, *empty.Empty) (*ContainerListResponse, error)
ContainerList(context.Context, *emptypb.Empty) (*ContainerListResponse, error)
// ContainerShow lists ContainerShow for each container.
ContainerShow(context.Context, *ContainerShowParameter) (*ContainerShowResponse, error)
// NodeAdd adds one or more nodes for management by Warewulf and returns
// the added nodes. Node fields may be shimmed in per profiles.
NodeAdd(context.Context, *NodeAddParameter) (*NodeListResponse, error)
// NodeDelete removes one or more nodes from Warewulf management.
NodeDelete(context.Context, *NodeDeleteParameter) (*empty.Empty, error)
NodeDelete(context.Context, *NodeDeleteParameter) (*emptypb.Empty, error)
// NodeList lists some or all nodes managed by Warewulf.
NodeList(context.Context, *NodeNames) (*NodeListResponse, error)
// NodeSet updates node fields for one or more nodes.
@@ -185,7 +196,7 @@ type WWApiServer interface {
NodeStatus(context.Context, *NodeNames) (*NodeStatusResponse, error)
// Version returns the wwapi version, the api prefix, and the Warewulf
// version. This is also useful for testing if the service is up.
Version(context.Context, *empty.Empty) (*VersionResponse, error)
Version(context.Context, *emptypb.Empty) (*VersionResponse, error)
mustEmbedUnimplementedWWApiServer()
}
@@ -196,13 +207,16 @@ type UnimplementedWWApiServer struct {
func (UnimplementedWWApiServer) ContainerBuild(context.Context, *ContainerBuildParameter) (*ContainerListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ContainerBuild not implemented")
}
func (UnimplementedWWApiServer) ContainerDelete(context.Context, *ContainerDeleteParameter) (*empty.Empty, error) {
func (UnimplementedWWApiServer) ContainerDelete(context.Context, *ContainerDeleteParameter) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ContainerDelete not implemented")
}
func (UnimplementedWWApiServer) ContainerCopy(context.Context, *ContainerCopyParameter) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ContainerCopy not implemented")
}
func (UnimplementedWWApiServer) ContainerImport(context.Context, *ContainerImportParameter) (*ContainerListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ContainerImport not implemented")
}
func (UnimplementedWWApiServer) ContainerList(context.Context, *empty.Empty) (*ContainerListResponse, error) {
func (UnimplementedWWApiServer) ContainerList(context.Context, *emptypb.Empty) (*ContainerListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ContainerList not implemented")
}
func (UnimplementedWWApiServer) ContainerShow(context.Context, *ContainerShowParameter) (*ContainerShowResponse, error) {
@@ -211,7 +225,7 @@ func (UnimplementedWWApiServer) ContainerShow(context.Context, *ContainerShowPar
func (UnimplementedWWApiServer) NodeAdd(context.Context, *NodeAddParameter) (*NodeListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NodeAdd not implemented")
}
func (UnimplementedWWApiServer) NodeDelete(context.Context, *NodeDeleteParameter) (*empty.Empty, error) {
func (UnimplementedWWApiServer) NodeDelete(context.Context, *NodeDeleteParameter) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method NodeDelete not implemented")
}
func (UnimplementedWWApiServer) NodeList(context.Context, *NodeNames) (*NodeListResponse, error) {
@@ -223,7 +237,7 @@ func (UnimplementedWWApiServer) NodeSet(context.Context, *NodeSetParameter) (*No
func (UnimplementedWWApiServer) NodeStatus(context.Context, *NodeNames) (*NodeStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NodeStatus not implemented")
}
func (UnimplementedWWApiServer) Version(context.Context, *empty.Empty) (*VersionResponse, error) {
func (UnimplementedWWApiServer) Version(context.Context, *emptypb.Empty) (*VersionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Version not implemented")
}
func (UnimplementedWWApiServer) mustEmbedUnimplementedWWApiServer() {}
@@ -275,6 +289,24 @@ func _WWApi_ContainerDelete_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler)
}
func _WWApi_ContainerCopy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ContainerCopyParameter)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WWApiServer).ContainerCopy(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wwapi.v1.WWApi/ContainerCopy",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WWApiServer).ContainerCopy(ctx, req.(*ContainerCopyParameter))
}
return interceptor(ctx, in, info, handler)
}
func _WWApi_ContainerImport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ContainerImportParameter)
if err := dec(in); err != nil {
@@ -294,7 +326,7 @@ func _WWApi_ContainerImport_Handler(srv interface{}, ctx context.Context, dec fu
}
func _WWApi_ContainerList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(empty.Empty)
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
@@ -306,7 +338,7 @@ func _WWApi_ContainerList_Handler(srv interface{}, ctx context.Context, dec func
FullMethod: "/wwapi.v1.WWApi/ContainerList",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WWApiServer).ContainerList(ctx, req.(*empty.Empty))
return srv.(WWApiServer).ContainerList(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
@@ -420,7 +452,7 @@ func _WWApi_NodeStatus_Handler(srv interface{}, ctx context.Context, dec func(in
}
func _WWApi_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(empty.Empty)
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
@@ -432,7 +464,7 @@ func _WWApi_Version_Handler(srv interface{}, ctx context.Context, dec func(inter
FullMethod: "/wwapi.v1.WWApi/Version",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WWApiServer).Version(ctx, req.(*empty.Empty))
return srv.(WWApiServer).Version(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
@@ -452,6 +484,10 @@ var WWApi_ServiceDesc = grpc.ServiceDesc{
MethodName: "ContainerDelete",
Handler: _WWApi_ContainerDelete_Handler,
},
{
MethodName: "ContainerCopy",
Handler: _WWApi_ContainerCopy_Handler,
},
{
MethodName: "ContainerImport",
Handler: _WWApi_ContainerImport_Handler,

View File

@@ -24,7 +24,7 @@ func Hostfile() error {
nodeInfo := node.NewInfo()
hostname, _ := os.Hostname()
nodeInfo.Id.Set(hostname)
tstruct := overlay.InitStruct(nodeInfo)
tstruct := overlay.InitStruct(&nodeInfo)
buffer, backupFile, writeFile, err := overlay.RenderTemplateFile(
hostTemplate,
tstruct)

View File

@@ -37,7 +37,7 @@ func Build(name string, buildForce bool) error {
}
err := util.BuildFsImage(
"VNFS container " + name,
"VNFS container "+name,
rootfsPath,
imagePath,
[]string{"*"},

View File

@@ -16,243 +16,402 @@ import (
"github.com/pkg/errors"
)
type completeUserInfo struct {
Name string
UidHost int `access:"r,w"`
GidHost int `access:"r,w"`
UidCont int `access:"r,w"`
GidCont int `access:"r,w"`
FileListUid []string `access:"r,w"`
FileListGid []string `access:"r,w"`
}
type simpleUserInfo struct {
name string
uid int
gid int
}
const passwdPath = "/etc/passwd"
const groupPath = "/etc/group"
/*
sync the uids,gids from the host to the container
*/
// SyncUids updates the /etc/passwd and /etc/group files in the
// container identified by containerName by installing the equivalent
// files from the host and appending names only in the
// container. Files in the container are updated to match the new
// numeric id assignments.
//
// If showOnly is true, the container is not actually updated, but
// relevant log entries and output are generated.
//
// Any I/O errors are returned unmodified.
//
// A conflict arises if the container has an entry with the same id as
// an entry in the host and the host does not have an entry with the
// same name. In this case, an error is returned.
func SyncUids(containerName string, showOnly bool) error {
var userDb []completeUserInfo
passwdName := "/etc/passwd"
groupName := "/etc/group"
containerPath := RootFsDir(containerName)
containerPasswdPath := path.Join(containerPath, passwdPath)
containerGroupPath := path.Join(containerPath, groupPath)
// populate db with users from the host
hostUsers, err := createPasswdMap(passwdName)
if err != nil {
wwlog.Error("Could not open "+passwdName)
return err
}
for _, hostUser := range hostUsers {
userDb = append(userDb, completeUserInfo{Name: hostUser.name,
UidHost: hostUser.uid, GidHost: hostUser.gid, UidCont: -1, GidCont: -1})
}
passwdSync := make(syncDB)
if err := passwdSync.readFromHost(passwdPath); err != nil { return err }
if err := passwdSync.readFromContainer(containerPasswdPath); err != nil { return err }
if err := passwdSync.checkConflicts(); err != nil { return err }
if err := passwdSync.findUserFiles(containerPath); err != nil { return err }
// merge container users into db and track users that are only in the
// container
fullPath := RootFsDir(containerName)
containerUsers, err := createPasswdMap(path.Join(fullPath, passwdName))
if err != nil {
wwlog.Error("Could not open "+path.Join(fullPath, passwdName))
return err
}
var userOnlyCont []string
for _, containerUser := range containerUsers {
foundUser := false
for idxHost, user := range userDb {
if containerUser.name == user.Name {
foundUser = true
(&userDb[idxHost]).UidCont = containerUser.uid
(&userDb[idxHost]).GidCont = containerUser.gid
}
}
if !foundUser {
userDb = append(userDb, completeUserInfo{Name: containerUser.name,
UidHost: -1, GidHost: -1, UidCont: containerUser.uid, GidCont: containerUser.gid})
wwlog.Warn("user: %s:%v:%v not present on host", containerUser.name, containerUser.uid, containerUser.gid)
userOnlyCont = append(userOnlyCont, containerUser.name)
}
}
groupSync := make(syncDB)
if err := groupSync.readFromHost(groupPath); err != nil { return err }
if err := groupSync.readFromContainer(containerGroupPath); err != nil { return err }
if err := groupSync.checkConflicts(); err != nil { return err }
if err := groupSync.findGroupFiles(containerPath); err != nil { return err }
// detect users in the host and container with conflicting uids
for _, containerUser := range userDb {
if (containerUser.UidCont == -1 || containerUser.UidHost != -1) {
// containerUser is either not actually in the
// container or is also in the host
continue
}
for _, hostUser := range userDb {
if hostUser.UidHost == containerUser.UidCont {
wwlog.Warn("uid(%v) collision for host: %s and container: %s",
containerUser.UidCont, hostUser.Name, containerUser.Name)
return errors.New(fmt.Sprintf("user %s only present in container has same uid(%v) as user %s on host,\n"+
"add this user to /etc/passwd on host", containerUser.Name, containerUser.UidCont, hostUser.Name))
}
}
}
passwdSync.log("passwd")
groupSync.log("group")
if showOnly {
wwlog.Info("uid/gid not synced, run \nwwctl container syncuser --write %s\nto synchronize uid/gids.", containerName)
return nil
}
// create list of files which need changed ownerships in order to
// change them later what avoid uid/gid collisions
for idx, user := range userDb {
if (user.UidHost != user.UidCont && user.UidHost != -1) ||
(user.GidHost != user.GidCont && user.GidHost != -1 && user.UidHost != -1) {
wwlog.Verbose(fmt.Sprintf("host %s:%v:%v <-> container %s:%v:%v",
user.Name, user.UidHost, user.GidHost, user.Name, user.UidCont, user.GidCont))
err = filepath.Walk(fullPath, func(filePath string, info fs.FileInfo, err error) error {
// root is always good, if we fail to get UID/GID of a file
var uid, gid int
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
uid = int(stat.Uid)
gid = int(stat.Gid)
}
if uid == user.UidCont {
(&userDb[idx]).FileListUid = append((&userDb[idx]).FileListUid, filePath)
}
if gid == user.GidCont {
(&userDb[idx]).FileListGid = append((&userDb[idx]).FileListGid, filePath)
}
return nil
})
if err != nil {
return err
}
if passwdSync.needsSync() || groupSync.needsSync() {
wwlog.Info("uid/gid not synced: run `wwctl container syncuser --write %s`", containerName)
} else {
wwlog.Info("uid/gid already synced")
}
}
// change uids and gid of file
for _, user := range userDb {
if len(user.FileListUid) != 0 {
//fmt.Printf("uidList(%s): %v\n", user.Name, user.FileListUid)
for _, file := range user.FileListUid {
fsInfo, err := os.Stat(file)
if err != nil {
return err
}
var gid int
if stat, ok := fsInfo.Sys().(*syscall.Stat_t); ok {
gid = int(stat.Gid)
}
wwlog.Debug("%s chown(%v,%v)", file, user.UidHost, gid)
err = os.Chown(file, user.UidHost, gid)
if err != nil {
return err
}
}
}
if len(user.FileListGid) != 0 {
//fmt.Printf("gidList(%s): %v\n", user.Name, user.FileListGid)
for _, file := range user.FileListGid {
fsInfo, err := os.Stat(file)
if err != nil {
return err
}
var uid int
if stat, ok := fsInfo.Sys().(*syscall.Stat_t); ok {
uid = int(stat.Uid)
}
wwlog.Debug("%s chown(%v,%v)", file, user.UidHost, uid)
// only chown files and dirs
if fsInfo.IsDir() && fsInfo.Mode().IsRegular() {
err = os.Chown(file, uid, user.GidHost)
if err != nil {
return err
}
}
}
} else {
if err := passwdSync.chownUserFiles(); err != nil { return err }
if err := groupSync.chownGroupFiles(); err != nil { return err }
if err := passwdSync.update(containerPasswdPath, passwdPath); err != nil { return err }
if err := groupSync.update(containerGroupPath, groupPath); err != nil { return err }
wwlog.Info("uid/gid synced for container %s", containerName)
}
return nil
}
// A syncDB maps user or group names to syncInfo instances, which
// correlate IDs between host and container and track affected
// files. This can be used for either /etc/passwd or /etc/group IDs.
type syncDB map[string]syncInfo
// checkConflicts inspects a syncDB map for irreconcilable
// conflicts. A conflict arises if the container has an entry with the
// same id as an entry in the host and the host does not have an entry
// with the same name.
func (db syncDB) checkConflicts() error {
for nameInContainer, containerIds := range db {
if (!containerIds.inContainer() || containerIds.inHost()) {
continue
}
}
// get the entries for the passwd/group file before copy over
passwdEntries, err := getEntires(path.Join(fullPath, passwdName), userOnlyCont)
if err != nil {
return err
}
// implicitly assuming that users/groups which only exists on the host have the same name
groupEntries, err := getEntires(path.Join(fullPath, groupName), userOnlyCont)
if err != nil {
return err
}
if err = os.Remove(path.Join(fullPath, passwdName)); err != nil {
return err
}
if err = os.Remove(path.Join(fullPath, groupName)); err != nil {
return err
}
if err = util.CopyFile(passwdName, path.Join(fullPath, passwdName)); err != nil {
return err
}
if err = util.CopyFile(groupName, path.Join(fullPath, groupName)); err != nil {
return err
}
if err = util.AppendLines(path.Join(fullPath, passwdName), passwdEntries); err != nil {
return err
}
if err = util.AppendLines(path.Join(fullPath, groupName), groupEntries); err != nil {
return err
for nameInHost, hostIds := range db {
if hostIds.HostID == containerIds.ContainerID {
errorMsg := fmt.Sprintf("id(%v) collision: host(%s) container(%s)", containerIds.ContainerID, nameInHost, nameInContainer)
wwlog.Error(errorMsg)
wwlog.Error("add %s to host to resolve conflict", nameInContainer)
return errors.New(errorMsg)
}
}
}
return nil
}
/*
creates simple user db []simpleUserInfo for a /etc/{passwd|group} file
*/
func createPasswdMap(fileName string) ([]simpleUserInfo, error) {
var nameDb []simpleUserInfo
file, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer file.Close()
fileScanner := bufio.NewScanner(file)
for fileScanner.Scan() {
line := fileScanner.Text()
entries := strings.Split(line, ":")
name := entries[0]
uid, err := strconv.Atoi(entries[2])
if err != nil {
wwlog.Warn("could not parse uid(%s) for %s", entries[2], name)
}
gid, err := strconv.Atoi(entries[3])
if err != nil {
wwlog.Warn("could not parse gid(%s) for %s", entries[2], name)
}
if name != "" {
nameDb = append(nameDb, simpleUserInfo{name: name, uid: uid, gid: gid})
}
// log generates debug and verbose logs inspecting a syncDB map.
//
// The provided prefix is prepended to log entries to provide context
// for the given syncDB map. (e.g., to differentiate between a user or
// group map)
func (db syncDB) log(prefix string) {
var onlyContainer, onlyHost, match, differ []string
for name, ids := range db {
wwlog.Debug("%s: %s: host(%v) container(%v) files: %v", prefix, name, ids.HostID, ids.ContainerID, ids.ContainerFiles)
if ids.onlyContainer() { onlyContainer = append(onlyContainer, name) }
if ids.onlyHost() { onlyHost = append(onlyHost, name) }
if ids.match() { match = append(match, name) }
if ids.differ() { differ = append(differ, name) }
}
wwlog.Debug(fmt.Sprintf("created uid/gid map with %v entries from %s", len(nameDb), fileName))
return nameDb, nil
wwlog.Verbose("%s: container only: %v", prefix, onlyContainer)
wwlog.Verbose("%s: host only: %v", prefix, onlyHost)
wwlog.Verbose("%s: match: %v", prefix, match)
wwlog.Verbose("%s: differ: %v", prefix, differ)
}
/*
Creates a slice with the entries of of passwd for the given slice of user names
*/
func getEntires(fileName string, names []string) ([]string, error) {
file, err := os.Open(fileName)
if err != nil {
return nil, err
// read reads fileName (an /etc/passwd or /etc/group file) into a
// syncDB map. Since the name and numerical id for both files are
// stored at fields 0 and 2, the same function can read both files.
//
// fromContainer identifies whether the given fileName includes
// entries from the host or the container.
func (db syncDB) read(fileName string, fromContainer bool) error {
wwlog.Debug("read %s (container: %t)", fileName, fromContainer)
if file, err := os.Open(fileName); err != nil {
return err
} else {
defer file.Close()
fileScanner := bufio.NewScanner(file)
for fileScanner.Scan() {
line := fileScanner.Text()
fields := strings.Split(line, ":")
name := fields[0]
if name == "" {
continue
}
id, err := strconv.Atoi(fields[2])
if err != nil {
wwlog.Warn("Ignoring line %s (parse error)", line)
continue
}
entry, ok := db[name]
if !ok {
entry = syncInfo{HostID: -1, ContainerID: -1}
}
if fromContainer {
if entry.ContainerID == -1 {
entry.ContainerID = id
} else if entry.ContainerID != id {
wwlog.Warn("Ignoring container id(%v) for %s from %s after existing value %v", id, name, fileName, entry.ContainerID)
continue
}
} else {
if entry.HostID == -1 {
entry.HostID = id
} else if entry.HostID != id {
wwlog.Warn("Ignoring host id(%v) for %s from %s after existing value %v", id, name, fileName, entry.HostID)
continue
}
}
db[name] = entry
}
return nil
}
}
// readFromHost reads fileName into a syncDB map.
//
// Equivalent to read(fileName, false)
func (db syncDB) readFromHost(fileName string) error { return db.read(fileName, false) }
// readFromContainer reads fileName into a syncDB map.
//
// Equivalent to read(fileName, true)
func (db syncDB) readFromContainer(fileName string) error { return db.read(fileName, true) }
// findFiles finds files under containerPath that are owned by each
// tracked container ID.
//
// If byGid is true, files with a matching gid are
// recorded. Otherwise, files with a matching uid are recorded.
func (db syncDB) findFiles(containerPath string, byGid bool) error {
for name, ids := range db {
if err := ids.findFiles(containerPath, byGid); err != nil { return err }
if len(ids.ContainerFiles) > 0 {
wwlog.Debug("files for %s (%v -> %v, gid: %v): %v", name, ids.ContainerID, ids.HostID, byGid, ids.ContainerFiles)
}
db[name] = ids
}
return nil
}
// findUserFiles is equivalent to findFiles(containerPath, false)
func (db syncDB) findUserFiles(containerPath string) error { return db.findFiles(containerPath, false) }
// findGroupFiles is equivalent to findFiles(containerPath, true)
func (db syncDB) findGroupFiles(containerPath string) error { return db.findFiles(containerPath, true) }
// chownFiles updates found files (see findFiles) to reflect ownership
// using host ids.
//
// If byGid is true, file gids are updated. Otherwise, file uids are
// updated.
func (db syncDB) chownFiles(byGid bool) error {
for _, ids := range db {
if err := ids.chownFiles(byGid); err != nil { return err }
}
return nil
}
// chownUserFiles is equivalent to chownFiles(false)
func (db syncDB) chownUserFiles() error { return db.chownFiles(false) }
// chownUserFiles is equivalent to chownFiles(true)
func (db syncDB) chownGroupFiles() error { return db.chownFiles(true) }
// getOnlyContainerLines returns the lines from fileName (either an
// /etc/passwd or /etc/group file) for names that occur only in the
// container.
//
// These lines are added to the respective file from the host when
// updating the container.
func (db syncDB) getOnlyContainerLines(fileName string) ([]string, error) {
file, err := os.Open(fileName)
if err != nil { return nil, err }
defer file.Close()
var list []string
fileScanner := bufio.NewScanner(file)
var lines []string
for fileScanner.Scan() {
line := fileScanner.Text()
entries := strings.Split(line, ":")
for _, name := range names {
if entries[0] == name {
list = append(list, line)
fields := strings.Split(line, ":")
for name, ids := range db {
if fields[0] == name {
if ids.onlyContainer() {
lines = append(lines, line)
}
break
}
}
}
wwlog.Debug("file: %s, list: %v", fileName, list)
return list, nil
wwlog.Debug("file: %s, entries only in container: %v", fileName, lines)
return lines, nil
}
// update replaces containerPath with hostPath and adds lines from
// getOnlyContainerLines to the end of containerPath. This is used to
// replace /etc/passwd (or /etc/group) in the container with the
// equivalent file from the host while retaining names unique to the
// container.
func (db syncDB) update(containerPath string, hostPath string) error {
wwlog.Debug("update %s from %s)", containerPath, hostPath)
if lines, err := db.getOnlyContainerLines(containerPath); err != nil {
return err
} else {
if err := os.Remove(containerPath); err != nil { return err }
if err := util.CopyFile(hostPath, containerPath); err != nil { return err }
if err := util.AppendLines(containerPath, lines); err != nil { return err }
return nil
}
}
// needsSync returns true if the syncDB map indicates that ids between
// the container and host are out-of-sync.
func (db syncDB) needsSync() bool {
for name, ids := range db {
if ids.onlyHost() {
wwlog.Debug("sync required: %s only in host", name)
return true
}
if ids.differ() {
wwlog.Debug("sync required: %s is %v in host and %v in container", name, ids.HostID, ids.ContainerID)
return true
}
if len(ids.ContainerFiles) > 0 {
wwlog.Debug("sync required: %v files to update for %s", len(ids.ContainerFiles), name)
}
}
return false
}
// sycncInfo correlates the numerical id of a name on the host
// (HostID) and the container (ContainerID), along with the files in
// the container that are owned by that name. This allows affected
// files to be updated when the HostID is applied to the container.
type syncInfo struct {
HostID int `access:"r,w"`
ContainerID int `access:"r,w"`
ContainerFiles []string `access:"r,w"`
}
// inHost returns true if info has a record of an id for this name in
// the host.
func (info *syncInfo) inHost() bool {
return info.HostID != -1
}
// inContainer returns true if info has a record of an id for this
// name in the container.
func (info *syncInfo) inContainer() bool {
return info.ContainerID != -1
}
// onlyHost returns true if info has a record of an id for this name
// in the host and not in the container.
func (info *syncInfo) onlyHost() bool {
return info.inHost() && !info.inContainer()
}
// onlyHost returns true if info has a record of an id for this name
// in the container and not in the host.
func (info *syncInfo) onlyContainer() bool {
return info.inContainer() && !info.inHost()
}
// match returns true if info represents a name that exists with the
// same numerical id in both the host and the container.
func (info *syncInfo) match() bool {
return info.inContainer() && info.inHost() && info.HostID == info.ContainerID
}
// differ returns true if info represents a name that exists in both
// the host and the container but with different numerical ids.
func (info *syncInfo) differ() bool {
return info.inContainer() && info.inHost() && info.HostID != info.ContainerID
}
// findFiles walks containerPath to find files owned by the name
// represented by info and records them in info.ContainerFiles.
//
// If byGid is true, the file's gid is checked; otherwise, the file's
// uid is checked.
func (info *syncInfo) findFiles(containerPath string, byGid bool) error {
var containerFiles []string
if (info.inHost() && !info.match()) {
if err := filepath.Walk(containerPath, func(filePath string, fileInfo fs.FileInfo, err error) error {
if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok {
var id int
if byGid {
id = int(stat.Gid)
} else {
id = int(stat.Uid)
}
if id == info.ContainerID {
containerFiles = append(containerFiles, filePath)
}
}
return nil
}); err != nil {
return err
}
}
info.ContainerFiles = containerFiles
return nil
}
// chownFiles updates the files recorded in info.ContainerFiles to use
// the numerical IDs from the host.
//
// If byGid is true, the file's gid is updated; otherwise, the file's
// uid is updated.
func (info *syncInfo) chownFiles(byGid bool) error {
for _, file := range info.ContainerFiles {
if fileInfo, err := os.Stat(file); err != nil {
return err
} else {
if fileInfo.IsDir() || fileInfo.Mode().IsRegular() {
var newUid, newGid int
if byGid {
newUid = -1
newGid = info.HostID
} else {
newUid = info.HostID
newGid = -1
}
if err := os.Chown(file, newUid, newGid); err != nil { return err }
}
}
}
return nil
}

View File

@@ -0,0 +1,283 @@
package container
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func writeTempFile(t *testing.T, input string) (string) {
tempFile, createTempError := os.CreateTemp("", "syncuids-*")
assert.NoError(t, createTempError)
_, writeError := tempFile.Write([]byte(input))
assert.NoError(t, writeError)
assert.NoError(t, tempFile.Sync())
return tempFile.Name()
}
func makeSyncDB(t *testing.T, hostInput string, containerInput string) (syncDB) {
hostFileName := writeTempFile(t, hostInput)
defer os.Remove(hostFileName)
containerFileName := writeTempFile(t, containerInput)
defer os.Remove(containerFileName)
db := make(syncDB)
var err error
err = db.readFromHost(hostFileName)
assert.NoError(t, err)
err = db.readFromContainer(containerFileName)
assert.NoError(t, err)
return db
}
func Test_readFromHost_single(t *testing.T) {
hostFileName := writeTempFile(t, `testuser:x:1001:1001::/home/testuser:/bin/bash`)
defer os.Remove(hostFileName)
db := make(syncDB)
err := db.readFromHost(hostFileName)
assert.NoError(t, err)
assert.Len(t, db, 1)
assert.Equal(t, 1001, db["testuser"].HostID)
assert.Equal(t, -1, db["testuser"].ContainerID)
}
func Test_readFromHost_multiple(t *testing.T) {
hostFileName := writeTempFile(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash
`)
defer os.Remove(hostFileName)
db := make(syncDB)
err := db.readFromHost(hostFileName)
assert.NoError(t, err)
assert.Len(t, db, 2)
assert.Equal(t, 1001, db["testuser1"].HostID)
assert.Equal(t, -1, db["testuser1"].ContainerID)
assert.Equal(t, 1002, db["testuser2"].HostID)
assert.Equal(t, -1, db["testuser2"].ContainerID)
}
func Test_readFromContainer_single(t *testing.T) {
containerFileName := writeTempFile(t, `testuser:x:1001:1001::/home/testuser:/bin/bash`)
defer os.Remove(containerFileName)
db := make(syncDB)
err := db.readFromContainer(containerFileName)
assert.NoError(t, err)
assert.Len(t, db, 1)
assert.Equal(t, 1001, db["testuser"].ContainerID)
assert.Equal(t, -1, db["testuser"].HostID)
}
func Test_readFromContainer_multiple(t *testing.T) {
containerFileName := writeTempFile(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash
`)
defer os.Remove(containerFileName)
db := make(syncDB)
err := db.readFromContainer(containerFileName)
assert.NoError(t, err)
assert.Len(t, db, 2)
assert.Equal(t, 1001, db["testuser1"].ContainerID)
assert.Equal(t, -1, db["testuser1"].HostID)
assert.Equal(t, 1002, db["testuser2"].ContainerID)
assert.Equal(t, -1, db["testuser2"].HostID)
}
func Test_readFromBoth_multiple(t *testing.T) {
containerFileName := writeTempFile(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash
`)
defer os.Remove(containerFileName)
hostFileName := writeTempFile(t, `
testuser1:x:2001:2001::/home/testuser:/bin/bash
testuser3:x:2003:2003::/home/testuser:/bin/bash
`)
defer os.Remove(hostFileName)
db := make(syncDB)
var err error
err = db.readFromContainer(containerFileName)
assert.NoError(t, err)
err = db.readFromHost(hostFileName)
assert.NoError(t, err)
assert.Len(t, db, 3)
assert.Equal(t, 1001, db["testuser1"].ContainerID)
assert.Equal(t, 2001, db["testuser1"].HostID)
assert.Equal(t, 1002, db["testuser2"].ContainerID)
assert.Equal(t, -1, db["testuser2"].HostID)
assert.Equal(t, -1, db["testuser3"].ContainerID)
assert.Equal(t, 2003, db["testuser3"].HostID)
}
func Test_checkConflicts_empty(t *testing.T) {
db := makeSyncDB(t, "", "")
assert.NoError(t, db.checkConflicts())
}
func Test_checkConflicts_single(t *testing.T) {
db := makeSyncDB(t, "", "testuser:x:1001:1001::/home/testuser:/bin/bash")
assert.NoError(t, db.checkConflicts())
}
func Test_checkConflicts_match(t *testing.T) {
db := makeSyncDB(t,
"testuser:x:1001:1001::/home/testuser:/bin/bash",
"testuser:x:1001:1001::/home/testuser:/bin/bash")
assert.NoError(t, db.checkConflicts())
}
func Test_checkConflicts_conflict(t *testing.T) {
db := makeSyncDB(t,
"testuser2:x:1001:1001::/home/testuser:/bin/bash",
"testuser1:x:1001:1001::/home/testuser:/bin/bash")
assert.Error(t, db.checkConflicts())
}
func Test_getOnlyContainerLines(t *testing.T) {
containerFileName := writeTempFile(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash
`)
defer os.Remove(containerFileName)
hostFileName := writeTempFile(t, `
testuser1:x:2001:2001::/home/testuser:/bin/bash
testuser3:x:2003:2003::/home/testuser:/bin/bash
`)
defer os.Remove(hostFileName)
db := make(syncDB)
var err error
err = db.readFromContainer(containerFileName)
assert.NoError(t, err)
err = db.readFromHost(hostFileName)
assert.NoError(t, err)
lines, err := db.getOnlyContainerLines(containerFileName)
assert.NoError(t, err)
assert.Len(t, lines, 1)
assert.Equal(t, lines[0], "testuser2:x:1002:1002::/home/testuser:/bin/bash")
}
func Test_needsSync_empty(t *testing.T) {
db := makeSyncDB(t, "", "")
assert.False(t, db.needsSync())
}
func Test_needsSync_containerOnly(t *testing.T) {
db := makeSyncDB(t, "", `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash`)
assert.False(t, db.needsSync())
}
func Test_needsSync_hostOnly(t *testing.T) {
db := makeSyncDB(t, `
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash`, "")
assert.True(t, db.needsSync())
}
func Test_needsSync_match(t *testing.T) {
db := makeSyncDB(t,
"testuser:x:1001:1001::/home/testuser:/bin/bash",
"testuser:x:1001:1001::/home/testuser:/bin/bash")
assert.False(t, db.needsSync())
}
func Test_needsSync_differ(t *testing.T) {
db := makeSyncDB(t,
`
testuser1:x:2001:2001::/home/testuser:/bin/bash
testuser3:x:2003:2003::/home/testuser:/bin/bash`,
`
testuser1:x:1001:1001::/home/testuser:/bin/bash
testuser2:x:1002:1002::/home/testuser:/bin/bash`)
assert.True(t, db.needsSync())
}
func Test_onlyHost(t *testing.T) {
db := makeSyncDB(t, "testuser1:x:2001:2001::/home/testuser:/bin/bash", "")
entry := db["testuser1"]
assert.True(t, entry.inHost())
assert.False(t, entry.inContainer())
assert.True(t, entry.onlyHost())
assert.False(t, entry.onlyContainer())
assert.False(t, entry.match())
assert.False(t, entry.differ())
}
func Test_onlyContainer(t *testing.T) {
db := makeSyncDB(t, "", "testuser1:x:2001:2001::/home/testuser:/bin/bash")
entry := db["testuser1"]
assert.False(t, entry.inHost())
assert.True(t, entry.inContainer())
assert.False(t, entry.onlyHost())
assert.True(t, entry.onlyContainer())
assert.False(t, entry.match())
assert.False(t, entry.differ())
}
func Test_match(t *testing.T) {
db := makeSyncDB(t,
"testuser1:x:2001:2001::/home/testuser:/bin/bash",
"testuser1:x:2001:2001::/home/testuser:/bin/bash")
entry := db["testuser1"]
assert.True(t, entry.inHost())
assert.True(t, entry.inContainer())
assert.False(t, entry.onlyHost())
assert.False(t, entry.onlyContainer())
assert.True(t, entry.match())
assert.False(t, entry.differ())
}
func Test_differ(t *testing.T) {
db := makeSyncDB(t,
"testuser1:x:1001:1001::/home/testuser:/bin/bash",
"testuser1:x:2001:2001::/home/testuser:/bin/bash")
entry := db["testuser1"]
assert.True(t, entry.inHost())
assert.True(t, entry.inContainer())
assert.False(t, entry.onlyHost())
assert.False(t, entry.onlyContainer())
assert.False(t, entry.match())
assert.True(t, entry.differ())
}

View File

@@ -48,14 +48,22 @@ func ListSources() ([]string, error) {
return ret, nil
}
func ValidSource(name string) bool {
fullPath := RootFsDir(name)
func DoesContainerExists(name string) bool {
fullPath := ImageFile(name)
return util.IsFile(fullPath)
}
func DoesSourceExist(name string) bool {
fullPath := RootFsDir(name)
return util.IsDir(fullPath)
}
func ValidSource(name string) bool {
if !ValidName(name) {
return false
}
if !util.IsDir(fullPath) {
if !DoesSourceExist(name) {
wwlog.Verbose("Location is not a VNFS source directory: %s", name)
return false
}
@@ -73,6 +81,23 @@ func DeleteSource(name string) error {
return os.RemoveAll(fullPath)
}
func Duplicate(name string, destination string) error {
fullPathImageSource := RootFsDir(name)
wwlog.Info("Copying sources...")
err := ImportDirectory(fullPathImageSource, destination)
if err != nil {
return err
}
wwlog.Info("Building container: %s", destination)
err = Build(destination, true)
if err != nil {
return err
}
return nil
}
/*
Delete the image of a container
*/

View File

@@ -166,7 +166,7 @@ func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) {
n.SetFrom(node)
// only now the netdevs start to exist so that default values can be set
for _, netdev := range n.NetDevs {
netdev.SetDefFrom(defConfNet)
SetDefFrom(defConfNet, netdev)
}
// backward compatibility
n.Ipmi.Ipaddr.Set(node.IpmiIpaddr)

View File

@@ -35,20 +35,22 @@ type NodeConf struct {
IpmiInterface string `yaml:"ipmi interface,omitempty"`
IpmiWrite string `yaml:"ipmi write,omitempty"`
// Deprecated end
RuntimeOverlay []string `yaml:"runtime overlay,omitempty" lopt:"runtime" sopt:"R" comment:"Set the runtime overlay"`
SystemOverlay []string `yaml:"system overlay,omitempty" lopt:"wwinit" sopt:"O" comment:"Set the system overlay"`
Kernel *KernelConf `yaml:"kernel,omitempty"`
Ipmi *IpmiConf `yaml:"ipmi,omitempty"`
Init string `yaml:"init,omitempty" lopt:"init" sopt:"i" comment:"Define the init process to boot the container"`
Root string `yaml:"root,omitempty" lopt:"root" comment:"Define the rootfs" `
AssetKey string `yaml:"asset key,omitempty" lopt:"asset" comment:"Set the node's Asset tag (key)"`
Discoverable string `yaml:"discoverable,omitempty" lopt:"discoverable" sopt:"e" comment:"Make discoverable in given network (true/false)" type:"bool"`
Profiles []string `yaml:"profiles,omitempty" lopt:"profile" sopt:"P" comment:"Set the node's profile members (comma separated)"`
NetDevs map[string]*NetDevs `yaml:"network devices,omitempty"`
Tags map[string]string `yaml:"tags,omitempty" lopt:"tagadd" comment:"base key"`
TagsDel []string `yaml:"tagsdel,omitempty" lopt:"tagdel" comment:"remove this tags"` // should not go to disk only to wire
Keys map[string]string `yaml:"keys,omitempty"` // Reverse compatibility
PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface"`
RuntimeOverlay []string `yaml:"runtime overlay,omitempty" lopt:"runtime" sopt:"R" comment:"Set the runtime overlay"`
SystemOverlay []string `yaml:"system overlay,omitempty" lopt:"wwinit" sopt:"O" comment:"Set the system overlay"`
Kernel *KernelConf `yaml:"kernel,omitempty"`
Ipmi *IpmiConf `yaml:"ipmi,omitempty"`
Init string `yaml:"init,omitempty" lopt:"init" sopt:"i" comment:"Define the init process to boot the container"`
Root string `yaml:"root,omitempty" lopt:"root" comment:"Define the rootfs" `
AssetKey string `yaml:"asset key,omitempty" lopt:"asset" comment:"Set the node's Asset tag (key)"`
Discoverable string `yaml:"discoverable,omitempty" lopt:"discoverable" sopt:"e" comment:"Make discoverable in given network (true/false)" type:"bool"`
Profiles []string `yaml:"profiles,omitempty" lopt:"profile" sopt:"P" comment:"Set the node's profile members (comma separated)"`
NetDevs map[string]*NetDevs `yaml:"network devices,omitempty"`
Tags map[string]string `yaml:"tags,omitempty" lopt:"tagadd" comment:"base key"`
TagsDel []string `yaml:"tagsdel,omitempty" lopt:"tagdel" comment:"remove this tags"` // should not go to disk only to wire
Keys map[string]string `yaml:"keys,omitempty"` // Reverse compatibility
PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface"`
Disks map[string]*Disk `yaml:"disks,omitempty"`
FileSystems map[string]*FileSystem `yaml:"filesystems,omitempty"`
}
type IpmiConf struct {
@@ -87,6 +89,42 @@ type NetDevs struct {
TagsDel []string `yaml:"tagsdel,omitempty" lopt:"nettagdel" comment:"delete network tags"` // should not go to disk only to wire
}
/*
Holds the disks of a node
*/
type Disk struct {
WipeTable string `yaml:"wipe_table,omitempty" type:"bool" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped"`
Partitions map[string]*Partition `yaml:"partitions,omitempty"`
}
/*
partition definition, the label must be uniq so its used as the key in the
Partitions map
*/
type Partition struct {
Number string `yaml:"number,omitempty" lopt:"partnumber" comment:"set the partition number, if not set next free slot is used"`
SizeMiB string `yaml:"size_mib,omitempty" lopt:"partsize" comment:"set the size of the partition, if not set maximal possible size is used"`
StartMiB string `yaml:"start_mib,omitempty" comment:"the start of the partition"`
TypeGuid string `yaml:"type_guid,omitempty" comment:"Linux filesystem data will be used if empty"`
Guid string `yaml:"guid,omitempty" comment:"the GPT unique partition GUID"`
WipePartitionEntry string `yaml:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config" type:"bool"`
ShouldExist string `yaml:"should_exist,omitempty" lopt:"partcreate" comment:"create partition if not exist" type:"bool"`
Resize string `yaml:"resize,omitempty" comment:" whether or not the existing partition should be resize" type:"bool"`
}
/*
Definition of a filesystem. The device is uniq so its used as key
*/
type FileSystem struct {
Format string `yaml:"format,omitempty" lopt:"fsformat" comment:"format of the file system"`
Path string `yaml:"path,omitempty" lopt:"fspath" comment:"the mount point of the file system"`
WipeFileSystem string `yaml:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot" type:"bool"`
Label string `yaml:"label,omitempty" comment:"the label of the filesystem"`
Uuid string `yaml:"uuid,omitempty" comment:"the uuid of the filesystem"`
Options []string `yaml:"options,omitempty" comment:"any additional options to be passed to the format-specific mkfs utility"`
MountOptions []string `yaml:"mount_options,omitempty" comment:"any special options to be passed to the mount command"`
}
/******
* Internal code data representations
******/
@@ -126,6 +164,8 @@ type NodeInfo struct {
PrimaryNetDev Entry
NetDevs map[string]*NetDevEntry
Tags map[string]*Entry
Disks map[string]*DiskEntry
FileSystems map[string]*FileSystemEntry
}
type IpmiEntry struct {
@@ -161,17 +201,32 @@ type NetDevEntry struct {
Tags map[string]*Entry
}
type DiskEntry struct {
WipeTable Entry
Partitions map[string]*PartitionEntry
}
type PartitionEntry struct {
Number Entry
SizeMiB Entry
StartMiB Entry
TypeGuid Entry
Guid Entry
WipePartitionEntry Entry
ShouldExist Entry
Resize Entry
}
type FileSystemEntry struct {
Format Entry
Path Entry
WipeFileSystem Entry
Label Entry
Uuid Entry
Options Entry
MountOptions Entry
}
// string which is printed if no value is set
const NoValue = "--"
/*
Has no real purpose as only New() needs it
func init() {
// Check that nodes.conf is found
if !util.IsFile(ConfigFile) {
wwlog.Warn("Missing node configuration file")
// just return silently, as init is also called for bash_completion
return
}
}
*/

View File

@@ -17,46 +17,48 @@ must be called, as the commandline parser returns e.g. netip.IP objects which mu
back to strings.
*/
func (nodeConf *NodeConf) CreateFlags(baseCmd *cobra.Command, excludeList []string) (converters []func() error) {
nodeInfoType := reflect.TypeOf(nodeConf)
nodeInfoVal := reflect.ValueOf(nodeConf)
/*
nodeInfoType := reflect.TypeOf(nodeConf)
nodeInfoVal := reflect.ValueOf(nodeConf)
*/
return RecursiveCreateFlags(nodeConf, baseCmd, excludeList)
}
func RecursiveCreateFlags(obj interface{}, baseCmd *cobra.Command, excludeList []string) (converters []func() error) {
// now iterate of every field
nodeInfoType := reflect.TypeOf(obj)
nodeInfoVal := reflect.ValueOf(obj)
for i := 0; i < nodeInfoVal.Elem().NumField(); i++ {
if nodeInfoType.Elem().Field(i).Tag.Get("comment") != "" &&
!util.InSlice(excludeList, nodeInfoType.Elem().Field(i).Tag.Get("lopt")) {
field := nodeInfoVal.Elem().Field(i)
converters = append(converters, createFlags(baseCmd, excludeList, nodeInfoType.Elem().Field(i), &field)...)
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr {
nestType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface())
nestVal := reflect.ValueOf(nodeInfoVal.Elem().Field(i).Interface())
for j := 0; j < nestType.Elem().NumField(); j++ {
field := nestVal.Elem().Field(j)
converters = append(converters, createFlags(baseCmd, excludeList, nestType.Elem().Field(j), &field)...)
}
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string]*NetDevs(nil)) {
netMap := nodeInfoVal.Elem().Field(i).Interface().(map[string]*NetDevs)
// add a default network so that it can hold values
key := "default"
if len(netMap) == 0 {
netMap[key] = new(NetDevs)
} else {
for keyIt := range netMap {
key = keyIt
break
newConv := RecursiveCreateFlags(nodeInfoVal.Elem().Field(i).Interface(), baseCmd, excludeList)
converters = append(converters, newConv...)
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Map &&
nodeInfoType.Elem().Field(i).Type != reflect.TypeOf(map[string]string{}) {
// add a map with key UNDEF so that it can hold values N.B. UNDEF can never be added through command line
key := reflect.ValueOf("UNDEF")
if nodeInfoVal.Elem().Field(i).Len() == 0 {
if nodeInfoVal.Elem().Field(i).IsNil() {
nodeInfoVal.Elem().Field(i).Set(reflect.MakeMap(nodeInfoType.Elem().Field(i).Type))
}
newPtr := reflect.New(nodeInfoType.Elem().Field(i).Type.Elem().Elem())
nodeInfoVal.Elem().Field(i).SetMapIndex(key, newPtr)
} else {
key = nodeInfoVal.Elem().Field(i).MapKeys()[0]
}
netType := reflect.TypeOf(netMap[key])
netVal := reflect.ValueOf(netMap[key])
for j := 0; j < netType.Elem().NumField(); j++ {
field := netVal.Elem().Field(j)
converters = append(converters, createFlags(baseCmd, excludeList, netType.Elem().Field(j), &field)...)
}
newConv := RecursiveCreateFlags(nodeInfoVal.Elem().Field(i).MapIndex(key).Interface(), baseCmd, excludeList)
converters = append(converters, newConv...)
}
}
return converters
}
/*
Helper function to create the different PerisitantFlags() for different types.
Helper function to create the different PersistentFlags() for different types.
*/
func createFlags(baseCmd *cobra.Command, excludeList []string,
myType reflect.StructField, myVal *reflect.Value) (converters []func() error) {

View File

@@ -0,0 +1,127 @@
package node
import (
"fmt"
"sort"
types_3_4 "github.com/coreos/ignition/v2/config/v3_2/types"
"github.com/coreos/vcontext/path"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
)
/*
Create a ignition struct class for ignition
*/
func (node *NodeInfo) GetStorage() (stor types_3_4.Storage, err error, rep string) {
var fileSystems []types_3_4.Filesystem
for fsdevice, fs := range node.FileSystems {
var mountOptions []types_3_4.MountOption
for _, opt := range fs.MountOptions.GetSlice() {
mountOptions = append(mountOptions, types_3_4.MountOption(opt))
}
var fsOption []types_3_4.FilesystemOption
for _, opt := range fs.Options.GetSlice() {
fsOption = append(fsOption, types_3_4.FilesystemOption(opt))
}
wipe := fs.WipeFileSystem.GetB()
myFs := types_3_4.Filesystem{
Device: fsdevice,
Path: fs.Path.GetPointer(),
WipeFilesystem: &wipe,
}
if fs.Format.Get() != "" {
myFs.Format = fs.Format.GetPointer()
}
if fs.Label.Get() != "" {
myFs.Label = fs.Label.GetPointer()
}
if fs.MountOptions.Get() != "" {
myFs.MountOptions = mountOptions
}
if fs.Options.Get() != "" {
myFs.Options = fsOption
}
if fs.Options.Get() != "" {
myFs.UUID = fs.Uuid.GetPointer()
}
wwlog.Debug("created file system struct: %v", myFs)
fileSystems = append(fileSystems, myFs)
}
var disks []types_3_4.Disk
for diskDev, disk := range node.Disks {
var partitions []types_3_4.Partition
for partlabel, part := range disk.Partitions {
resize := part.Resize.GetB()
shouldExist := part.ShouldExist.GetB()
wipe := part.WipePartitionEntry.GetB()
label := partlabel
myPart := types_3_4.Partition{
Label: &label,
Number: part.Number.GetInt(),
ShouldExist: &shouldExist,
WipePartitionEntry: &wipe,
}
if part.Guid.Get() != "" {
myPart.GUID = part.Guid.GetPointer()
}
if part.Resize.Get() != "" {
myPart.Resize = &resize
}
if part.SizeMiB.Get() != "" {
myPart.SizeMiB = part.SizeMiB.GetIntPtr()
}
if part.StartMiB.Get() != "" {
myPart.StartMiB = part.StartMiB.GetIntPtr()
}
if part.TypeGuid.Get() != "" {
myPart.TypeGUID = part.TypeGuid.GetPointer()
}
partitions = append(partitions, myPart)
}
sort.SliceStable(partitions, func(i int, j int) bool {
if partitions[i].Number == partitions[j].Number {
if partitions[i].SizeMiB != nil && partitions[j].SizeMiB == nil {
return true
}
if partitions[j].SizeMiB != nil && partitions[i].SizeMiB == nil {
return false
}
return *partitions[i].SizeMiB < *partitions[j].SizeMiB
}
return partitions[i].Number < partitions[j].Number
})
wipe := disk.WipeTable.GetB()
disks = append(disks, types_3_4.Disk{
Device: diskDev,
Partitions: partitions,
WipeTable: &wipe,
})
}
stor = types_3_4.Storage{
Disks: disks,
Filesystems: fileSystems,
}
report := stor.Validate(path.ContextPath{})
if report.IsFatal() {
err = fmt.Errorf(report.String())
}
rep = report.String()
return
}
type MyIgnition struct {
Version string `json:"version"`
}
type SimpleIgnitionConfig struct {
Ignition MyIgnition `json:"ignition"`
Storage types_3_4.Storage `json:"storage"`
}
/*
Get a simple config which can be marshalled to json
*/
func (node *NodeInfo) GetConfig() (conf SimpleIgnitionConfig, rep string, err error) {
conf.Storage, err, rep = node.GetStorage()
conf.Ignition.Version = "3.1.0"
return
}

79
internal/pkg/node/list.go Normal file
View File

@@ -0,0 +1,79 @@
package node
import (
"reflect"
)
/*
struct to hold the fields of GetFields
*/
type NodeFields struct {
Field string
Source string
Value string
}
/*
Get all the info out of NodeInfo. If emptyFields is set true, all fields
are shown not only the ones with effective values
*/
func (node *NodeInfo) GetFields(emptyFields bool) (output []NodeFields) {
return recursiveFields(node, emptyFields, "")
}
/*
Internal function which travels through all fields of a NodeInfo and for this
reason needs tb called via interface{}
*/
func recursiveFields(obj interface{}, emptyFields bool, prefix string) (output []NodeFields) {
valObj := reflect.ValueOf(obj)
typeObj := reflect.TypeOf(obj)
for i := 0; i < typeObj.Elem().NumField(); i++ {
if typeObj.Elem().Field(i).Type == reflect.TypeOf(Entry{}) {
myField := valObj.Elem().Field(i).Interface().(Entry)
if emptyFields || myField.Get() != "" {
output = append(output, NodeFields{
Field: prefix + typeObj.Elem().Field(i).Name,
Source: myField.Source(),
Value: myField.Print(),
})
}
} else if typeObj.Elem().Field(i).Type == reflect.TypeOf(map[string]*Entry{}) {
for key, val := range valObj.Elem().Field(i).Interface().(map[string]*Entry) {
if emptyFields || val.Get() != "" {
output = append(output, NodeFields{
Field: prefix + typeObj.Elem().Field(i).Name + "[" + key + "]",
Source: val.Source(),
Value: val.Print(),
})
}
}
if valObj.Elem().Field(i).Len() == 0 && emptyFields {
output = append(output, NodeFields{
Field: prefix + typeObj.Elem().Field(i).Name + "[]",
})
}
} else if typeObj.Elem().Field(i).Type.Kind() == reflect.Map {
mapIter := valObj.Elem().Field(i).MapRange()
for mapIter.Next() {
nestedOut := recursiveFields(mapIter.Value().Interface(), emptyFields, prefix+typeObj.Elem().Field(i).Name+"["+mapIter.Key().String()+"].")
if len(nestedOut) == 0 {
output = append(output, NodeFields{
Field: prefix + typeObj.Elem().Field(i).Name + "[" + mapIter.Key().String() + "]",
})
} else {
output = append(output, nestedOut...)
}
}
if valObj.Elem().Field(i).Len() == 0 && emptyFields {
output = append(output, NodeFields{
Field: prefix + typeObj.Elem().Field(i).Name + "[]",
})
}
} else if typeObj.Elem().Field(i).Type.Kind() == reflect.Ptr {
nestedOut := recursiveFields(valObj.Elem().Field(i).Interface(), emptyFields, prefix+typeObj.Elem().Field(i).Name+".")
output = append(output, nestedOut...)
}
}
return
}

View File

@@ -4,12 +4,20 @@ import (
"fmt"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
)
type sortByName []NodeInfo
func (a sortByName) Len() int { return len(a) }
func (a sortByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a sortByName) Less(i, j int) bool { return a[i].Id.Print() < a[j].Id.Print() }
func GetUnsetVerbs() []string {
return []string{"UNSET", "DELETE", "UNDEF", "undef", "--", "nil", "0.0.0.0"}
}
@@ -43,6 +51,7 @@ func FilterByName(set []NodeInfo, searchList []string) []NodeInfo {
ret = set
}
sort.Sort(sortByName(ret))
return ret
}
@@ -58,7 +67,6 @@ func FilterMapByName(inputMap map[string]*NodeConf, searchList []string) (retMap
retMap[name] = nConf
}
}
}
}
return retMap
@@ -156,7 +164,6 @@ func (ent *Entry) SetDefault(val string) {
return
}
ent.def = []string{val}
}
/*
@@ -167,7 +174,6 @@ func (ent *Entry) SetDefaultSlice(val []string) {
return
}
ent.def = val
}
/*
@@ -276,6 +282,37 @@ func (ent *Entry) GotReal() bool {
return len(ent.value) != 0
}
/*
Get a pointer to the value
*/
func (ent *Entry) GetPointer() *string {
ret := ent.Get()
return &ret
}
/*
Try to get a int of a value, 0 if value can't be parsed!
*/
func (ent *Entry) GetInt() int {
var ret int
if len(ent.value) != 0 {
ret, _ = strconv.Atoi(ent.value[0])
} else if len(ent.altvalue) != 0 {
ret, _ = strconv.Atoi(ent.altvalue[0])
} else if len(ent.def) != 0 {
ret, _ = strconv.Atoi(ent.def[0])
}
return ret
}
/*
Ptr to int
*/
func (ent *Entry) GetIntPtr() *int {
ret := ent.GetInt()
return &ret
}
/**********
*
* Misc
@@ -328,7 +365,7 @@ per profile. Else -- is returned.
func (ent *Entry) Source() string {
if len(ent.value) != 0 && len(ent.altvalue) != 0 {
return "SUPERSEDED"
//return fmt.Sprintf("[%s]", ent.from)
// return fmt.Sprintf("[%s]", ent.from)
} else if ent.from == "" {
return "--"
}
@@ -375,11 +412,6 @@ func NewInfo() (nodeInfo NodeInfo) {
return nodeInfo
}
func NewNetDevEntry() (netdev NetDevEntry) {
netdev.Tags = make(map[string]*Entry)
return
}
/*
Get a entry by its name
*/
@@ -399,12 +431,15 @@ func GetByName(node interface{}, name string) (string, error) {
/*
Check if the Netdev is empty, so has no values set
*/
func (dev *NetDevs) Empty() bool {
if dev == nil {
func ObjectIsEmpty(obj interface{}) bool {
if obj == nil {
return true
}
varType := reflect.TypeOf(*dev)
varVal := reflect.ValueOf(*dev)
varType := reflect.TypeOf(obj)
varVal := reflect.ValueOf(obj)
if varType.Kind() == reflect.Ptr && !varVal.IsNil() {
return ObjectIsEmpty(varVal.Elem().Interface())
}
if varVal.IsZero() {
return true
}
@@ -418,6 +453,10 @@ func (dev *NetDevs) Empty() bool {
if len(varVal.Field(i).Interface().(map[string]string)) != 0 {
return false
}
} else if varType.Field(i).Type.Kind() == reflect.Ptr {
if !ObjectIsEmpty(varVal.Field(i).Interface()) {
return false
}
}
}
return true

View File

@@ -7,25 +7,25 @@ func Test_Empty(t *testing.T) {
var netdevPtr *NetDevs
t.Run("test for empty", func(t *testing.T) {
if netdev.Empty() != true {
if ObjectIsEmpty(netdev) != true {
t.Errorf("netdev must be empty")
}
})
t.Run("test for non empty", func(t *testing.T) {
netdev.Device = "foo"
if netdev.Empty() == true {
if ObjectIsEmpty(netdev) == true {
t.Errorf("netdev must be non empty")
}
})
t.Run("test for nil pointer", func(t *testing.T) {
if netdevPtr.Empty() != true {
if ObjectIsEmpty(netdevPtr) != true {
t.Errorf("netdev must be empty")
}
})
t.Run("test for pointer assigned", func(t *testing.T) {
netdev.Ipaddr = "10.10.10.1"
netdevPtr = &netdev
if netdevPtr.Empty() == true {
if ObjectIsEmpty(netdevPtr) == true {
t.Errorf("netdev must be empty")
}
})

View File

@@ -48,12 +48,19 @@ func (config *NodeYaml) DelNode(nodeID string) error {
return nil
}
/*
update the node in the database
*/
func (config *NodeYaml) NodeUpdate(node NodeInfo) error {
nodeID := node.Id.Get()
wwlog.Debug("updating node %s: %v", nodeID, node)
if _, ok := config.Nodes[nodeID]; !ok {
return errors.New("Nodename does not exist: " + nodeID)
}
// maps may have deleted elements which will not be updated
// so we delete the node and call GetRealFrom on an empty NodeConv
delete(config.Nodes, nodeID)
config.Nodes[nodeID] = new(NodeConf)
config.Nodes[nodeID].GetRealFrom(node)
return nil
}
@@ -102,6 +109,10 @@ func (config *NodeYaml) ProfileUpdate(profile NodeInfo) error {
if _, ok := config.NodeProfiles[profileID]; !ok {
return errors.New("Profile name does not exist: " + profileID)
}
// maps may have deleted elements which will not be updated
// so we delete the profile and call GetRealFrom on an empty NodeConv
delete(config.NodeProfiles, profileID)
config.NodeProfiles[profileID] = new(NodeConf)
config.NodeProfiles[profileID].GetRealFrom(profile)
return nil
}
@@ -133,7 +144,6 @@ func (config *NodeYaml) Persist() error {
return nil
}
// Dump returns a YAML document representing the nodeDb
// instance. Passes through any errors generated by yaml.Marshal.
func (config *NodeYaml) Dump() ([]byte, error) {

View File

@@ -37,4 +37,45 @@ nodes:
},
)
assert.NoError(t, err)
}
}
func TestNodeDisk(t *testing.T) {
node_config := `WW_INTERNAL: 43
nodes:
n1:
disks:
/dev/vda:
wipe_table: "true"
partitions:
scratch:
should_exist: "true"
filesystems:
/dev/disk/by-partlabel/scratch:
format: btrfs
path: /scratch
wipe_filesystem: "true"`
config, parse_error := Parse([]byte(node_config))
assert.Empty(t, parse_error)
nodeInfos, info_error := config.FindAllNodes()
assert.Empty(t, info_error)
assert.Len(t, nodeInfos, 1)
node := nodeInfos[0]
assert.Len(t, node.Disks, 1)
assert.Len(t, node.FileSystems, 1)
disk := node.Disks["/dev/vda"]
assert.True(t, disk.WipeTable.GetB())
assert.Len(t, disk.Partitions, 1)
partition := disk.Partitions["scratch"]
assert.True(t, partition.ShouldExist.GetB())
filesystem := node.FileSystems["/dev/disk/by-partlabel/scratch"]
assert.Equal(t, "btrfs", filesystem.Format.Get())
assert.Equal(t, "/scratch", filesystem.Path.Get())
assert.True(t, filesystem.WipeFileSystem.GetB())
}

View File

@@ -137,7 +137,7 @@ func Test_nodeYaml_SetFrom(t *testing.T) {
t.Run("Get() default network mask", func(t *testing.T) {
value := test_node1.NetDevs["net0"].Netmask.Get()
if value != "255.255.255.0" {
t.Errorf("Get() returned wrong default netmask: %s", value)
t.Errorf("Get() returned wrong default netmask, got: %s want: 255.255.255.0", value)
}
})
t.Run("Get() default network mask", func(t *testing.T) {
@@ -299,19 +299,19 @@ ipmi: {}
t.Errorf("Got wrong yml, wanted:\n'%s'\nGot:\n'%s'", wanted, string(ymlByte))
}
delete(test_node4.Tags, "foo")
nodeConf = NewConf()
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ = yaml.Marshal(nodeConf)
wanted = `{}
`
if string(ymlByte) != wanted {
t.Errorf("Couldn't remove tag'%s'", string(ymlByte))
t.Errorf("Couldn't remove tag, wanted:\n%s\nGot:\n%s", wanted, string(ymlByte))
}
})
t.Run("Set() netdev foo with device name baar for flattened empty node", func(t *testing.T) {
netdev := NewNetDevEntry()
test_node4.NetDevs["foo"] = &netdev
test_node4.NetDevs["foo"] = new(NetDevEntry)
test_node4.NetDevs["foo"].Device.Set("baar")
nodeConf := NewConf()
nodeConf.GetFrom(test_node4)
@@ -324,7 +324,8 @@ ipmi: {}
if !(wanted == string(ymlByte)) {
t.Errorf("Got wrong yml, wanted:\n'%s'\nGot:\n'%s'", wanted, string(ymlByte))
}
test_node4.NetDevs["foo"].Tags["netfoo"] = &Entry{}
test_node4.NetDevs["foo"].Tags = make(map[string]*Entry)
test_node4.NetDevs["foo"].Tags["netfoo"] = new(Entry)
test_node4.NetDevs["foo"].Tags["netfoo"].Set("netbaar")
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
@@ -336,10 +337,11 @@ ipmi: {}
`
ymlByte, _ = yaml.Marshal(nodeConf)
if string(ymlByte) != wanted {
t.Errorf("Couldn set nettag: '%s' got: '%s'", wanted, string(ymlByte))
t.Errorf("Couldn't set nettag: '%s' got: '%s'", wanted, string(ymlByte))
}
delete(test_node4.NetDevs, "foo")
nodeConf = NewConf()
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ = yaml.Marshal(nodeConf)

View File

@@ -1,6 +1,7 @@
package node
import (
"fmt"
"reflect"
"strings"
@@ -15,7 +16,7 @@ the underlying entries using GetReal, so just the explicit values
go do disk.
*/
func (nodeConf *NodeConf) GetRealFrom(nodeInfo NodeInfo) {
nodeConf.getterFrom(nodeInfo, (*Entry).GetReal, (*Entry).GetRealSlice)
recursiveGetter(&nodeInfo, nodeConf, (*Entry).GetReal, (*Entry).GetRealSlice)
}
/*
@@ -23,168 +24,67 @@ Populates a NodeConf struct from a NodeInfo, with the combined
values from the underlying entries using Get.
*/
func (nodeConf *NodeConf) GetFrom(nodeInfo NodeInfo) {
nodeConf.getterFrom(nodeInfo, (*Entry).Get, (*Entry).GetSlice)
recursiveGetter(&nodeInfo, nodeConf, (*Entry).Get, (*Entry).GetSlice)
}
/*
Abstract function which populates a NodeConf from the given NodeInfo
via getter functions.
via getter functions. Calls recursive itself for nested structures.
Panics if the NodeConf has fields which are not type of string,[]string,map[string]*ptr
*/
func (nodeConf *NodeConf) getterFrom(nodeInfo NodeInfo,
func recursiveGetter(
source, target interface{},
getter func(*Entry) string,
getterSlice func(*Entry) []string) {
nodeInfoType := reflect.TypeOf(nodeInfo)
nodeInfoVal := reflect.ValueOf(nodeInfo)
configVal := reflect.ValueOf(nodeConf)
// now iterate of every field
for i := 0; i < nodeInfoType.NumField(); i++ {
// found field with same name for Conf and Info
confField := configVal.Elem().FieldByName(nodeInfoType.Field(i).Name)
if confField.IsValid() {
if nodeInfoVal.Field(i).Type() == reflect.TypeOf(Entry{}) {
if confField.Type().Kind() == reflect.String {
newValue := (confField.Addr().Interface()).(*string)
entryVal := nodeInfoVal.Field(i).Interface().(Entry)
*newValue = getter(&entryVal)
} else if confField.Type() == reflect.TypeOf([]string{}) {
newValue := (confField.Addr().Interface()).(*[]string)
entryVal := nodeInfoVal.Field(i).Interface().(Entry)
*newValue = getterSlice(&entryVal)
}
} else if nodeInfoVal.Field(i).Type() == reflect.TypeOf(map[string]*Entry{}) {
entryMap := nodeInfoVal.Field(i).Interface().(map[string]*Entry)
confMap := confField.Interface().(map[string]string)
sourceValue := reflect.ValueOf(source)
targetType := reflect.TypeOf(target)
targetValue := reflect.ValueOf(target)
if targetValue.Elem().Kind() == reflect.Struct && sourceValue.Elem().Kind() == reflect.Struct {
for i := 0; i < targetType.Elem().NumField(); i++ {
sourceValueMatched := sourceValue.Elem().FieldByName(targetType.Elem().Field(i).Name)
if sourceValueMatched.IsValid() {
if sourceValueMatched.Type() == reflect.TypeOf(Entry{}) {
// get the fields which are part of the struct
switch targetValue.Elem().Field(i).Type() {
case reflect.TypeOf(""):
newValue := (targetValue.Elem().Field(i).Addr().Interface()).(*string)
source := sourceValueMatched.Interface().(Entry)
*newValue = getter(&source)
case reflect.TypeOf([]string{}):
newValue := (targetValue.Elem().Field(i).Addr().Interface()).(*[]string)
source := sourceValueMatched.Interface().(Entry)
*newValue = getterSlice(&source)
default:
panic(fmt.Errorf("can't convert an Entry to %s", targetValue.Elem().Field(i).Type()))
}
} else if sourceValueMatched.Kind() == reflect.Ptr {
// if we get a pointer, initialize if empty and then have a recursive call
if targetValue.Elem().Field(i).IsZero() {
targetValue.Elem().Field(i).Set(reflect.New(targetType.Elem().Field(i).Type.Elem()))
}
recursiveGetter(sourceValueMatched.Interface(), targetValue.Elem().Field(i).Interface(), getter, getterSlice)
} else if sourceValueMatched.Type().Kind() == reflect.Map {
if targetValue.Elem().Field(i).IsZero() {
targetValue.Elem().Field(i).Set(reflect.MakeMap(targetType.Elem().Field(i).Type))
}
sourceIter := sourceValueMatched.MapRange()
if sourceValueMatched.Type() == reflect.TypeOf(map[string]*Entry{}) {
// go over a simple map with strings
for sourceIter.Next() {
if !targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).IsValid() {
str := getter((sourceIter.Value().Interface()).(*Entry))
targetValue.Elem().Field(i).SetMapIndex(sourceIter.Key(), reflect.ValueOf(str))
}
}
} else {
// now the complicated map which contains pointers to objects
for sourceIter.Next() {
if !targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).IsValid() {
newPtr := reflect.New(targetType.Elem().Field(i).Type.Elem().Elem())
targetValue.Elem().Field(i).SetMapIndex(sourceIter.Key(), newPtr)
}
recursiveGetter(sourceIter.Value().Interface(), targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).Interface(), getter, getterSlice)
if len(confMap) > len(entryMap) {
for confKey := range confMap {
foundKey := false
for entrKey := range entryMap {
if confKey == entrKey {
foundKey = true
}
}
if !foundKey {
delete(confMap, confKey)
}
}
}
for key, val := range entryMap {
confMap[key] = getter(val)
}
} else if nodeInfoVal.Field(i).Type().Kind() == reflect.Ptr && !nodeInfoVal.Field(i).IsNil() {
// initialize the nested NodeConf structs, but only if these will be set
if confField.Addr().Elem().IsZero() {
switch confField.Addr().Elem().Type() {
case reflect.TypeOf((*KernelConf)(nil)):
var newConf KernelConf
newConfPtr := (confField.Addr().Elem().Addr().Interface()).(**KernelConf)
*newConfPtr = &newConf
case reflect.TypeOf((*IpmiConf)(nil)):
var newConf IpmiConf
newConfPtr := (confField.Addr().Elem().Addr().Interface()).(**IpmiConf)
*newConfPtr = &newConf
}
}
nestedInfoType := reflect.TypeOf(nodeInfoVal.Field(i).Interface())
nestedInfoVal := reflect.ValueOf(nodeInfoVal.Field(i).Interface())
nestedConfVal := reflect.ValueOf(confField.Interface())
for j := 0; j < nestedInfoType.Elem().NumField(); j++ {
nestedVal := nestedConfVal.Elem().FieldByName(nestedInfoType.Elem().Field(j).Name)
if nestedInfoVal.Elem().Field(j).Type() == reflect.TypeOf(Entry{}) {
if nestedVal.Type().Kind() == reflect.String {
newValue := (nestedVal.Addr().Interface()).(*string)
entryVal := nestedInfoVal.Elem().Field(j).Interface().(Entry)
*newValue = getter(&entryVal)
} else if nestedVal.Type() == reflect.TypeOf([]string{}) {
newValue := (nestedVal.Addr().Interface()).(*[]string)
entryVal := nestedInfoVal.Elem().Field(j).Interface().(Entry)
*newValue = getterSlice(&entryVal)
}
} else if nestedInfoVal.Elem().Field(j).Type() == reflect.TypeOf(map[string]*Entry{}) {
if nestedVal.IsNil() {
mapPtr := nestedVal.Addr().Interface().(*map[string]string)
*mapPtr = make(map[string]string)
}
entryMap := nestedInfoVal.Elem().Field(j).Interface().(map[string]*Entry)
confMap := nestedVal.Interface().(map[string]string)
if len(confMap) > len(entryMap) {
for confKey := range confMap {
foundKey := false
for entrKey := range entryMap {
if confKey == entrKey {
foundKey = true
}
}
if !foundKey {
delete(confMap, confKey)
}
}
}
for key, val := range entryMap {
confMap[key] = getter(val)
}
}
}
} else if nodeInfoVal.Field(i).Type() == reflect.TypeOf(map[string]*NetDevEntry{}) {
if confField.IsNil() {
netMapPtr := confField.Addr().Interface().(*map[string](*NetDevs))
*netMapPtr = make(map[string](*NetDevs))
}
nestedMap := nodeInfoVal.Field(i).Interface().(map[string]*NetDevEntry)
netMap := confField.Interface().(map[string](*NetDevs))
// check if a network was deleted
if len(netMap) > len(nestedMap) {
for netMapKey := range netMap {
foundKey := false
for nestedMapKey := range nestedMap {
if netMapKey == nestedMapKey {
foundKey = true
}
}
if !foundKey {
delete(netMap, netMapKey)
}
}
}
for netName, netVal := range nestedMap {
netValsType := reflect.ValueOf(netVal)
if _, ok := netMap[netName]; !ok {
netMap[netName] = new(NetDevs)
}
netConfType := reflect.TypeOf(*netMap[netName])
netConfVal := reflect.ValueOf(netMap[netName])
for j := 0; j < netConfType.NumField(); j++ {
netVal := netValsType.Elem().FieldByName(netConfType.Field(j).Name)
if netVal.IsValid() {
if netVal.Type() == reflect.TypeOf(Entry{}) {
newVal := netConfVal.Elem().Field(j).Addr().Interface().((*string))
*newVal = getter((netVal.Addr().Interface()).(*Entry))
} else if netVal.Type() == reflect.TypeOf(map[string]*Entry{}) {
entryMap := netVal.Interface().(map[string](*Entry))
confMap := netConfVal.Elem().Field(j).Interface().(map[string]string)
if confMap == nil {
confMapPtr := netConfVal.Elem().Field(j).Addr().Interface().(*map[string]string)
*confMapPtr = make(map[string]string)
}
if len(confMap) > len(entryMap) {
for confMapKey := range confMap {
foundKey := false
for entryMapKey := range entryMap {
if confMapKey == entryMapKey {
foundKey = true
}
}
if !foundKey {
delete(netConfVal.Elem().Field(j).Interface().(map[string]string), confMapKey)
}
}
}
for key, val := range entryMap {
netConfVal.Elem().Field(j).Interface().(map[string]string)[key] = getter(val)
}
}
}
}
}
@@ -204,17 +104,17 @@ func (node *NodeInfo) SetFrom(n *NodeConf) {
setSliceWrap := func(entr *Entry, val []string, nameArg string) {
entr.SetSlice(val)
}
node.setterFrom(n, "", setWrap, setSliceWrap)
recursiveSetter(n, node, "", setWrap, setSliceWrap)
}
/*
Populates all fields of NodeInfo with SetAlt from the
values of NodeConf. The string profileName is used to
destermine from which source/NodeInfo the entry came
determine from which source/NodeInfo the entry came
from.
*/
func (node *NodeInfo) SetAltFrom(n *NodeConf, profileName string) {
node.setterFrom(n, profileName, (*Entry).SetAlt, (*Entry).SetAltSlice)
recursiveSetter(n, node, profileName, (*Entry).SetAlt, (*Entry).SetAltSlice)
}
/*
@@ -228,120 +128,95 @@ func (node *NodeInfo) SetDefFrom(n *NodeConf) {
setSliceWrap := func(entr *Entry, val []string, nameArg string) {
entr.SetDefaultSlice(val)
}
node.setterFrom(n, "", setWrap, setSliceWrap)
recursiveSetter(n, node, "", setWrap, setSliceWrap)
}
func SetDefFrom(source, target interface{}) {
setWrap := func(entr *Entry, val string, nameArg string) {
entr.SetDefault(val)
}
setSliceWrap := func(entr *Entry, val []string, nameArg string) {
entr.SetDefaultSlice(val)
}
recursiveSetter(source, target, "", setWrap, setSliceWrap)
}
/*
Abstract function which populates a NodeInfo from a NodeConf via
setter functionns.
setter functions. Panics if other type than string, []string *ptr is used in NodeConf.
*/
func (node *NodeInfo) setterFrom(n *NodeConf, nameArg string,
setter func(*Entry, string, string),
func recursiveSetter(source, target interface{}, nameArg string, setter func(*Entry, string, string),
setterSlice func(*Entry, []string, string)) {
// get the full memory, taking the shortcut and init Ipmi and Kernel directly
if node.Kernel == nil {
node.Kernel = new(KernelEntry)
}
if node.Ipmi == nil {
node.Ipmi = new(IpmiEntry)
}
// also n could be nil
if n == nil {
myn := NewConf()
n = &myn
}
nodeInfoVal := reflect.ValueOf(node)
nodeInfoType := reflect.TypeOf(node)
nodeConfVal := reflect.ValueOf(n)
// now iterate of every field
for i := 0; i < nodeInfoType.Elem().NumField(); i++ {
valField := nodeConfVal.Elem().FieldByName(nodeInfoType.Elem().Field(i).Name)
if valField.IsValid() {
// found field with same name for Conf and Info
if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(Entry{}) {
if valField.Type().Kind() == reflect.String {
setter(nodeInfoVal.Elem().Field(i).Addr().Interface().(*Entry), valField.String(), nameArg)
} else if valField.Type() == reflect.TypeOf([]string{}) {
setterSlice(nodeInfoVal.Elem().Field(i).Addr().Interface().(*Entry), valField.Interface().([]string), nameArg)
}
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr && !valField.IsZero() {
nestedInfoType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface())
nestedInfoVal := reflect.ValueOf(nodeInfoVal.Elem().Field(i).Interface())
nestedConfVal := reflect.ValueOf(valField.Interface())
for j := 0; j < nestedInfoType.Elem().NumField(); j++ {
nestedVal := nestedConfVal.Elem().FieldByName(nestedInfoType.Elem().Field(j).Name)
if nestedVal.IsValid() {
if nestedInfoVal.Elem().Field(j).Type() == reflect.TypeOf(Entry{}) {
setter(nestedInfoVal.Elem().Field(j).Addr().Interface().(*Entry), nestedVal.String(), nameArg)
} else if nestedInfoVal.Elem().Field(j).Type() == reflect.TypeOf(map[string](*Entry){}) {
confMap := nestedVal.Interface().(map[string]string)
if nestedInfoVal.Elem().Field(j).IsNil() {
ptr := nestedInfoVal.Elem().Field(j).Addr().Interface().(*map[string](*Entry))
*ptr = make(map[string]*Entry)
}
tagMap := nestedInfoVal.Elem().Field(j).Interface().(map[string](*Entry))
for key, val := range confMap {
if entr, ok := tagMap[key]; ok {
setter(entr, val, nameArg)
} else {
entr := new(Entry)
tagMap[key] = entr
setter(entr, val, nameArg)
sourceValue := reflect.ValueOf(source)
targetType := reflect.TypeOf(target)
targetValue := reflect.ValueOf(target)
if targetValue.Elem().Kind() == reflect.Struct && sourceValue.Elem().Kind() == reflect.Struct {
for i := 0; i < targetType.Elem().NumField(); i++ {
sourceValueMatched := sourceValue.Elem().FieldByName(targetType.Elem().Field(i).Name)
if sourceValueMatched.IsValid() {
if targetValue.Elem().Field(i).Type() == reflect.TypeOf(Entry{}) {
// get the fields which are part of the struct
switch sourceValueMatched.Type() {
case reflect.TypeOf(""):
setter(targetValue.Elem().Field(i).Addr().Interface().(*Entry), sourceValueMatched.String(), nameArg)
case reflect.TypeOf([]string{}):
setterSlice(targetValue.Elem().Field(i).Addr().Interface().(*Entry), sourceValueMatched.Interface().([]string), nameArg)
default:
panic(fmt.Errorf("can't convert an Entry to %s", targetValue.Elem().Field(i).Type()))
}
} else if sourceValueMatched.Kind() == reflect.Ptr {
// if we get a pointer, initialize if empty and then have a recursive call
if targetValue.Elem().Field(i).IsZero() {
targetValue.Elem().Field(i).Set(reflect.New(targetType.Elem().Field(i).Type.Elem()))
}
recursiveSetter(sourceValueMatched.Interface(), targetValue.Elem().Field(i).Interface(), nameArg, setter, setterSlice)
} else if sourceValueMatched.Type().Kind() == reflect.Map {
if targetValue.Elem().Field(i).IsZero() {
targetValue.Elem().Field(i).Set(reflect.MakeMap(targetType.Elem().Field(i).Type))
}
// delete a ap element which is only in the target
if targetValue.Elem().Field(i).Len() > 0 && targetValue.Elem().Field(i).Len() < 0 {
sourceIter := sourceValueMatched.MapRange()
targetIter := targetValue.Elem().Field(i).MapRange()
for targetIter.Next() {
sameKey := false
for sourceIter.Next() {
if sourceIter.Key() == targetIter.Key() {
sameKey = true
}
}
if !sameKey {
targetValue.Elem().Field(i).SetMapIndex(targetIter.Key(), reflect.Value{})
}
}
}
}
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string](*Entry)(nil)) {
confMap := valField.Interface().(map[string]string)
for key, val := range confMap {
tagMap := nodeInfoVal.Elem().Field(i).Interface().(map[string](*Entry))
if nodeInfoVal.Elem().Field(i).IsNil() {
tagMap = make(map[string]*Entry)
}
if entr, ok := tagMap[key]; ok {
setter(entr, val, nameArg)
} else {
entr := new(Entry)
tagMap[key] = entr
setter(entr, val, nameArg)
}
}
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string](*NetDevEntry)(nil)) {
netValMap := valField.Interface().(map[string](*NetDevs))
for netName, netVals := range netValMap {
netValsType := reflect.ValueOf(netVals)
netMap := nodeInfoVal.Elem().Field(i).Interface().(map[string](*NetDevEntry))
if nodeInfoVal.Elem().Field(i).IsNil() {
netMap = make(map[string]*NetDevEntry)
}
if _, ok := netMap[netName]; !ok {
var newNet NetDevEntry
newNet.Tags = make(map[string]*Entry)
netMap[netName] = &newNet
}
netInfoType := reflect.TypeOf(*netMap[netName])
netInfoVal := reflect.ValueOf(netMap[netName])
for j := 0; j < netInfoType.NumField(); j++ {
netVal := netValsType.Elem().FieldByName(netInfoType.Field(j).Name)
if netVal.IsValid() {
if netVal.Type().Kind() == reflect.String {
setter(netInfoVal.Elem().Field(j).Addr().Interface().((*Entry)), netVal.String(), nameArg)
} else if netVal.Type() == reflect.TypeOf(map[string]string{}) {
for key, val := range (netVal.Interface()).(map[string]string) {
//netTagMap := netInfoVal.Elem().Field(j).Interface().((map[string](*Entry)))
if _, ok := netInfoVal.Elem().Field(j).Interface().((map[string](*Entry)))[key]; !ok {
netInfoVal.Elem().Field(j).Interface().((map[string](*Entry)))[key] = new(Entry)
}
setter(netInfoVal.Elem().Field(j).Interface().((map[string](*Entry)))[key], val, nameArg)
}
sourceIter := sourceValueMatched.MapRange()
if sourceValueMatched.Type().Elem() == reflect.TypeOf("") {
// go over a simple map with strings
for sourceIter.Next() {
if !targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).IsValid() {
newEntr := new(Entry)
setter(newEntr, sourceIter.Value().String(), nameArg)
targetValue.Elem().Field(i).SetMapIndex(sourceIter.Key(), reflect.ValueOf(newEntr))
}
}
} else {
// now the complicated map which contains pointers to objects
for sourceIter.Next() {
if !targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).IsValid() {
newPtr := reflect.New(targetType.Elem().Field(i).Type.Elem().Elem())
targetValue.Elem().Field(i).SetMapIndex(sourceIter.Key(), newPtr)
}
recursiveSetter(sourceIter.Value().Interface(), targetValue.Elem().Field(i).MapIndex(sourceIter.Key()).Interface(), nameArg, setter, setterSlice)
}
}
}
}
}
}
}
/*
@@ -350,8 +225,11 @@ or *KernelConf, these pointer will set to nil. This will remove something like
ipmi: {} from nodes.conf
*/
func (info *NodeConf) Flatten() {
confType := reflect.TypeOf(info)
confVal := reflect.ValueOf(info)
recursiveFlatten(info)
}
func recursiveFlatten(strct interface{}) {
confType := reflect.TypeOf(strct)
confVal := reflect.ValueOf(strct)
for j := 0; j < confType.Elem().NumField(); j++ {
if confVal.Elem().Field(j).Type().Kind() == reflect.Ptr && !confVal.Elem().Field(j).IsNil() {
// iterate now over the ptr fields
@@ -371,149 +249,54 @@ func (info *NodeConf) Flatten() {
}
}
if setToNil {
switch confType.Elem().Field(j).Type {
case reflect.TypeOf((*IpmiConf)(nil)):
ptr := confVal.Elem().Field(j).Addr().Interface().(**IpmiConf)
*ptr = (*IpmiConf)(nil)
case reflect.TypeOf((*KernelConf)(nil)):
ptr := confVal.Elem().Field(j).Addr().Interface().(**KernelConf)
*ptr = (*KernelConf)(nil)
}
confVal.Elem().Field(j).Set(reflect.Zero(confVal.Elem().Field(j).Type()))
}
}
}
}
/*
Populates all fields of NetDevEntry with Set from the
values of NetDevs.
Actually not used, just for completeness.
Create a string slice, where every element represents a yaml entry, used for node/profile edit
in order to get a summary of all available elements
*/
func (netDev *NetDevEntry) SetFrom(netYaml *NetDevs) {
setWrap := func(entr *Entry, val string, nameArg string) {
entr.Set(val)
}
setSliceWrap := func(entr *Entry, val []string, nameArg string) {
entr.SetSlice(val)
}
netDev.setterFrom(netYaml, "", setWrap, setSliceWrap)
}
/*
Populates all fields of NetDevEntry with SetAlt from the
values of NetDevs. The string profileName is used to
destermine from which source/NodeInfo the entry came
from.
Actually not used, just for completeness.
*/
func (netDev *NetDevEntry) SetAltFrom(netYaml *NetDevs, profileName string) {
netDev.setterFrom(netYaml, profileName, (*Entry).SetAlt, (*Entry).SetAltSlice)
}
/*
Populates all fields of NodeInfo with SetDefault from the
values of NodeConf.
*/
func (netDev *NetDevEntry) SetDefFrom(netYaml *NetDevs) {
setWrap := func(entr *Entry, val string, nameArg string) {
entr.SetDefault(val)
}
setSliceWrap := func(entr *Entry, val []string, nameArg string) {
entr.SetDefaultSlice(val)
}
netDev.setterFrom(netYaml, "", setWrap, setSliceWrap)
}
/*
Abstract function for setting a NetDevEntry from a NetDevs
*/
func (netDev *NetDevEntry) setterFrom(netYaml *NetDevs, nameArg string,
setter func(*Entry, string, string),
setterSlice func(*Entry, []string, string)) {
// check if netYaml is empty
if netYaml == nil {
netYaml = new(NetDevs)
}
netValues := reflect.ValueOf(netDev)
netInfoType := reflect.TypeOf(*netYaml)
netInfoVal := reflect.ValueOf(*netYaml)
for j := 0; j < netInfoType.NumField(); j++ {
netVal := netValues.Elem().FieldByName(netInfoType.Field(j).Name)
if netVal.IsValid() {
if netInfoVal.Field(j).Type().Kind() == reflect.String {
setter(netVal.Addr().Interface().((*Entry)), netInfoVal.Field(j).String(), nameArg)
} else if netVal.Type() == reflect.TypeOf(map[string]string{}) {
// danger zone following code is not tested
for key, val := range (netVal.Interface()).(map[string]string) {
//netTagMap := netInfoVal.Elem().Field(j).Interface().((map[string](*Entry)))
if _, ok := netInfoVal.Elem().Field(j).Interface().((map[string](*Entry)))[key]; !ok {
netInfoVal.Elem().Field(j).Interface().((map[string](*Entry)))[key] = new(Entry)
}
setter(netInfoVal.Elem().Field(j).Interface().((map[string](*Entry)))[key], val, nameArg)
}
}
}
}
}
/*
Create a string slice, where every element represents a yaml entry
*/
func (nodeConf *NodeConf) UnmarshalConf(excludeList []string) (lines []string) {
nodeInfoType := reflect.TypeOf(nodeConf)
nodeInfoVal := reflect.ValueOf(nodeConf)
func UnmarshalConf(obj interface{}, excludeList []string) (lines []string) {
objType := reflect.TypeOf(obj)
// now iterate of every field
for i := 0; i < nodeInfoVal.Elem().NumField(); i++ {
if nodeInfoType.Elem().Field(i).Tag.Get("lopt") != "" {
if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok {
for i := 0; i < objType.NumField(); i++ {
if objType.Field(i).Tag.Get("comment") != "" {
if ymlStr, ok := getYamlString(objType.Field(i), excludeList); ok {
lines = append(lines, ymlStr...)
}
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr {
nestType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface())
if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok {
lines = append(lines, ymlStr...)
}
if objType.Field(i).Type.Kind() == reflect.Ptr && objType.Field(i).Tag.Get("yaml") != "" {
typeLine := objType.Field(i).Tag.Get("yaml")
if len(strings.Split(typeLine, ",")) > 1 {
typeLine = strings.Split(typeLine, ",")[0] + ":"
}
for j := 0; j < nestType.Elem().NumField(); j++ {
if nestType.Elem().Field(j).Tag.Get("lopt") != "" &&
!util.InSlice(excludeList, nestType.Elem().Field(j).Tag.Get("lopt")) {
if ymlStr, ok := getYamlString(nestType.Elem().Field(j), excludeList); ok {
for _, str := range ymlStr {
lines = append(lines, " "+str)
}
}
}
lines = append(lines, typeLine)
nestedLine := UnmarshalConf(reflect.New(objType.Field(i).Type.Elem()).Elem().Interface(), excludeList)
for _, ln := range nestedLine {
lines = append(lines, " "+ln)
}
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string]*NetDevs(nil)) {
netMap := nodeInfoVal.Elem().Field(i).Interface().(map[string]*NetDevs)
// add a default network so that it can hold values
key := "default"
if len(netMap) == 0 {
netMap[key] = new(NetDevs)
} else {
for keyIt := range netMap {
key = keyIt
break
}
} else if objType.Field(i).Type.Kind() == reflect.Map && objType.Field(i).Type.Elem().Kind() == reflect.Ptr {
typeLine := objType.Field(i).Tag.Get("yaml")
if len(strings.Split(typeLine, ",")) > 1 {
typeLine = strings.Split(typeLine, ",")[0] + ":"
}
if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok {
lines = append(lines, ymlStr[0]+":", " "+key+":")
netType := reflect.TypeOf(netMap[key])
for j := 0; j < netType.Elem().NumField(); j++ {
if ymlStr, ok := getYamlString(netType.Elem().Field(j), excludeList); ok {
for _, str := range ymlStr {
lines = append(lines, " "+str)
}
}
} // lines
} // this
} //not
} //do
lines = append(lines, typeLine, " element:")
nestedLine := UnmarshalConf(reflect.New(objType.Field(i).Type.Elem().Elem()).Elem().Interface(), excludeList)
for _, ln := range nestedLine {
lines = append(lines, " "+ln)
}
}
}
return lines
}
/*
Get the string of the yaml tag
*/
func getYamlString(myType reflect.StructField, excludeList []string) ([]string, bool) {
ymlStr := myType.Tag.Get("yaml")
if len(strings.Split(ymlStr, ",")) > 1 {
@@ -521,11 +304,15 @@ func getYamlString(myType reflect.StructField, excludeList []string) ([]string,
}
if util.InSlice(excludeList, ymlStr) {
return []string{""}, false
} else if myType.Tag.Get("lopt") == "" && myType.Type.Kind() == reflect.String {
} else if myType.Tag.Get("comment") == "" && myType.Type.Kind() == reflect.String {
return []string{""}, false
}
if myType.Type.Kind() == reflect.String {
ymlStr += ": string"
fieldType := myType.Tag.Get("type")
if fieldType == "" {
fieldType = "string"
}
ymlStr += ": " + fieldType
return []string{ymlStr}, true
} else if myType.Type == reflect.TypeOf([]string{}) {
return []string{ymlStr + ":", " - string"}, true
@@ -548,7 +335,6 @@ func (nodeConf *NodeConf) SetLopt(lopt string, value string) (found bool) {
nodeInfoVal := reflect.ValueOf(nodeConf)
// try to find the normal fields, networks come later
for i := 0; i < nodeInfoVal.Elem().NumField(); i++ {
//fmt.Println(nodeInfoType.Elem().Field(i).Tag.Get("lopt"), lopt)
if nodeInfoType.Elem().Field(i).Tag.Get("lopt") == lopt {
if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.String {
wwlog.Verbose("Found lopt %s mapping to %s, setting to %s\n",

View File

@@ -5,6 +5,7 @@ import (
"path"
"strings"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
)
@@ -30,7 +31,26 @@ func OverlaySourceDir(overlayName string) string {
/*
Returns the overlay name of the image for a given node
*/
func OverlayImage(nodeName string, overlayName []string) string {
func OverlayImage(nodeName string, overlayName []string, img_context ...string) string {
var name string
var context string
/* Check optional context argument. If missing, default to legacy. */
if len(img_context) == 0 {
context = "legacy"
} else {
context = img_context[0]
}
conf := warewulfconf.Get()
return path.Join(conf.Paths.WWProvisiondir, "overlays/", nodeName, strings.Join(overlayName, "-")+".img")
switch context {
case "legacy":
name = strings.Join(overlayName, "-")+".img"
default:
wwlog.Warn("Context %s passed to OverlayImage(), using %s to build image name.", context, "__" + strings.ToUpper(context) + "__")
name = "__" + strings.ToUpper(context) + "__.img"
}
return path.Join(conf.Paths.WWProvisiondir, "overlays/", nodeName, name)
}

View File

@@ -6,8 +6,8 @@ import (
"strconv"
"time"
"github.com/hpcng/warewulf/internal/pkg/node"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
)
@@ -37,13 +37,15 @@ type TemplateStruct struct {
node.NodeConf
// backward compatiblity
Container string
ThisNode *node.NodeInfo
}
/*
Initialize an TemplateStruct with the given node.NodeInfo
*/
func InitStruct(nodeInfo node.NodeInfo) TemplateStruct {
func InitStruct(nodeInfo *node.NodeInfo) TemplateStruct {
var tstruct TemplateStruct
tstruct.ThisNode = nodeInfo
controller := warewulfconf.Get()
nodeDB, err := node.New()
if err != nil {
@@ -55,7 +57,7 @@ func InitStruct(nodeInfo node.NodeInfo) TemplateStruct {
wwlog.Error("%s", err)
os.Exit(1)
}
// init some convininence vars
// init some convenience vars
tstruct.Id = nodeInfo.Id.Get()
tstruct.Hostname = nodeInfo.Id.Get()
// Backwards compatibility for templates using "Keys"
@@ -81,17 +83,17 @@ func InitStruct(nodeInfo node.NodeInfo) TemplateStruct {
tstruct.BuildTime = dt.Format("01-02-2006 15:04:05 MST")
tstruct.BuildTimeUnix = strconv.FormatInt(dt.Unix(), 10)
tstruct.NodeConf.Tags = map[string]string{}
tstruct.NodeConf.GetFrom(nodeInfo)
tstruct.NodeConf.GetFrom(*nodeInfo)
// FIXME: Set ipCIDR address at this point, will fail with
// invalid ipv4 addr
for _, network := range tstruct.NetDevs {
for _, network := range tstruct.NodeConf.NetDevs {
ipCIDR := net.IPNet{
IP: net.ParseIP(network.Ipaddr),
Mask: net.IPMask(net.ParseIP(network.Netmask))}
network.IpCIDR = ipCIDR.String()
}
// backward compatibilty
tstruct.Container = tstruct.ContainerName
tstruct.Container = tstruct.NodeConf.ContainerName
return tstruct

View File

@@ -2,13 +2,16 @@ package overlay
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path"
"strings"
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/hpcng/warewulf/internal/pkg/util"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
)
@@ -100,3 +103,20 @@ func templateContainerFileInclude(containername string, filepath string) string
}
return strings.TrimSuffix(string(content), "\n")
}
func createIgnitionJson(node *node.NodeInfo) string {
conf, rep, err := node.GetConfig()
if len(conf.Storage.Disks) == 0 && len(conf.Storage.Filesystems) == 0 {
wwlog.Debug("no disks or filesystems present, don't create a json object")
return ""
}
if err != nil {
wwlog.Error("disk, filesystem configuration has following error: ", fmt.Sprint(err))
return fmt.Sprint(err)
}
if rep != "" {
wwlog.Warn("%s storage configuration has following non fatal problems: %s", node.Id, rep)
}
tmpYaml, _ := json.Marshal(&conf)
return string(tmpYaml)
}

View File

@@ -0,0 +1,62 @@
package overlay
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/hpcng/warewulf/internal/pkg/node"
)
func Test_createIgnitionJson(t *testing.T) {
node_config := `WW_INTERNAL: 43
nodes:
n1:
disks:
/dev/vda:
wipe_table: "true"
partitions:
scratch:
should_exist: "true"
filesystems:
/dev/disk/by-partlabel/scratch:
format: btrfs
path: /scratch
wipe_filesystem: "true"`
expected_json := `{
"ignition": {
"version": "3.1.0"
},
"storage": {
"disks": [
{
"device": "/dev/vda",
"partitions": [
{
"label": "scratch",
"shouldExist": true,
"wipePartitionEntry": false
}
],
"wipeTable": true
}
],
"filesystems": [
{
"device": "/dev/disk/by-partlabel/scratch",
"format": "btrfs",
"path": "/scratch",
"wipeFilesystem": true
}
]
}
}`
config, parse_error := node.Parse([]byte(node_config))
assert.Empty(t, parse_error)
nodeInfos, info_error := config.FindAllNodes()
assert.Empty(t, info_error)
node := nodeInfos[0]
assert.JSONEq(t, expected_json, createIgnitionJson(&node))
}

View File

@@ -47,13 +47,13 @@ func BuildAllOverlays(nodes []node.NodeInfo) error {
sysOverlays := n.SystemOverlay.GetSlice()
wwlog.Info("Building system overlays for %s: [%s]", n.Id.Get(), strings.Join(sysOverlays, ", "))
err := BuildOverlay(n, sysOverlays)
err := BuildOverlay(n, sysOverlays, "system")
if err != nil {
return errors.Wrapf(err, "could not build system overlays %v for node %s", sysOverlays, n.Id.Get())
}
runOverlays := n.RuntimeOverlay.GetSlice()
wwlog.Info("Building runtime overlays for %s: [%s]", n.Id.Get(), strings.Join(runOverlays, ", "))
err = BuildOverlay(n, runOverlays)
err = BuildOverlay(n, runOverlays, "runtime")
if err != nil {
return errors.Wrapf(err, "could not build runtime overlays %v for node %s", runOverlays, n.Id.Get())
}
@@ -66,11 +66,12 @@ func BuildAllOverlays(nodes []node.NodeInfo) error {
func BuildSpecificOverlays(nodes []node.NodeInfo, overlayNames []string) error {
for _, n := range nodes {
wwlog.Info("Building overlay for %s: %v", n.Id.Get(), overlayNames)
err := BuildOverlay(n, overlayNames)
if err != nil {
return errors.Wrapf(err, "could not build overlay for node %s: %v", n.Id.Get(), overlayNames)
for _, overlayName := range overlayNames {
err := BuildOverlay(n, []string{overlayName})
if err != nil {
return errors.Wrapf(err, "could not build overlay %s for node %s", overlayName, n.Id.Get())
}
}
}
@@ -139,10 +140,18 @@ func OverlayInit(overlayName string) error {
/*
Build the given overlays for a node and create a Image for them
*/
func BuildOverlay(nodeInfo node.NodeInfo, overlayNames []string) error {
func BuildOverlay(nodeInfo node.NodeInfo, overlayNames []string, img_context ...string) error {
var context string
/* Check optional context argument. If missing, default to legacy. */
if len(img_context) == 0 {
context = "legacy"
} else {
context = img_context[0]
}
// create the dir where the overlay images will reside
name := fmt.Sprintf("overlay %s/%v", nodeInfo.Id.Get(), overlayNames)
overlayImage := OverlayImage(nodeInfo.Id.Get(), overlayNames)
overlayImage := OverlayImage(nodeInfo.Id.Get(), overlayNames, context)
overlayImageDir := path.Dir(overlayImage)
err := os.MkdirAll(overlayImageDir, 0755)
@@ -236,7 +245,7 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
wwlog.Debug("Created directory in overlay: %s", location)
} else if filepath.Ext(location) == ".ww" {
tstruct := InitStruct(nodeInfo)
tstruct := InitStruct(&nodeInfo)
tstruct.BuildSource = path.Join(overlaySourceDir, location)
wwlog.Verbose("Evaluating overlay template file: %s", location)
destFile := strings.TrimSuffix(location, ".ww")
@@ -257,7 +266,7 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
line := fileScanner.Text()
filenameFromTemplate := reg.FindAllStringSubmatch(line, -1)
if len(filenameFromTemplate) != 0 {
wwlog.Debug("Found multifile comment, new filename %s", filenameFromTemplate[0][1])
wwlog.Debug("Found multiple comment, new filename %s", filenameFromTemplate[0][1])
if foundFileComment {
err = CarefulWriteBuffer(path.Join(outputDir, destFileName),
fileBuffer, backupFile, info.Mode())
@@ -375,6 +384,14 @@ func RenderTemplateFile(fileName string, data TemplateStruct) (
"inc": func(i int) int { return i + 1 },
"dec": func(i int) int { return i - 1 },
"file": func(str string) string { return fmt.Sprintf("{{ /* file \"%s\" */ }}", str) },
"IgnitionJson": func() string {
str := createIgnitionJson(data.ThisNode)
if str != "" {
return str
}
writeFile = false
return ""
},
"abort": func() string {
wwlog.Debug("abort file called in %s", fileName)
writeFile = false
@@ -388,6 +405,12 @@ func RenderTemplateFile(fileName string, data TemplateStruct) (
"split": func(s string, d string) []string {
return strings.Split(s, d)
},
"tr": func(source, old, new string) string {
return strings.Replace(source, old, new, -1)
},
"replace": func(source, old, new string) string {
return strings.Replace(source, old, new, -1)
},
// }).ParseGlob(path.Join(OverlayDir, destFile+".ww*"))
}).ParseGlob(fileName)
if err != nil {

View File

@@ -9,9 +9,9 @@ import (
"syscall"
"time"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/version"
warewulfconf "github.com/hpcng/warewulf/internal/pkg/config"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/pkg/errors"
)
@@ -73,7 +73,7 @@ func DaemonInitLogging() error {
}
wwlog.SetLogFormatter(wwlog.DefaultFormatter)
wwlog.SetLogWriters(logwriter, logwriter)
wwlog.SetLogWriter(logwriter)
}
@@ -107,12 +107,12 @@ func DaemonStart() error {
os.Setenv("WAREWULFD_LOGLEVEL", strconv.Itoa(logLevel))
}
f, err := os.OpenFile(WAREWULFD_LOGFILE, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
f, err := os.OpenFile(WAREWULFD_LOGFILE, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o644)
if err != nil {
return err
}
p, err := os.OpenFile(WAREWULFD_PIDFILE, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
p, err := os.OpenFile(WAREWULFD_PIDFILE, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}

View File

@@ -59,6 +59,8 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
status_stage := status_stages[rinfo.stage]
var stage_overlays []string
var stage_file string = ""
var img_context string = "legacy" /* Default to old image name behavior */
// TODO: when module version is upgraded to go1.18, should be 'any' type
var tmpl_data interface{}
@@ -128,6 +130,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
} else if rinfo.stage == "system" {
if len(node.SystemOverlay.GetSlice()) != 0 {
stage_overlays = node.SystemOverlay.GetSlice()
img_context = rinfo.stage
} else {
wwlog.Warn("No system overlay set for node %s", node.Id.Get())
}
@@ -135,8 +138,10 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
} else if rinfo.stage == "runtime" {
if rinfo.overlay != "" {
stage_overlays = []string{rinfo.overlay}
img_context = "legacy"
} else if len(node.RuntimeOverlay.GetSlice()) != 0 {
stage_overlays = node.RuntimeOverlay.GetSlice()
img_context = rinfo.stage
} else {
wwlog.Warn("No runtime overlay set for node %s", node.Id.Get())
}
@@ -147,7 +152,8 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
stage_file, err = getOverlayFile(
node.Id.Get(),
stage_overlays,
conf.Warewulf.AutobuildOverlays)
conf.Warewulf.AutobuildOverlays,
img_context )
if err != nil {
w.WriteHeader(http.StatusInternalServerError)

View File

@@ -44,9 +44,10 @@ func sendFile(
func getOverlayFile(
nodeId string,
stage_overlays []string,
autobuild bool ) (stage_file string, err error) {
autobuild bool,
img_context string) (stage_file string, err error) {
stage_file = overlay.OverlayImage(nodeId, stage_overlays)
stage_file = overlay.OverlayImage(nodeId, stage_overlays, img_context)
err = nil
build := !util.IsFile(stage_file)

View File

@@ -2,29 +2,29 @@ package wwlog
import (
"fmt"
"os"
"io"
"os"
"reflect"
"runtime"
"sort"
"strings"
"time"
"runtime"
"reflect"
"sort"
)
type LogRecord struct {
Level int
Err error
Msg string
Args []interface{}
Pc uintptr
File string
Line int
Time time.Time
Err error
Msg string
Args []interface{}
Pc uintptr
File string
Line int
Time time.Time
}
/*
Format a log message from a record
Only called if rec.level >= logLevel
Format a log message from a record
Only called if rec.level >= logLevel
*/
type LogFormatter func(logLevel int, rec *LogRecord) string
@@ -47,19 +47,20 @@ var (
DEBUG = SetLevelName(10, "DEBUG")
)
var levelNums = []int{0}
var levelNames = []string{"NOTSET"}
var logLevel = INFO
var logOut io.Writer = os.Stdout
var logErr io.Writer = os.Stderr
var logFormatter LogFormatter = DefaultFormatter
var (
levelNums = []int{0}
levelNames = []string{"NOTSET"}
logLevel = INFO
logErr io.Writer = os.Stderr
logFormatter LogFormatter = DefaultFormatter
)
func LevelNameEff(level int) (int, int, string) {
n := len(levelNums)
idx := sort.SearchInts(levelNums, level)
if idx >= n {
idx = n-1
idx = n - 1
}
eff_level := levelNums[idx]
@@ -69,7 +70,6 @@ func LevelNameEff(level int) (int, int, string) {
}
func LevelName(level int) string {
_, _, name := LevelNameEff(level)
return name
}
@@ -80,8 +80,7 @@ func SetLevelName(level int, name string) int {
if idx < n && levelNums[idx] == level {
levelNames[idx] = name
}else{
} else {
levelNums = append(levelNums, level)
levelNames = append(levelNames, name)
@@ -101,7 +100,7 @@ func SetLevelName(level int, name string) int {
func DefaultFormatter(logLevel int, rec *LogRecord) string {
message := fmt.Sprintf(rec.Msg, rec.Args...)
if ( !strings.HasSuffix(message, "\n") ) {
if !strings.HasSuffix(message, "\n") {
// ensure written messages are separated by at least one newline
message += "\n"
}
@@ -109,10 +108,9 @@ func DefaultFormatter(logLevel int, rec *LogRecord) string {
if rec.Err != nil {
if logLevel < VERBOSE {
// when debugging errors, add file and line number, and any stack trace
message += fmt.Sprintf("%s:%d\n%+v\n", rec.File, rec.Line, rec.Err )
}else{
message += fmt.Sprintf("%v\n", rec.Err )
message += fmt.Sprintf("%s:%d\n%+v\n", rec.File, rec.Line, rec.Err)
} else {
message += fmt.Sprintf("%v\n", rec.Err)
}
}
@@ -133,7 +131,6 @@ func DefaultFormatter(logLevel int, rec *LogRecord) string {
return fmt.Sprintf("%-11s: %s", name, message)
}
func EnabledForLevel(level int) bool {
return level >= logLevel
}
@@ -153,17 +150,15 @@ func GetLogLevel() int {
}
/*
Set the log output writers
By default they are set to os.Stdout and os.Stderr
Set the log output writer
By default they are set to output writer
*/
func SetLogWriters(out io.Writer, err io.Writer) {
logOut = out
func SetLogWriter(err io.Writer) {
logErr = err
Debug("Set log writers")
}
func GetLogWriters() (io.Writer, io.Writer) {
return logOut, logErr
func GetLogWriter() io.Writer {
return logErr
}
/*
@@ -180,10 +175,9 @@ func GetLogFormatter() LogFormatter {
}
/*
Internal method to create a log record
Internal method to create a log record
*/
func LogCaller(level int, skip int, err error, message string, a ...interface{}) {
if EnabledForLevel(level) {
pc, file, line, ok := runtime.Caller(skip + 1)
if !ok {
@@ -191,22 +185,19 @@ func LogCaller(level int, skip int, err error, message string, a ...interface{})
}
rec := LogRecord{
Level : level,
Err : err,
Msg : message,
Args : a,
Pc : pc,
File : file,
Line : line,
Time : time.Now() }
Level: level,
Err: err,
Msg: message,
Args: a,
Pc: pc,
File: file,
Line: line,
Time: time.Now(),
}
message = logFormatter(logLevel, &rec)
if level >= ERROR {
fmt.Fprint(logErr, message)
} else {
fmt.Fprint(logOut, message)
}
fmt.Fprint(logErr, message)
}
}
@@ -218,8 +209,9 @@ func Printf(level int, message string, a ...interface{}) {
LogCaller(level, 1, nil, message, a...)
}
/*******************************************************************************
Named log level functions
/*
******************************************************************************
Named log level functions
*/
func Log(level int, message string, a ...interface{}) {
LogCaller(level, 1, nil, message, a...)

View File

@@ -0,0 +1,284 @@
package wwlog
import (
"errors"
"io"
"os"
"strings"
"testing"
)
type (
levelTypeFunc func(int, string, ...interface{})
msgTypeFunc func(string, ...interface{})
errTypeFunc func(error, string, ...interface{})
levelErrTypeFunc func(int, error, string, ...interface{})
)
func Test_Log(t *testing.T) {
t.Logf("Running Test_Log test")
SetLogLevel(DEBUG)
tests := []struct {
name string
msgTypeFunc msgTypeFunc
levelTypeFunc levelTypeFunc
errTypeFunc errTypeFunc
levelErrTypeFunc levelErrTypeFunc
level int
err error
message string
args interface{}
expect string
exactMatch bool
}{
{
name: "Log DEBUG",
levelTypeFunc: Log,
level: DEBUG,
message: "Log",
expect: "DEBUG : Log\n",
exactMatch: true,
},
{
name: "Log ERROR",
levelTypeFunc: Log,
level: ERROR,
message: "Log",
expect: "ERROR : Log\n",
exactMatch: true,
},
{
name: "LogExc",
levelErrTypeFunc: LogExc,
level: INFO,
err: errors.New("error"),
message: "Log",
expect: "INFO : Log\nerror\n",
},
{
name: "Debug",
msgTypeFunc: Debug,
message: "Debug",
expect: "DEBUG : Debug\n",
exactMatch: true,
},
{
name: "DebugExc",
errTypeFunc: DebugExc,
message: "Debug",
err: errors.New("error"),
expect: "DEBUG : Debug\nerror\n",
},
{
name: "SecDebug",
msgTypeFunc: SecDebug,
message: "Debug",
expect: "SECDEBUG : Debug\n",
exactMatch: true,
},
{
name: "Verbose",
msgTypeFunc: Verbose,
message: "Verbose",
expect: "VERBOSE: Verbose\n",
exactMatch: true,
},
{
name: "VerboseExc",
errTypeFunc: VerboseExc,
message: "Verbose",
err: errors.New("error"),
expect: "VERBOSE: Verbose\nerror\n",
},
{
name: "SecVerbose",
msgTypeFunc: SecVerbose,
message: "Verbose",
expect: "SECVERBOSE : Verbose\n",
exactMatch: true,
},
{
name: "Info",
msgTypeFunc: Info,
message: "Info",
expect: "INFO : Info\n",
exactMatch: true,
},
{
name: "InfoExc",
errTypeFunc: InfoExc,
message: "Info",
err: errors.New("error"),
expect: "INFO : Info\nerror\n",
},
{
name: "SecInfo",
msgTypeFunc: SecInfo,
message: "Info",
expect: "SECINFO: Info\n",
exactMatch: true,
},
{
name: "Serv",
msgTypeFunc: Serv,
message: "Serv",
expect: "SERV : Serv\n",
exactMatch: true,
},
{
name: "Recv",
msgTypeFunc: Recv,
message: "Recv",
expect: "RECV : Recv\n",
exactMatch: true,
},
{
name: "Send",
msgTypeFunc: Send,
message: "Send",
expect: "SEND : Send\n",
exactMatch: true,
},
{
name: "Warn",
msgTypeFunc: Warn,
message: "Warn",
expect: "WARN : Warn\n",
exactMatch: true,
},
{
name: "WarnExc",
errTypeFunc: WarnExc,
message: "Warn",
err: errors.New("error"),
expect: "WARN : Warn\nerror\n",
},
{
name: "SecWarn",
msgTypeFunc: SecWarn,
message: "Warn",
expect: "SECWARN: Warn\n",
exactMatch: true,
},
{
name: "Error",
msgTypeFunc: Error,
message: "Error",
expect: "ERROR : Error\n",
exactMatch: true,
},
{
name: "ErrorExc",
errTypeFunc: ErrorExc,
message: "Error",
err: errors.New("error"),
expect: "ERROR : Error\nerror\n",
},
{
name: "SecError",
msgTypeFunc: SecError,
message: "Error",
expect: "SECERROR : Error\n",
exactMatch: true,
},
{
name: "Denied",
msgTypeFunc: Denied,
message: "Denied",
expect: "DENIED : Denied\n",
exactMatch: true,
},
{
name: "Critical",
msgTypeFunc: Critical,
message: "Critical",
expect: "CRITICAL : Critical\n",
exactMatch: true,
},
{
name: "CriticalExc",
errTypeFunc: CriticalExc,
message: "Critical",
err: errors.New("error"),
expect: "CRITICAL : Critical\nerror\n",
},
{
name: "SecCritical",
msgTypeFunc: SecCritical,
message: "Critical",
expect: "SECCRITICAL: Critical\n",
exactMatch: true,
},
}
for _, tt := range tests {
oldErr := os.Stderr
r, w, err := os.Pipe()
if err != nil {
t.Errorf("Could not create stderr pipe, err:%v", err)
t.FailNow()
}
os.Stderr = w
// make sure os.Stderr is always reset
defer func() {
os.Stderr = oldErr
}()
SetLogWriter(os.Stderr)
if tt.msgTypeFunc != nil {
if tt.args != nil {
tt.msgTypeFunc(tt.message, tt.args)
} else {
tt.msgTypeFunc(tt.message)
}
} else if tt.levelTypeFunc != nil {
if tt.args != nil {
tt.levelTypeFunc(tt.level, tt.message, tt.args)
} else {
tt.levelTypeFunc(tt.level, tt.message)
}
} else if tt.errTypeFunc != nil {
if tt.args != nil {
tt.errTypeFunc(tt.err, tt.message, tt.args)
} else {
tt.errTypeFunc(tt.err, tt.message)
}
} else if tt.levelErrTypeFunc != nil {
if tt.args != nil {
tt.levelErrTypeFunc(tt.level, tt.err, tt.message, tt.args)
} else {
tt.levelErrTypeFunc(tt.level, tt.err, tt.message)
}
} else {
os.Stderr = oldErr
t.Errorf("One of `msgTypeFunc`, `levelTypeFunc` and `errTypeFunc` should be set")
t.FailNow()
}
outCh := make(chan string, 1)
go func() {
out, _ := io.ReadAll(r)
outCh <- string(out)
}()
w.Close()
os.Stderr = oldErr
out := <-outCh
if tt.exactMatch {
if out != tt.expect {
t.Errorf("Test: %s failed with unexpected output out: `%s`, expect: `%s`", tt.name, out, tt.expect)
t.FailNow()
}
} else {
for _, line := range strings.Split(tt.expect, "\n") {
if !strings.Contains(out, line) {
t.Errorf("Test: %s should contain output expect: `%s`, out:`%s`", tt.name, line, out)
t.FailNow()
}
}
}
}
}

View File

@@ -7,8 +7,17 @@ devpts /dev/pts devpts gid=5,mode=620 0 0
tmpfs /run/shm tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
proc /proc proc defaults 0 0
{{- if .FileSystems }}
# mounts for local file systems created with ignition in nodes.conf
# all with noauto as mounts happens with systemd units
{{- range $fsdevice,$fs := .FileSystems }}
{{ $fsdevice }} {{ $fs.Path }} {{ $fs.Format }}
{{- if $fs.MountOptions }} noauto{{range $index,$opt := $fs.MountOptions }},{{ $opt }}{{ end }} 0 0
{{- else }} noauto,defaults 0 0
{{- end }}{{ end }}{{ end }}
{{- $IP:=.Ipaddr }}
{{- if .Nfs.Enabled }}
# nfs mounts provided in warewulf.conf
{{- range .Nfs.ExportsExtended }}
{{- if .Mount }}
{{ $IP }}:{{ .Path }} {{ .Path }} nfs {{ .MountOptions }} 0 0

View File

@@ -0,0 +1,32 @@
[Unit]
Description=Ignition warewulf (disks)
Documentation=https://hpcng.github.io/warewulf
ConditionPathExists=/warewulf/ignition.json
ConditionPathExists=/usr/lib/dracut/modules.d/30ignition/ignition
DefaultDependencies=false
Before=ignition-ww4-mount.service
# This stage runs between `basic.target` and `initrd-root-device.target`,
# see https://www.freedesktop.org/software/systemd/man/bootup.html
# Make sure to run before the file system checks, as sgdisk will trigger
# udev events, potentially resulting in race conditions due to disappearing
# devices.
# Note that CL runs this before `local-fs-pre.target` to allow for configs that
# completely wipe the rootfs. Though we're not there yet. But we still run
# before `sysroot.mount` on principle.
Before=initrd-root-device.target
Before=sysroot.mount
#OnFailure=emergency.target
#OnFailureJobMode=isolate
# This stage requires udevd to detect disk partitioning changes.
Requires=systemd-udevd.service
After=systemd-udevd.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/lib/dracut/modules.d/30ignition/ignition --platform=metal --stage=disks --config-cache /warewulf/ignition.json

View File

@@ -0,0 +1 @@
/etc/systemd/system/ignition-ww4-disks.service

View File

@@ -0,0 +1 @@
/etc/systemd/system/ww4-disks.target

View File

@@ -0,0 +1,20 @@
# This file is autogenerated by warewulf
# Host: {{.BuildHost}}
# Time: {{.BuildTime}}
# Source: {{.BuildSource}}
[Unit]
Description=mount ww4 disks
# make sure that the disks are available
Requires=ignition-ww4-disks.service
After=ignition-ww4-disks.service
Prequisite=ignition-ww4-disks.service
# Get the mounts
{{- range $fsdevice,$fs := .FileSystems }}
{{- $prefix := $fsdevice }}{{ $suffix := "mount" }}
{{- if eq $fs.Format "swap"}}
{{- $prefix = tr $fsdevice "/dev/disk/by-partlabel/" "dev-disk-by\\x2dpartlabel-"}}{{ $suffix = "swap"}}
{{- else }}
{{- if $fs.Path }}{{ $prefix = tr $fs.Path "/" "" }}{{ end }}
{{- end }}
Wants={{ print $prefix "." $suffix }}
{{- end }}

View File

@@ -0,0 +1,38 @@
{{- $host := .BuildHost }}
{{- $time := .BuildTime }}
{{- $source := .BuildSource }}
{{- range $fsdevice,$fs := .FileSystems }}
{{- $prefix := tr $fs.Path "/" "" }}{{ $suffix:="mount"}}
{{- if $fs.Label }}{{ $prefix = $fs.Label }}{{ end }}
{{- if eq $fs.Format "swap"}}{{ $prefix = tr $fsdevice "/dev/disk/by-partlabel/" "dev-disk-by\\x2dpartlabel-"}}{{ $suffix = "swap"}}{{ end }}
{{- $filename := print $prefix "." $suffix }}
{{- file $filename }}
# This file is autogenerated by warewulf
# Host: {{ $host }}
# Time: {{ $time }}
# Source: {{ $source }}
{{- if eq $fs.Format "swap"}}
[Unit]
ConditionPathExists=/warewulf/ignition.json
After=ignition-ww4-disks.service
Before=swap.target
[Swap]
What={{ $fsdevice }}
[Install]
RequiredBy=swap.target
{{- else }}
[Unit]
ConditionPathExists=/warewulf/ignition.json
Before=local-fs.target
After=ignition-ww4-disks.service
[Mount]
Where={{ $fs.Path }}
What={{ $fsdevice }}
Type={{ $fs.Format }}
{{- if $fs.MountOptions }}
Options={{range $index,$opt := $fs.MountOptions }}{{if $index }},{{ end }}{{ $opt }}{{ end }}
{{- end }}
[Install]
RequiredBy=local-fs.target
{{- end }}
{{- end }}

View File

@@ -11,7 +11,7 @@ if test -f "/warewulf/config"; then
else
echo "ERROR: Warewulf configuration file not found... rebooting in 1 minute"
sleep 60
/sbin/reboot
echo b > /proc/sysrq-trigger || /sbin/reboot
fi
echo "Warewulf v4 is now booting: $WWHOSTNAME"
@@ -53,4 +53,4 @@ fi
echo
echo "There was a problem with the provisioning process, rebooting in 1 minute..."
sleep 60
/sbin/reboot
echo b > /proc/sysrq-trigger || /sbin/reboot

View File

@@ -0,0 +1 @@
{{ IgnitionJson }}

View File

@@ -0,0 +1,37 @@
package hostlist
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Single(t *testing.T) {
assert.Equal(t, []string{"node1"}, Expand([]string{"node1"}))
}
func Test_Multiple(t *testing.T) {
assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node1", "node2"}))
}
func Test_Range(t *testing.T) {
assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node[1-2]"}))
}
func Test_Internal_Comma(t *testing.T) {
assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node[1,2]"}))
}
func Test_Mixed_Range_Comma(t *testing.T) {
assert.Equal(t, []string{"node1", "node2", "node3"}, Expand([]string{"node[1,2-3]"}))
}
// not currently supported
//
// func Test_External_Comma(t *testing.T) {
// assert.Equal(t, []string{"node1", "node2"}, Expand([]string{"node1,node2"}))
// }

View File

@@ -349,3 +349,13 @@ issues in most circumstances:
If you are still getting "Not enough memory" or "No space left on
device" errors, try disabling any "memory hole" features or updating
your system BIOS or firmware.
Duplicating a container
============================
It is possible to duplicate an installed image by using :
.. code-block:: console
# wwctl container copy CONTAINER_NAME DUPLICATED_CONTAINER_NAME
This kind of duplication can be useful if you are looking for canary tests.

View File

@@ -0,0 +1,89 @@
===============
Disk Management
===============
Warewulf itself does not manage disks, partitions, or file systems
directly, but provides structures in the configuration for these
objects. At the moment warewulf supports `ignition` to create the
partitions and file systems.
.. note::
It is not currently possible to manage the root file system with
Warewulf.
Warewulf can be used, for example, to create `swap` partitions or
`/scratch` file systems.
Storage objects
===============
The format of the storage objects is inspired by `butane/ignition`;
but, where `butane/ignition` uses lists for holding disks, partitions
and file systems, Warewulf uses maps instead.
A node or profile can have several disks, where each disk is
identified by the path to its block device. Every disks holds a map to
its partitions and a `bool` switch to indicate if an existing
partition table should be overwritten if it does not matched the
desired configuration.
Each partition is identified by its label. The partition number can be
omitted, but specifying it is recommended as `ignition` may fail
without it. Partition sizes should also be set (specified in MiB),
except of the last partition: if no size is given, the maximum
available size is used. Each partition has the switches `should_exist`
and `wipe_partition_entry` which control the partition creation
process.
File systems are identified by their underlying block device,
preferably using the `/dev/by-partlabel` format. Except for a `swap`
partition, an absolute path for the mount point must be specified for
each file system. Depending on the container used, valid formats are
`btrfs`, `ext3`, `ext4`, and `xfs`. Each file system has the switch
`wipe_filesystem` to control whether an existing file system is wiped.
Ignition Implementation
=======================
The ignition implementation uses systemd services, as the underlying
`sgdisk` command relies on dbus notifications. All necessary services
are distributed by the `wwinit` overlay and depends on the existence
of the file `/warewulf/ignition.json`. This file is created by the
template function `{{ createIgnitionJson }}` only if the configuration
contains necessary specifications for disks, partitions, and file
systems. If the file `/warewulf/ignition.json` exists, the service
`ignition-disks-ww4.service` calls the ignition binary which takes
creates partitions and file systems. A systemd `.mount` unit is
created for each configured file system, which also creates the
necessary mount points in the root file system. These mount units are
required by the enabled `ww4-disks.target`. Entries in `/etc/fstab`
are created with the `no_auto` option so that file systems can be
easily mounted.
Example
=======
The following command will create a `/scratch` file system on the node
`n01`
.. code-block:: shell
wwctl node set n01 \
--diskname /dev/vda --diskwipe \
--partname scratch --partcreate \
--fsname scratch --fsformat btrfs --fspath /scratch --fswipe
As this is a single file system, the partition number can be omitted.
A swap partition with 1Gig can be added with
.. code-block:: shell
wwctl node set n01 \
--diskname /dev/vda \
--partname swap --partsize=1024 --partnumber 1 \
--fsname swap --fsformat swap --fspath swap
which has the partition number `1` so that it will be added before the
`/scratch` partition.

View File

@@ -143,7 +143,7 @@ permissions, etc.
..
note::
There is now possibility to delete files with an overlay! [example needed]
There is no possibility to delete files with an overlay!
Build
-----

View File

@@ -70,3 +70,32 @@ occur:
#. Container:
#. The container now boots exactly as any operating system would
expect
Node status
========================
During the whole provisioning process of your nodes, you can check their status
through the following command :
.. code-block:: console
# wwctl node status
NODENAME STAGE SENT LASTSEEN (s)
================================================================================
c001 RUNTIME_OVERLAY generic.img.gz 16
For each node, there is 4 different stages :
* **IPXE**
* **KERNEL**
* **SYSTEM_OVERLAY**
* **RUNTIME_OVERLAY**
Those stages are directly linked to the The Provisioning Process section and are provided
by the wwclient
Thanks to ``wwctl node status`` command, you can also check your communication between
the warewulf client and server.
Depending on your warewulf version, you should see a reset of the last seen counter every 1 minute due to the
warewulf runtime overlay update.

View File

@@ -211,3 +211,17 @@ A given string can be split into substrings.
{{ $x := "a:b:c" -}}
{{ $y := (split $x ":") -}}
{{ range $y }} {{.}} {{ end }}
Node specific files
-------------------
Sometimes there is the need to have specific files for every node
which can't be generated by a template. You can include these files
with following template:
.. code-block:: go
{{- $filename := print "/root/" .Id "-payload" }}
{{ Include $filename }}

View File

@@ -0,0 +1,33 @@
============================
Controlling Warewulf (wwctl)
============================
Warewulf's command-line interface is based primarily around the
``wwctl`` command. This command has sub-commands for each major
component of Warewulf's functionality.
* ``configure`` configures external services that Warewulf depends on
* ``container`` configures containers (node images)
* ``kernel`` configures override kernels
* ``node`` manages nodes in the cluster
* ``profiles`` defines configuration which can be applied to multiple
nodes
* ``overlays`` manages nodes
* ``power`` turns nodes on and off
* ``ssh`` provides basic parallel ssh functionality
All of these subcommands (and their respective sub-subcommands) have
built-in help with either ``wwctl help`` or ``--help``.
Hostlists
=========
Many of the commands (e.g., ``wwctl node list`` support a "hostlist"
syntax for referring to multiple nodes at once. Hostlist expressions
support both ranges and comma-separated numerical lists.
For example:
* ``node[1-2]`` expands to ``node1 node2``
* ``node[1,3]`` expands to ``node1 node3``
* ``node[1,5-6]`` expands to ``node1 node5 node6``

View File

@@ -21,12 +21,12 @@ vet`` on the full codebase.
Running the full test suite
===========================
The Warewulf ``Makefile`` includes a ``test-it`` target which runs the
The Warewulf ``Makefile`` includes a ``test`` target which runs the
full test suite.
.. code-block:: console
$ make test-it
$ make test
Using delve

View File

@@ -0,0 +1,274 @@
=============================
Development Environment (Vagrant)
=============================
Create Rocky Linux 9 virtual machine for Warewulf testbed using Vagrant
Host system requirements
=====================
#. CPU supports H/W virtualization.
#. KVM kernel module available and loaded.
CPU H/W Virtualization support
--------------------------------
Check CPU virtualization capability using following command. If your system has Intel CPU, you will see :code:`Intel VT` here, and if your system has AMD CPU, you will see :code:`AMD-V` here.
.. code-block:: bash
lscpu | grep Virtualization
Virtualization: AMD-V
Virtualization type: full
KVM kernel module
---------------------
.. code-block:: bash
lsmod | grep kvm
ccp 118784 1 kvm_amd
kvm 1105920 1 kvm_amd
irqbypass 16384 1 kvm
Setup development environment on Rocky Linux 9
==============================================================
Install QEMU, libvirt
-----------------------
.. code-block:: bash
# Install packages
sudo dnf install -y libvirt qemu-kvm \
libguestfs virtio-win guestfs-tools libguestfs-inspect-icons virt-win-reg \
virt-install virt-top
# Enable and start libvirtd
sudo systemctl enable --now libvirtd
# Add user to libvirt group
sudo usermod -aG libvirt rocky
Install Cockpit (Optional)
-----------------
.. code-block:: bash
# Install packages
sudo dnf install -y cockpit cockpit-machines
# Enable and start cockpit (http://localhost:9090)
sudo systemctl enable --now cockpit.socket
Install Vagrant, vagrant-libvirt plug-in and vagrant-reload plug-in
---------------------------------------------------------------------
.. code-block:: bash
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo dnf install -y vagrant
sudo dnf group install -y "Development tools"
sudo dnf config-manager --set-enabled crb
sudo dnf install -y libvirt-devel
vagrant plugin install vagrant-libvirt
vagrant plugin install vagrant-reload
Vagrant box and Vagrantfile for Warewulf sandbox
===================================================
Create Rocky Linux 9.2 vagrant box
------------------------------------
.. code-block:: bash
cat << 'EOF' > box-metadata.json
{
"name" : "rockylinux/9",
"description" : "Rocky Linux 9 2.0.0",
"versions" : [
{
"version" : "2.0.0-20230513.0",
"providers" : [
{
"name" : "libvirt",
"url" : "https://dl.rockylinux.org/pub/rocky/9.2/images/x86_64/Rocky-9-Vagrant-Libvirt-9.2-20230513.0.x86_64.box"
}
]
}
]
}
EOF
vagrant box add box-metadata.json
Vagrantfile
------------
.. code-block:: bash
mkdir -p ~/warewulf-sandbox
cd ~/warewulf-sandbox
cat << 'EOF' > Vagrantfile
Vagrant.configure("2") do |config|
number_of_node = ENV["NODES"] || 2
branch = ENV["BRANCH"] || "v4.4.0"
config.vm.define :head do |head|
head.vm.box = "rockylinux/9"
head.vm.box_version = "2.0.0-20230513.0"
head.vm.hostname = "warewulf"
head.vm.network "private_network",
ip: "192.168.200.254",
netmask: "255.255.255.0",
libvirt__network_name: "pxe",
libvirt__dhcp_enabled: false
head.vm.synced_folder ".", "/vagrant", type: "nfs", nfs_version: 4, nfs_udp: false
head.vm.provider :libvirt do |libvirt|
libvirt.cpu_mode = "host-passthrough"
libvirt.memory = '8192'
libvirt.cpus = '2'
libvirt.machine_virtual_size = 40
end
head.vm.provision "shell", inline: <<-SHELL
dnf install -y cloud-utils-growpart
growpart /dev/vda 5
xfs_growfs /dev/vda5
SHELL
head.vm.provision "shell", inline: <<-SHELL
dnf groupinstall -y "Development Tools"
dnf install -y epel-release
dnf config-manager --set-enabled crb
dnf install -y golang tftp-server dhcp-server nfs-utils gpgme-devel libassuan-devel
cd /tmp
git clone https://github.com/hpcng/warewulf.git
cd warewulf
git checkout v4.4.0
make clean Defaults.mk \
PREFIX=/usr \
BINDIR=/usr/bin \
SYSCONFDIR=/etc \
DATADIR=/usr/share \
LOCALSTATEDIR=/var/lib \
SHAREDSTATEDIR=/var/lib \
MANDIR=/usr/share/man \
INFODIR=/usr/share/info \
DOCDIR=/usr/share/doc \
SRVDIR=/var/lib \
TFTPDIR=/var/lib/tftpboot \
SYSTEMDDIR=/usr/lib/systemd/system \
BASHCOMPDIR=/etc/bash_completion.d/ \
FIREWALLDDIR=/usr/lib/firewalld/services \
WWCLIENTDIR=/warewulf
make all
make install
systemctl disable --now firewalld
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
SHELL
head.vm.provision "reload"
head.vm.provision "shell", inline: <<-SHELL
cat << 'CONF' | sudo tee /etc/warewulf/warewulf.conf
WW_INTERNAL: 43
ipaddr: 192.168.200.254
netmask: 255.255.255.0
network: 192.168.200.0
warewulf:
port: 9873
secure: false
update interval: 60
autobuild overlays: true
host overlay: true
syslog: false
dhcp:
enabled: true
range start: 192.168.200.50
range end: 192.168.200.99
systemd name: dhcpd
tftp:
enabled: true
systemd name: tftp
nfs:
enabled: true
export paths:
- path: /home
export options: rw,sync
mount options: defaults
mount: true
- path: /opt
export options: ro,sync,no_root_squash
mount options: defaults
mount: false
systemd name: nfs-server
CONF
sed -i 's@ExecStart=/usr/bin/wwctl server start@ExecStart=/usr/bin/wwctl server start -d -v@' /usr/lib/systemd/system/warewulfd.service
systemctl enable --now warewulfd
wwctl configure --all
wwctl container import docker://ghcr.io/hpcng/warewulf-rockylinux:9 rocky-9
wwctl profile set --yes --container rocky-9 "default"
wwctl profile set --yes --netdev eth1 --netmask 255.255.255.0 --gateway 192.168.200.254 "default"
wwctl node add n0001.cluster -I 192.168.200.101 --discoverable true
wwctl node add n0002.cluster -I 192.168.200.102 --discoverable true
SHELL
end
(1..number_of_node).each do |i|
config.vm.define :"n000#{i}", autostart: false do |node|
node.vm.hostname = "n000#{i}"
node.vm.network "private_network",
libvirt__network_name: "pxe"
node.vm.provider :libvirt do |compute|
compute.cpu_mode = 'host-passthrough'
compute.memory = '8192'
compute.cpus = '2'
boot_network = {'network' => 'pxe'}
compute.boot boot_network
end
end
end
end
EOF
Spin up head node
===================
.. code-block:: bash
vagrant up
Spin up compute nodes
=======================
.. code-block:: bash
vagrant up n0001
# Wait until n0001 becomes ready
vagrant up n0002

View File

@@ -14,6 +14,7 @@ Welcome to the Warewulf User Guide!
Control Server Setup <contents/setup>
Warewulf Installation <contents/installation>
Warewulf Configuration <contents/configuration>
Controlling Warewulf (wwctl) <contents/wwctl>
Warewulf Initialization <contents/initialization>
Container Management <contents/containers>
Kernel Management <contents/kernel>
@@ -22,6 +23,7 @@ Welcome to the Warewulf User Guide!
Warewulf Overlays <contents/overlays>
Node Provisioning <contents/provisioning>
IPMI <contents/ipmi>
Disk Management <contents/disks>
Security <contents/security>
Templating <contents/templating>
@@ -31,6 +33,7 @@ Welcome to the Warewulf User Guide!
EL7 (CentOS and RHEL) <quickstart/el7>
EL8 (Rocky Linux and RHEL) <quickstart/el8>
EL9 (Rocky Linux and RHEL) <quickstart/el9>
openSUSE Leap and SLES 15 <quickstart/suse15>
.. toctree::
@@ -40,6 +43,7 @@ Welcome to the Warewulf User Guide!
Contributing <contributing/contributing>
Debugging <contributing/debugging>
Documentation <contributing/documentation>
Development Environment (Vagrant) <contributing/development-environment-vagrant>
Development Environment (KVM) <contributing/development-environment-kvm>
Development Environment (VirtualBox) <contributing/development-environment-vbox>
Glossary <contents/glossary>

View File

@@ -9,10 +9,27 @@ Install Warewulf and dependencies
sudo dnf groupinstall "Development Tools"
sudo dnf install epel-release
sudo dnf install golang tftp-server dhcp-server nfs-utils gpgpme-devel libassuan-devel
sudo dnf config-manager --set-enabled powertools
sudo dnf install golang tftp-server dhcp-server nfs-utils gpgme-devel libassuan-devel
git clone https://github.com/hpcng/warewulf.git
cd warewulf
make clean Defaults.mk \
PREFIX=/usr \
BINDIR=/usr/bin \
SYSCONFDIR=/etc \
DATADIR=/usr/share \
LOCALSTATEDIR=/var/lib \
SHAREDSTATEDIR=/var/lib \
MANDIR=/usr/share/man \
INFODIR=/usr/share/info \
DOCDIR=/usr/share/doc \
SRVDIR=/var/lib \
TFTPDIR=/var/lib/tftpboot \
SYSTEMDDIR=/usr/lib/systemd/system \
BASHCOMPDIR=/etc/bash_completion.d/ \
FIREWALLDDIR=/usr/lib/firewalld/services \
WWCLIENTDIR=/warewulf
make all
sudo make install

185
userdocs/quickstart/el9.rst Normal file
View File

@@ -0,0 +1,185 @@
=====================================
EL9 Quickstart (Rocky Linux and RHEL)
=====================================
Install Warewulf and dependencies
=================================
.. code-block:: bash
sudo dnf groupinstall "Development Tools"
sudo dnf install epel-release
sudo dnf config-manager --set-enabled crb
sudo dnf install golang tftp-server dhcp-server nfs-utils gpgme-devel libassuan-devel
git clone https://github.com/hpcng/warewulf.git
cd warewulf
make clean Defaults.mk \
PREFIX=/usr \
BINDIR=/usr/bin \
SYSCONFDIR=/etc \
DATADIR=/usr/share \
LOCALSTATEDIR=/var/lib \
SHAREDSTATEDIR=/var/lib \
MANDIR=/usr/share/man \
INFODIR=/usr/share/info \
DOCDIR=/usr/share/doc \
SRVDIR=/var/lib \
TFTPDIR=/var/lib/tftpboot \
SYSTEMDDIR=/usr/lib/systemd/system \
BASHCOMPDIR=/etc/bash_completion.d/ \
FIREWALLDDIR=/usr/lib/firewalld/services \
WWCLIENTDIR=/warewulf
make all
sudo make install
Configure firewalld
===================
Restart firewalld to register the added service file, add the service
to the default zone, and reload.
.. code-block:: bash
sudo systemctl restart firewalld
sudo firewall-cmd --permanent --add-service warewulf
sudo firewall-cmd --permanent --add-service nfs
sudo firewall-cmd --permanent --add-service tftp
sudo firewall-cmd --reload
Configure the controller
========================
Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've
set the appropriate configuration parameters. Here are some of the
defaults for reference assuming that ``192.168.200.1`` is the IP
address of your cluster's private network interface:
.. code-block:: yaml
ipaddr: 192.168.200.1
netmask: 255.255.255.0
warewulf:
port: 9873
secure: false
update interval: 60
dhcp:
enabled: true
range start: 192.168.200.10
range end: 192.168.200.99
template: default
systemd name: dhcpd
tftp:
enabled: true
tftproot: /var/lib/tftpboot
systemd name: tftp
nfs:
systemd name: nfs-server
exports:
- /home
- /var/warewulf
.. note::
The DHCP range ends at ``192.168.200.99`` and as you will see
below, the first node static IP address (post boot) is configured
to ``192.168.200.100``.
Start and enable the Warewulf service
=====================================
.. code-block:: bash
# Start and enable the warewulfd service
sudo systemctl enable --now warewulfd
Configure system services automatically
=======================================
There are a number of services and configurations that Warewulf relies
on to operate. If you wish to configure all services, you can do so
individually (omitting the ``--all``) will print a help and usage
instructions.
.. code-block:: bash
sudo wwctl configure --all
.. note::
If you just installed the system fresh and have SELinux enforcing,
you may need to reboot the system at this stage to properly set the
contexts of the TFTP contents. After rebooting, you might also need
to run ``$ sudo restorecon -Rv /var/lib/tftpboot/`` if there are
errors with TFTP still.
Pull and build the VNFS container (including the kernel)
========================================================
This will pull a basic VNFS container from Docker Hub and import the
default running kernel from the controller node and set both in the
"default" node profile.
.. code-block:: bash
sudo wwctl container import docker://ghcr.io/hpcng/warewulf-rockylinux:9 rocky-9
Set up the default node profile
===============================
Node configurations can be set via node profiles. Each node by default
is configured to be part of the ``default`` node profile, so any
changes you make to that profile will affect all nodes.
The following command will set the container we just imported above to
the ``default`` node profile:
.. code-block:: bash
sudo wwctl profile set --yes --container rocky-8 "default"
Next we set some default networking configurations for the first
ethernet device. On modern Linux distributions, the name of the device
is not critical, as it will be setup according to the HW
address. Because all nodes will share the netmask and gateway
configuration, we can set them in the default profile as follows:
.. code-block:: bash
sudo wwctl profile set --yes --netdev eth0 --netmask 255.255.255.0 --gateway 192.168.200.1 "default"
Once those configurations have been set, you can view the changes by
listing the profiles as follows:
.. code-block:: bash
sudo wwctl profile list -a
Add a node
==========
Adding nodes can be done while setting configurations in one
command. Here we are setting the IP address of ``eth0`` and setting
this node to be discoverable, which will then automatically have the
HW address added to the configuration as the node boots.
Node names must be unique. If you have node groups and/or multiple
clusters, designate them using dot notation.
Note that the full node configuration comes from both cascading
profiles and node configurations which always supersede profile
configurations.
.. code-block:: bash
sudo wwctl node add n0000.cluster --ipaddr 192.168.200.100 --discoverable true
At this point you can view the basic configuration of this node by
typing the following:
.. code-block:: bash
sudo wwctl node list -a n0000.cluster
Turn on your compute node and watch it boot!

View File

@@ -16,7 +16,7 @@ Install Warewulf and dependencies
git clone https://github.com/hpcng/warewulf.git
cd warewulf
PREFIX=/usr SYSCONFDIR=/etc TFTPDIR=/srv/tftproot LOCALSTATEDIR=/var/lib make genconfig
PREFIX=/usr SYSCONFDIR=/etc TFTPDIR=/srv/tftproot LOCALSTATEDIR=/var/lib make clean Defaults.mk
make all
sudo make install

View File

@@ -71,7 +71,7 @@ system for large clusters of bare metal and/or virtual systems.
%build
%{?with_offline:OFFLINE_BUILD=1}
# Install to sharedstatedir by redirecting LOCALSTATEDIR
make genconfig \
make Defaults.mk \
PREFIX=%{_prefix} \
BINDIR=%{_bindir} \
SYSCONFDIR=%{_sysconfdir} \
@@ -92,7 +92,8 @@ make
%install
export NO_BRP_STALE_LINK_ERROR=yes
make install DESTDIR=%{buildroot}
make install \
DESTDIR=%{buildroot}
%pre
@@ -116,14 +117,16 @@ getent group %{wwgroup} >/dev/null || groupadd -r %{wwgroup}
%files
%defattr(-, root, %{wwgroup})
%dir %{_sysconfdir}/warewulf
%config(noreplace) %{_sysconfdir}/warewulf/*
%config(noreplace) %{_sysconfdir}/warewulf/warewulf.conf
%config(noreplace) %{_sysconfdir}/warewulf/wwapi*.conf
%config(noreplace) %{_sysconfdir}/warewulf/examples
%config(noreplace) %{_sysconfdir}/warewulf/ipxe
%config(noreplace) %attr(0640,-,-) %{_sysconfdir}/warewulf/nodes.conf
%{_sysconfdir}/bash_completion.d/wwctl
%dir %{_sharedstatedir}/warewulf
%{_sharedstatedir}/warewulf/chroots
%{_sharedstatedir}/warewulf/overlays
%{srvdir}/warewulf
%attr(-, root, root) %{_bindir}/wwctl
%attr(-, root, root) %{_bindir}/wwapi*
@@ -162,10 +165,10 @@ getent group %{wwgroup} >/dev/null || groupadd -r %{wwgroup}
- Update license string
- Make shared store relocatable
* Fri Sep 24 2021 Michael L. Young <myoung@ciq.co> - 4.2.0-1
* Fri Sep 24 2021 Michael L. Young <myoung@ciq.com> - 4.2.0-1
- Update spec file to use systemd macros
- Use macros to refer to system paths
- Update syntax
* Tue Jan 26 2021 14:46:24 JST Brian Clemens <bclemens@ctrliq.com> - 4.0.0
* Tue Jan 26 2021 14:46:24 JST Brian Clemens <bclemens@ciq.com> - 4.0.0
- Initial release