diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bc829b3d..dc9d8664 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/.gitignore b/.gitignore index 7859c0c0..42e1c380 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/API.md b/API.md index d5e03737..2b94cf88 100644 --- a/API.md +++ b/API.md @@ -19,6 +19,7 @@ wwctl container delete wwctl container import wwctl container list wwctl container show +wwctl container copy Some notes on the files: diff --git a/CHANGELOG.md b/CHANGELOG.md index 70449bbb..bc0e55f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 45ee2d7d..9b394cae 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -5,8 +5,8 @@ * Jeremy Siadal @jcsiadal * Niko Kivel @kivel * Shannon V. Davidson -* Brian Clemens @brianclemens -* Michael L. Young @ajyounge +* Brian Clemens @brianclemens +* Michael L. Young @ajyounge * Ian Kaneshiro @ikaneshiro * Carter Dodd @kcdodd * MatthewHink @MatthewHink @@ -22,7 +22,8 @@ * David McFarlane @Prepultrue * Jean-Baptiste Denis * TerranWorks -* Jonathon Anderson @anderbubble -* Brian Phan +* Jonathon Anderson @anderbubble +* Brian Phan * Jeffrey Frey @jtfrey * Xu Yang(Jason Yang) @JasonYangShadow +* Arnaud LECOMTE diff --git a/Dockerfile b/Dockerfile index dfffc58e..616c294d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 \ diff --git a/LICENSE.md b/LICENSE.md index fa199c60..31dfc198 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -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. \ No newline at end of file +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. diff --git a/Makefile b/Makefile index 1abd6869..424e106b 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/README.md b/README.md index 53ab8058..2100a7b2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/Variables.mk b/Variables.mk new file mode 100644 index 00000000..51be6796 --- /dev/null +++ b/Variables.mk @@ -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 diff --git a/cmd/update_configuration/vers42/nodes.conf.bak b/cmd/update_configuration/vers42/nodes.conf.bak deleted file mode 100644 index 792e48a2..00000000 --- a/cmd/update_configuration/vers42/nodes.conf.bak +++ /dev/null @@ -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 diff --git a/go.mod b/go.mod index 61146ac7..ad9c083d 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 249ac530..6a7ee2ff 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/app/api/wwapid/wwapid.go b/internal/app/api/wwapid/wwapid.go index 5042b8c7..311addc6 100644 --- a/internal/app/api/wwapid/wwapid.go +++ b/internal/app/api/wwapid/wwapid.go @@ -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) { diff --git a/internal/app/wwclient/root.go b/internal/app/wwclient/root.go index 5af79840..88df49e1 100644 --- a/internal/app/wwclient/root.go +++ b/internal/app/wwclient/root.go @@ -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) diff --git a/internal/app/wwctl/container/copy/main.go b/internal/app/wwctl/container/copy/main.go new file mode 100644 index 00000000..5ecc6e3a --- /dev/null +++ b/internal/app/wwctl/container/copy/main.go @@ -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 + +} diff --git a/internal/app/wwctl/container/copy/root.go b/internal/app/wwctl/container/copy/root.go new file mode 100644 index 00000000..fad0dbaa --- /dev/null +++ b/internal/app/wwctl/container/copy/root.go @@ -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 +} diff --git a/internal/app/wwctl/container/delete/main.go b/internal/app/wwctl/container/delete/main.go index 250b5ac2..576ccef7 100644 --- a/internal/app/wwctl/container/delete/main.go +++ b/internal/app/wwctl/container/delete/main.go @@ -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 } diff --git a/internal/app/wwctl/container/root.go b/internal/app/wwctl/container/root.go index 363f82b5..6ffcf279 100644 --- a/internal/app/wwctl/container/root.go +++ b/internal/app/wwctl/container/root.go @@ -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()) } diff --git a/internal/app/wwctl/node/add/main.go b/internal/app/wwctl/node/add/main.go index 694e3e13..125c49e6 100644 --- a/internal/app/wwctl/node/add/main.go +++ b/internal/app/wwctl/node/add/main.go @@ -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) diff --git a/internal/app/wwctl/node/add/main_test.go b/internal/app/wwctl/node/add/main_test.go index 71513ea1..8a243724 100644 --- a/internal/app/wwctl/node/add/main_test.go +++ b/internal/app/wwctl/node/add/main_test.go @@ -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` diff --git a/internal/app/wwctl/node/add/root.go b/internal/app/wwctl/node/add/root.go index a74a4cf4..caab5cee 100644 --- a/internal/app/wwctl/node/add/root.go +++ b/internal/app/wwctl/node/add/root.go @@ -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() diff --git a/internal/app/wwctl/node/edit/main.go b/internal/app/wwctl/node/edit/main.go index c21a786c..adde59aa 100644 --- a/internal/app/wwctl/node/edit/main.go +++ b/internal/app/wwctl/node/edit/main.go @@ -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 } diff --git a/internal/app/wwctl/node/list/main.go b/internal/app/wwctl/node/list/main.go index f4e027c0..6e44fe92 100644 --- a/internal/app/wwctl/node/list/main.go +++ b/internal/app/wwctl/node/list/main.go @@ -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() } diff --git a/internal/app/wwctl/node/list/main_test.go b/internal/app/wwctl/node/list/main_test.go index 2717d156..850636f9 100644 --- a/internal/app/wwctl/node/list/main_test.go +++ b/internal/app/wwctl/node/list/main_test.go @@ -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() } }) diff --git a/internal/app/wwctl/node/list/root.go b/internal/app/wwctl/node/list/root.go index 37acc641..a3b226e7 100644 --- a/internal/app/wwctl/node/list/root.go +++ b/internal/app/wwctl/node/list/root.go @@ -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 diff --git a/internal/app/wwctl/node/set/main.go b/internal/app/wwctl/node/set/main.go index 4bc44f21..00e08135 100644 --- a/internal/app/wwctl/node/set/main.go +++ b/internal/app/wwctl/node/set/main.go @@ -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) } diff --git a/internal/app/wwctl/node/set/main_test.go b/internal/app/wwctl/node/set/main_test.go new file mode 100644 index 00000000..4feda0d3 --- /dev/null +++ b/internal/app/wwctl/node/set/main_test.go @@ -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() + } + }) + } +} diff --git a/internal/app/wwctl/node/set/root.go b/internal/app/wwctl/node/set/root.go index ac4c1f14..5a278518 100644 --- a/internal/app/wwctl/node/set/root.go +++ b/internal/app/wwctl/node/set/root.go @@ -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 } diff --git a/internal/app/wwctl/overlay/imprt/main.go b/internal/app/wwctl/overlay/imprt/main.go index e83a5e59..f8a863ec 100644 --- a/internal/app/wwctl/overlay/imprt/main.go +++ b/internal/app/wwctl/overlay/imprt/main.go @@ -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") diff --git a/internal/app/wwctl/overlay/imprt/main_test.go b/internal/app/wwctl/overlay/imprt/main_test.go new file mode 100644 index 00000000..bbed5a63 --- /dev/null +++ b/internal/app/wwctl/overlay/imprt/main_test.go @@ -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() + } + }) +} diff --git a/internal/app/wwctl/overlay/imprt/root.go b/internal/app/wwctl/overlay/imprt/root.go index fd9b957f..f0f9e8a4 100644 --- a/internal/app/wwctl/overlay/imprt/root.go +++ b/internal/app/wwctl/overlay/imprt/root.go @@ -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. diff --git a/internal/app/wwctl/overlay/show/main.go b/internal/app/wwctl/overlay/show/main.go index d1a27958..8475c85e 100644 --- a/internal/app/wwctl/overlay/show/main.go +++ b/internal/app/wwctl/overlay/show/main.go @@ -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 { diff --git a/internal/app/wwctl/profile/add/main.go b/internal/app/wwctl/profile/add/main.go index 40ad46a4..8c04ece8 100644 --- a/internal/app/wwctl/profile/add/main.go +++ b/internal/app/wwctl/profile/add/main.go @@ -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) diff --git a/internal/app/wwctl/profile/add/main_test.go b/internal/app/wwctl/profile/add/main_test.go index f1a084e7..bbc49d0d 100644 --- a/internal/app/wwctl/profile/add/main_test.go +++ b/internal/app/wwctl/profile/add/main_test.go @@ -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: {} `, }, diff --git a/internal/app/wwctl/profile/add/root.go b/internal/app/wwctl/profile/add/root.go index 3a0d4336..42db93d0 100644 --- a/internal/app/wwctl/profile/add/root.go +++ b/internal/app/wwctl/profile/add/root.go @@ -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 } diff --git a/internal/app/wwctl/profile/edit/main.go b/internal/app/wwctl/profile/edit/main.go index cbe018b3..a91ca3d9 100644 --- a/internal/app/wwctl/profile/edit/main.go +++ b/internal/app/wwctl/profile/edit/main.go @@ -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") } diff --git a/internal/app/wwctl/profile/list/main.go b/internal/app/wwctl/profile/list/main.go index 3a984baf..57b8866c 100644 --- a/internal/app/wwctl/profile/list/main.go +++ b/internal/app/wwctl/profile/list/main.go @@ -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 { diff --git a/internal/app/wwctl/profile/list/main_test.go b/internal/app/wwctl/profile/list/main_test.go index 62e2ec03..5741e471 100644 --- a/internal/app/wwctl/profile/list/main_test.go +++ b/internal/app/wwctl/profile/list/main_test.go @@ -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 `, }, } diff --git a/internal/app/wwctl/profile/list/root.go b/internal/app/wwctl/profile/list/root.go index 14689314..c686e525 100644 --- a/internal/app/wwctl/profile/list/root.go +++ b/internal/app/wwctl/profile/list/root.go @@ -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 } diff --git a/internal/app/wwctl/profile/set/main.go b/internal/app/wwctl/profile/set/main.go index efa20fd2..fe553cbc 100644 --- a/internal/app/wwctl/profile/set/main.go +++ b/internal/app/wwctl/profile/set/main.go @@ -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) } diff --git a/internal/app/wwctl/profile/set/root.go b/internal/app/wwctl/profile/set/root.go index 0c1fd56b..534e4d85 100644 --- a/internal/app/wwctl/profile/set/root.go +++ b/internal/app/wwctl/profile/set/root.go @@ -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 } diff --git a/internal/pkg/api/container/container.go b/internal/pkg/api/container/container.go index 5ae4347a..2539ff50 100644 --- a/internal/pkg/api/container/container.go +++ b/internal/pkg/api/container/container.go @@ -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) diff --git a/internal/pkg/api/node/list.go b/internal/pkg/api/node/list.go index 5001638e..7f79f10c 100644 --- a/internal/pkg/api/node/list.go +++ b/internal/pkg/api/node/list.go @@ -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)) } } } diff --git a/internal/pkg/api/node/methods.go b/internal/pkg/api/node/methods.go deleted file mode 100644 index 8943d455..00000000 --- a/internal/pkg/api/node/methods.go +++ /dev/null @@ -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 -} diff --git a/internal/pkg/api/node/node.go b/internal/pkg/api/node/node.go index 13a10c15..f8d76565 100644 --- a/internal/pkg/api/node/node.go +++ b/internal/pkg/api/node/node.go @@ -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 { diff --git a/internal/pkg/api/profile/list.go b/internal/pkg/api/profile/list.go index a06a4c42..dd153240 100644 --- a/internal/pkg/api/profile/list.go +++ b/internal/pkg/api/profile/list.go @@ -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 { diff --git a/internal/pkg/api/profile/profile.go b/internal/pkg/api/profile/profile.go index 5b0a50c3..e6ca6c68 100644 --- a/internal/pkg/api/profile/profile.go +++ b/internal/pkg/api/profile/profile.go @@ -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") } diff --git a/internal/pkg/api/routes/v1/routes.proto b/internal/pkg/api/routes/v1/routes.proto index 336eba66..8684b5e8 100644 --- a/internal/pkg/api/routes/v1/routes.proto +++ b/internal/pkg/api/routes/v1/routes.proto @@ -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) = { diff --git a/internal/pkg/api/routes/wwapiv1/routes.pb.go b/internal/pkg/api/routes/wwapiv1/routes.pb.go index 3f87f8a6..4e25603f 100644 --- a/internal/pkg/api/routes/wwapiv1/routes.pb.go +++ b/internal/pkg/api/routes/wwapiv1/routes.pb.go @@ -3,17 +3,17 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.0 -// protoc v3.9.2 +// protoc-gen-go v1.28.1 +// protoc v4.24.0 // source: routes.proto package wwapiv1 import ( - empty "github.com/golang/protobuf/ptypes/empty" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" ) @@ -33,6 +33,7 @@ const ( GetNodeList_Network GetNodeList_ListType = 2 GetNodeList_Long GetNodeList_ListType = 3 GetNodeList_All GetNodeList_ListType = 4 + GetNodeList_FullAll GetNodeList_ListType = 5 ) // Enum value maps for GetNodeList_ListType. @@ -43,6 +44,7 @@ var ( 2: "Network", 3: "Long", 4: "All", + 5: "FullAll", } GetNodeList_ListType_value = map[string]int32{ "Simple": 0, @@ -50,6 +52,7 @@ var ( "Network": 2, "Long": 3, "All": 4, + "FullAll": 5, } ) @@ -77,7 +80,7 @@ func (x GetNodeList_ListType) Number() protoreflect.EnumNumber { // Deprecated: Use GetNodeList_ListType.Descriptor instead. func (GetNodeList_ListType) EnumDescriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{14, 0} + return file_routes_proto_rawDescGZIP(), []int{15, 0} } type NodeDBHash struct { @@ -248,6 +251,62 @@ func (x *ContainerDeleteParameter) GetContainerNames() []string { return nil } +// ContainerCopyParameter contains 2 inputs : first one for the source container name and second one for the duplicated container name. +type ContainerCopyParameter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ContainerSource string `protobuf:"bytes,1,opt,name=containerSource,proto3" json:"containerSource,omitempty"` + ContainerDestination string `protobuf:"bytes,2,opt,name=containerDestination,proto3" json:"containerDestination,omitempty"` +} + +func (x *ContainerCopyParameter) Reset() { + *x = ContainerCopyParameter{} + if protoimpl.UnsafeEnabled { + mi := &file_routes_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContainerCopyParameter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContainerCopyParameter) ProtoMessage() {} + +func (x *ContainerCopyParameter) ProtoReflect() protoreflect.Message { + mi := &file_routes_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContainerCopyParameter.ProtoReflect.Descriptor instead. +func (*ContainerCopyParameter) Descriptor() ([]byte, []int) { + return file_routes_proto_rawDescGZIP(), []int{3} +} + +func (x *ContainerCopyParameter) GetContainerSource() string { + if x != nil { + return x.ContainerSource + } + return "" +} + +func (x *ContainerCopyParameter) GetContainerDestination() string { + if x != nil { + return x.ContainerDestination + } + return "" +} + // ContainerImportParameter has all input for importing a container. type ContainerImportParameter struct { state protoimpl.MessageState @@ -266,7 +325,7 @@ type ContainerImportParameter struct { func (x *ContainerImportParameter) Reset() { *x = ContainerImportParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[3] + mi := &file_routes_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -279,7 +338,7 @@ func (x *ContainerImportParameter) String() string { func (*ContainerImportParameter) ProtoMessage() {} func (x *ContainerImportParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[3] + mi := &file_routes_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -292,7 +351,7 @@ func (x *ContainerImportParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerImportParameter.ProtoReflect.Descriptor instead. func (*ContainerImportParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{3} + return file_routes_proto_rawDescGZIP(), []int{4} } func (x *ContainerImportParameter) GetSource() string { @@ -362,7 +421,7 @@ type ContainerInfo struct { func (x *ContainerInfo) Reset() { *x = ContainerInfo{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[4] + mi := &file_routes_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -375,7 +434,7 @@ func (x *ContainerInfo) String() string { func (*ContainerInfo) ProtoMessage() {} func (x *ContainerInfo) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[4] + mi := &file_routes_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -388,7 +447,7 @@ func (x *ContainerInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerInfo.ProtoReflect.Descriptor instead. func (*ContainerInfo) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{4} + return file_routes_proto_rawDescGZIP(), []int{5} } func (x *ContainerInfo) GetName() string { @@ -445,7 +504,7 @@ type ContainerListResponse struct { func (x *ContainerListResponse) Reset() { *x = ContainerListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[5] + mi := &file_routes_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -458,7 +517,7 @@ func (x *ContainerListResponse) String() string { func (*ContainerListResponse) ProtoMessage() {} func (x *ContainerListResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[5] + mi := &file_routes_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -471,7 +530,7 @@ func (x *ContainerListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerListResponse.ProtoReflect.Descriptor instead. func (*ContainerListResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{5} + return file_routes_proto_rawDescGZIP(), []int{6} } func (x *ContainerListResponse) GetContainers() []*ContainerInfo { @@ -493,7 +552,7 @@ type ContainerShowParameter struct { func (x *ContainerShowParameter) Reset() { *x = ContainerShowParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[6] + mi := &file_routes_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -506,7 +565,7 @@ func (x *ContainerShowParameter) String() string { func (*ContainerShowParameter) ProtoMessage() {} func (x *ContainerShowParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[6] + mi := &file_routes_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -519,7 +578,7 @@ func (x *ContainerShowParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerShowParameter.ProtoReflect.Descriptor instead. func (*ContainerShowParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{6} + return file_routes_proto_rawDescGZIP(), []int{7} } func (x *ContainerShowParameter) GetContainerName() string { @@ -544,7 +603,7 @@ type ContainerShowResponse struct { func (x *ContainerShowResponse) Reset() { *x = ContainerShowResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[7] + mi := &file_routes_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -557,7 +616,7 @@ func (x *ContainerShowResponse) String() string { func (*ContainerShowResponse) ProtoMessage() {} func (x *ContainerShowResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[7] + mi := &file_routes_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -570,7 +629,7 @@ func (x *ContainerShowResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerShowResponse.ProtoReflect.Descriptor instead. func (*ContainerShowResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{7} + return file_routes_proto_rawDescGZIP(), []int{8} } func (x *ContainerShowResponse) GetName() string { @@ -613,7 +672,7 @@ type ContainerSyncUserParameter struct { func (x *ContainerSyncUserParameter) Reset() { *x = ContainerSyncUserParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[8] + mi := &file_routes_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -626,7 +685,7 @@ func (x *ContainerSyncUserParameter) String() string { func (*ContainerSyncUserParameter) ProtoMessage() {} func (x *ContainerSyncUserParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[8] + mi := &file_routes_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -639,7 +698,7 @@ func (x *ContainerSyncUserParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerSyncUserParameter.ProtoReflect.Descriptor instead. func (*ContainerSyncUserParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{8} + return file_routes_proto_rawDescGZIP(), []int{9} } func (x *ContainerSyncUserParameter) GetContainerName() string { @@ -661,7 +720,7 @@ type NodeNames struct { func (x *NodeNames) Reset() { *x = NodeNames{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[9] + mi := &file_routes_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -674,7 +733,7 @@ func (x *NodeNames) String() string { func (*NodeNames) ProtoMessage() {} func (x *NodeNames) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[9] + mi := &file_routes_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -687,7 +746,7 @@ func (x *NodeNames) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeNames.ProtoReflect.Descriptor instead. func (*NodeNames) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{9} + return file_routes_proto_rawDescGZIP(), []int{10} } func (x *NodeNames) GetNodeNames() []string { @@ -711,7 +770,7 @@ type NodeField struct { func (x *NodeField) Reset() { *x = NodeField{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[10] + mi := &file_routes_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -724,7 +783,7 @@ func (x *NodeField) String() string { func (*NodeField) ProtoMessage() {} func (x *NodeField) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[10] + mi := &file_routes_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -737,7 +796,7 @@ func (x *NodeField) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeField.ProtoReflect.Descriptor instead. func (*NodeField) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{10} + return file_routes_proto_rawDescGZIP(), []int{11} } func (x *NodeField) GetSource() string { @@ -774,7 +833,7 @@ type NetDev struct { func (x *NetDev) Reset() { *x = NetDev{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[11] + mi := &file_routes_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -787,7 +846,7 @@ func (x *NetDev) String() string { func (*NetDev) ProtoMessage() {} func (x *NetDev) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[11] + mi := &file_routes_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -800,7 +859,7 @@ func (x *NetDev) ProtoReflect() protoreflect.Message { // Deprecated: Use NetDev.ProtoReflect.Descriptor instead. func (*NetDev) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{11} + return file_routes_proto_rawDescGZIP(), []int{12} } func (x *NetDev) GetField() map[string]*NodeField { @@ -832,7 +891,7 @@ type NodeInfo struct { func (x *NodeInfo) Reset() { *x = NodeInfo{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[12] + mi := &file_routes_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -845,7 +904,7 @@ func (x *NodeInfo) String() string { func (*NodeInfo) ProtoMessage() {} func (x *NodeInfo) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[12] + mi := &file_routes_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -858,7 +917,7 @@ func (x *NodeInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeInfo.ProtoReflect.Descriptor instead. func (*NodeInfo) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{12} + return file_routes_proto_rawDescGZIP(), []int{13} } func (x *NodeInfo) GetFields() map[string]*NodeField { @@ -901,7 +960,7 @@ type NodeListResponse struct { func (x *NodeListResponse) Reset() { *x = NodeListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[13] + mi := &file_routes_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -914,7 +973,7 @@ func (x *NodeListResponse) String() string { func (*NodeListResponse) ProtoMessage() {} func (x *NodeListResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[13] + mi := &file_routes_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -927,7 +986,7 @@ func (x *NodeListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeListResponse.ProtoReflect.Descriptor instead. func (*NodeListResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{13} + return file_routes_proto_rawDescGZIP(), []int{14} } func (x *NodeListResponse) GetNodes() []*NodeInfo { @@ -950,7 +1009,7 @@ type GetNodeList struct { func (x *GetNodeList) Reset() { *x = GetNodeList{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[14] + mi := &file_routes_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -963,7 +1022,7 @@ func (x *GetNodeList) String() string { func (*GetNodeList) ProtoMessage() {} func (x *GetNodeList) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[14] + mi := &file_routes_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -976,7 +1035,7 @@ func (x *GetNodeList) ProtoReflect() protoreflect.Message { // Deprecated: Use GetNodeList.ProtoReflect.Descriptor instead. func (*GetNodeList) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{14} + return file_routes_proto_rawDescGZIP(), []int{15} } func (x *GetNodeList) GetType() GetNodeList_ListType { @@ -1005,7 +1064,7 @@ type NodeList struct { func (x *NodeList) Reset() { *x = NodeList{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[15] + mi := &file_routes_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1018,7 +1077,7 @@ func (x *NodeList) String() string { func (*NodeList) ProtoMessage() {} func (x *NodeList) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[15] + mi := &file_routes_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1031,7 +1090,7 @@ func (x *NodeList) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeList.ProtoReflect.Descriptor instead. func (*NodeList) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{15} + return file_routes_proto_rawDescGZIP(), []int{16} } func (x *NodeList) GetOutput() []string { @@ -1047,14 +1106,15 @@ type GetProfileList struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ShowAll bool `protobuf:"varint,1,opt,name=ShowAll,proto3" json:"ShowAll,omitempty"` - Profiles []string `protobuf:"bytes,2,rep,name=Profiles,proto3" json:"Profiles,omitempty"` + ShowAll bool `protobuf:"varint,1,opt,name=ShowAll,proto3" json:"ShowAll,omitempty"` + ShowFullAll bool `protobuf:"varint,2,opt,name=ShowFullAll,proto3" json:"ShowFullAll,omitempty"` + Profiles []string `protobuf:"bytes,3,rep,name=Profiles,proto3" json:"Profiles,omitempty"` } func (x *GetProfileList) Reset() { *x = GetProfileList{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[16] + mi := &file_routes_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1067,7 +1127,7 @@ func (x *GetProfileList) String() string { func (*GetProfileList) ProtoMessage() {} func (x *GetProfileList) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[16] + mi := &file_routes_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1080,7 +1140,7 @@ func (x *GetProfileList) ProtoReflect() protoreflect.Message { // Deprecated: Use GetProfileList.ProtoReflect.Descriptor instead. func (*GetProfileList) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{16} + return file_routes_proto_rawDescGZIP(), []int{17} } func (x *GetProfileList) GetShowAll() bool { @@ -1090,6 +1150,13 @@ func (x *GetProfileList) GetShowAll() bool { return false } +func (x *GetProfileList) GetShowFullAll() bool { + if x != nil { + return x.ShowFullAll + } + return false +} + func (x *GetProfileList) GetProfiles() []string { if x != nil { return x.Profiles @@ -1109,7 +1176,7 @@ type ProfileList struct { func (x *ProfileList) Reset() { *x = ProfileList{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[17] + mi := &file_routes_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1122,7 +1189,7 @@ func (x *ProfileList) String() string { func (*ProfileList) ProtoMessage() {} func (x *ProfileList) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[17] + mi := &file_routes_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1135,7 +1202,7 @@ func (x *ProfileList) ProtoReflect() protoreflect.Message { // Deprecated: Use ProfileList.ProtoReflect.Descriptor instead. func (*ProfileList) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{17} + return file_routes_proto_rawDescGZIP(), []int{18} } func (x *ProfileList) GetOutput() []string { @@ -1162,7 +1229,7 @@ type NodeAddParameter struct { func (x *NodeAddParameter) Reset() { *x = NodeAddParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[18] + mi := &file_routes_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1175,7 +1242,7 @@ func (x *NodeAddParameter) String() string { func (*NodeAddParameter) ProtoMessage() {} func (x *NodeAddParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[18] + mi := &file_routes_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1188,7 +1255,7 @@ func (x *NodeAddParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeAddParameter.ProtoReflect.Descriptor instead. func (*NodeAddParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{18} + return file_routes_proto_rawDescGZIP(), []int{19} } func (x *NodeAddParameter) GetNodeConfYaml() string { @@ -1233,7 +1300,7 @@ type NodeYaml struct { func (x *NodeYaml) Reset() { *x = NodeYaml{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[19] + mi := &file_routes_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1246,7 +1313,7 @@ func (x *NodeYaml) String() string { func (*NodeYaml) ProtoMessage() {} func (x *NodeYaml) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[19] + mi := &file_routes_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1259,7 +1326,7 @@ func (x *NodeYaml) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeYaml.ProtoReflect.Descriptor instead. func (*NodeYaml) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{19} + return file_routes_proto_rawDescGZIP(), []int{20} } func (x *NodeYaml) GetNodeConfMapYaml() string { @@ -1293,7 +1360,7 @@ type NodeDeleteParameter struct { func (x *NodeDeleteParameter) Reset() { *x = NodeDeleteParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[20] + mi := &file_routes_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1306,7 +1373,7 @@ func (x *NodeDeleteParameter) String() string { func (*NodeDeleteParameter) ProtoMessage() {} func (x *NodeDeleteParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[20] + mi := &file_routes_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1319,7 +1386,7 @@ func (x *NodeDeleteParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeDeleteParameter.ProtoReflect.Descriptor instead. func (*NodeDeleteParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{20} + return file_routes_proto_rawDescGZIP(), []int{21} } func (x *NodeDeleteParameter) GetForce() bool { @@ -1350,18 +1417,20 @@ type NodeSetParameter struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - NodeConfYaml string `protobuf:"bytes,1,opt,name=nodeConfYaml,proto3" json:"nodeConfYaml,omitempty"` - Container string `protobuf:"bytes,2,opt,name=container,proto3" json:"container,omitempty"` - NetdevDelete string `protobuf:"bytes,14,opt,name=netdevDelete,proto3" json:"netdevDelete,omitempty"` - AllNodes bool `protobuf:"varint,27,opt,name=allNodes,proto3" json:"allNodes,omitempty"` - Force bool `protobuf:"varint,31,opt,name=force,proto3" json:"force,omitempty"` - NodeNames []string `protobuf:"bytes,39,rep,name=nodeNames,proto3" json:"nodeNames,omitempty"` + NodeConfYaml string `protobuf:"bytes,1,opt,name=nodeConfYaml,proto3" json:"nodeConfYaml,omitempty"` + NetdevDelete string `protobuf:"bytes,2,opt,name=netdevDelete,proto3" json:"netdevDelete,omitempty"` + DiskDelete string `protobuf:"bytes,3,opt,name=diskDelete,proto3" json:"diskDelete,omitempty"` + PartitionDelete string `protobuf:"bytes,4,opt,name=partitionDelete,proto3" json:"partitionDelete,omitempty"` + FilesystemDelete string `protobuf:"bytes,5,opt,name=filesystemDelete,proto3" json:"filesystemDelete,omitempty"` + AllNodes bool `protobuf:"varint,6,opt,name=allNodes,proto3" json:"allNodes,omitempty"` + Force bool `protobuf:"varint,7,opt,name=force,proto3" json:"force,omitempty"` + NodeNames []string `protobuf:"bytes,8,rep,name=nodeNames,proto3" json:"nodeNames,omitempty"` } func (x *NodeSetParameter) Reset() { *x = NodeSetParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[21] + mi := &file_routes_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1374,7 +1443,7 @@ func (x *NodeSetParameter) String() string { func (*NodeSetParameter) ProtoMessage() {} func (x *NodeSetParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[21] + mi := &file_routes_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1387,7 +1456,7 @@ func (x *NodeSetParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeSetParameter.ProtoReflect.Descriptor instead. func (*NodeSetParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{21} + return file_routes_proto_rawDescGZIP(), []int{22} } func (x *NodeSetParameter) GetNodeConfYaml() string { @@ -1397,16 +1466,30 @@ func (x *NodeSetParameter) GetNodeConfYaml() string { return "" } -func (x *NodeSetParameter) GetContainer() string { +func (x *NodeSetParameter) GetNetdevDelete() string { if x != nil { - return x.Container + return x.NetdevDelete } return "" } -func (x *NodeSetParameter) GetNetdevDelete() string { +func (x *NodeSetParameter) GetDiskDelete() string { if x != nil { - return x.NetdevDelete + return x.DiskDelete + } + return "" +} + +func (x *NodeSetParameter) GetPartitionDelete() string { + if x != nil { + return x.PartitionDelete + } + return "" +} + +func (x *NodeSetParameter) GetFilesystemDelete() string { + if x != nil { + return x.FilesystemDelete } return "" } @@ -1432,6 +1515,111 @@ func (x *NodeSetParameter) GetNodeNames() []string { return nil } +// ProfileSetParameter contains all fields for updating aspects of profiles managed. Basically a copy of NodeSetParameters +// by Warewulf. +type ProfileSetParameter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeConfYaml string `protobuf:"bytes,1,opt,name=nodeConfYaml,proto3" json:"nodeConfYaml,omitempty"` + NetdevDelete string `protobuf:"bytes,2,opt,name=netdevDelete,proto3" json:"netdevDelete,omitempty"` + DiskDelete string `protobuf:"bytes,3,opt,name=diskDelete,proto3" json:"diskDelete,omitempty"` + PartitionDelete string `protobuf:"bytes,4,opt,name=partitionDelete,proto3" json:"partitionDelete,omitempty"` + FilesystemDelete string `protobuf:"bytes,5,opt,name=filesystemDelete,proto3" json:"filesystemDelete,omitempty"` + AllProfiles bool `protobuf:"varint,6,opt,name=allProfiles,proto3" json:"allProfiles,omitempty"` + Force bool `protobuf:"varint,7,opt,name=force,proto3" json:"force,omitempty"` + ProfileNames []string `protobuf:"bytes,8,rep,name=profileNames,proto3" json:"profileNames,omitempty"` +} + +func (x *ProfileSetParameter) Reset() { + *x = ProfileSetParameter{} + if protoimpl.UnsafeEnabled { + mi := &file_routes_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProfileSetParameter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProfileSetParameter) ProtoMessage() {} + +func (x *ProfileSetParameter) ProtoReflect() protoreflect.Message { + mi := &file_routes_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProfileSetParameter.ProtoReflect.Descriptor instead. +func (*ProfileSetParameter) Descriptor() ([]byte, []int) { + return file_routes_proto_rawDescGZIP(), []int{23} +} + +func (x *ProfileSetParameter) GetNodeConfYaml() string { + if x != nil { + return x.NodeConfYaml + } + return "" +} + +func (x *ProfileSetParameter) GetNetdevDelete() string { + if x != nil { + return x.NetdevDelete + } + return "" +} + +func (x *ProfileSetParameter) GetDiskDelete() string { + if x != nil { + return x.DiskDelete + } + return "" +} + +func (x *ProfileSetParameter) GetPartitionDelete() string { + if x != nil { + return x.PartitionDelete + } + return "" +} + +func (x *ProfileSetParameter) GetFilesystemDelete() string { + if x != nil { + return x.FilesystemDelete + } + return "" +} + +func (x *ProfileSetParameter) GetAllProfiles() bool { + if x != nil { + return x.AllProfiles + } + return false +} + +func (x *ProfileSetParameter) GetForce() bool { + if x != nil { + return x.Force + } + return false +} + +func (x *ProfileSetParameter) GetProfileNames() []string { + if x != nil { + return x.ProfileNames + } + return nil +} + // NodeStatus contains information about the imaging status per node. type NodeStatus struct { state protoimpl.MessageState @@ -1448,7 +1636,7 @@ type NodeStatus struct { func (x *NodeStatus) Reset() { *x = NodeStatus{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[22] + mi := &file_routes_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1461,7 +1649,7 @@ func (x *NodeStatus) String() string { func (*NodeStatus) ProtoMessage() {} func (x *NodeStatus) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[22] + mi := &file_routes_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1474,7 +1662,7 @@ func (x *NodeStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatus.ProtoReflect.Descriptor instead. func (*NodeStatus) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{22} + return file_routes_proto_rawDescGZIP(), []int{24} } func (x *NodeStatus) GetNodeName() string { @@ -1524,7 +1712,7 @@ type NodeStatusResponse struct { func (x *NodeStatusResponse) Reset() { *x = NodeStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[23] + mi := &file_routes_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1537,7 +1725,7 @@ func (x *NodeStatusResponse) String() string { func (*NodeStatusResponse) ProtoMessage() {} func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[23] + mi := &file_routes_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1550,7 +1738,7 @@ func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatusResponse.ProtoReflect.Descriptor instead. func (*NodeStatusResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{23} + return file_routes_proto_rawDescGZIP(), []int{25} } func (x *NodeStatusResponse) GetNodeStatus() []*NodeStatus { @@ -1574,7 +1762,7 @@ type VersionResponse struct { func (x *VersionResponse) Reset() { *x = VersionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[24] + mi := &file_routes_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1587,7 +1775,7 @@ func (x *VersionResponse) String() string { func (*VersionResponse) ProtoMessage() {} func (x *VersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[24] + mi := &file_routes_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1600,7 +1788,7 @@ func (x *VersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead. func (*VersionResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{24} + return file_routes_proto_rawDescGZIP(), []int{26} } func (x *VersionResponse) GetApiPrefix() string { @@ -1636,7 +1824,7 @@ type CanWriteConfig struct { func (x *CanWriteConfig) Reset() { *x = CanWriteConfig{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[25] + mi := &file_routes_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1649,7 +1837,7 @@ func (x *CanWriteConfig) String() string { func (*CanWriteConfig) ProtoMessage() {} func (x *CanWriteConfig) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[25] + mi := &file_routes_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1662,7 +1850,7 @@ func (x *CanWriteConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use CanWriteConfig.ProtoReflect.Descriptor instead. func (*CanWriteConfig) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{25} + return file_routes_proto_rawDescGZIP(), []int{27} } func (x *CanWriteConfig) GetCanWriteConfig() bool { @@ -1695,258 +1883,300 @@ var file_routes_proto_rawDesc = []byte{ 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, - 0xc0, 0x01, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x55, 0x73, - 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x55, 0x73, - 0x65, 0x72, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6e, 0x6f, 0x64, - 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6b, - 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6d, - 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x50, 0x0a, 0x15, 0x43, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x3e, 0x0a, 0x16, - 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x7f, 0x0a, 0x15, - 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x52, 0x6f, 0x6f, - 0x74, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x52, 0x6f, 0x6f, 0x74, 0x66, - 0x73, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x4b, 0x65, 0x72, 0x6e, 0x65, - 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, - 0x4b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x42, 0x0a, - 0x1a, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x79, 0x6e, 0x63, 0x55, 0x73, - 0x65, 0x72, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0d, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, - 0x65, 0x22, 0x29, 0x0a, 0x09, 0x4e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1c, - 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x4f, 0x0a, 0x09, - 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x22, 0x88, 0x02, - 0x0a, 0x06, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x12, 0x31, 0x0a, 0x05, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, - 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x54, - 0x61, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x2e, 0x54, 0x61, 0x67, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x4d, 0x0a, 0x0a, 0x46, - 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4c, 0x0a, 0x09, 0x54, 0x61, - 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9b, 0x04, 0x0a, 0x08, 0x4e, 0x6f, 0x64, - 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x36, 0x0a, 0x06, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, - 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x39, 0x0a, - 0x07, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x73, 0x18, 0x17, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x76, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x70, 0x79, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x14, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc0, 0x01, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x55, 0x73, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x55, 0x73, 0x65, 0x72, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x43, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, + 0x0a, 0x0d, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, + 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x44, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, + 0x7a, 0x65, 0x22, 0x50, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x73, 0x22, 0x3e, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x24, + 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x7f, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x52, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x52, 0x6f, 0x6f, 0x74, 0x66, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x6f, 0x64, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, + 0x24, 0x0a, 0x0d, 0x4b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x4b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x42, 0x0a, 0x1a, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x53, 0x79, 0x6e, 0x63, 0x55, 0x73, 0x65, 0x72, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x29, 0x0a, 0x09, 0x4e, 0x6f, 0x64, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x22, 0x4f, 0x0a, 0x09, 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x70, 0x72, 0x69, 0x6e, 0x74, 0x22, 0x88, 0x02, 0x0a, 0x06, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, + 0x12, 0x31, 0x0a, 0x05, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x44, 0x65, + 0x76, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x54, 0x61, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x74, + 0x44, 0x65, 0x76, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x54, + 0x61, 0x67, 0x73, 0x1a, 0x4d, 0x0a, 0x0a, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, + 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x1a, 0x4c, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x9b, 0x04, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x36, 0x0a, + 0x06, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, + 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, + 0x6f, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x39, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x73, + 0x18, 0x17, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x44, 0x65, + 0x76, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x73, + 0x12, 0x30, 0x0a, 0x04, 0x54, 0x61, 0x67, 0x73, 0x18, 0x18, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, - 0x66, 0x6f, 0x2e, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x07, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x73, 0x12, 0x30, 0x0a, 0x04, 0x54, 0x61, 0x67, 0x73, - 0x18, 0x18, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x54, 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x04, 0x4b, 0x65, - 0x79, 0x73, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4b, 0x65, 0x79, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4b, 0x65, 0x79, 0x73, 0x1a, 0x4e, 0x0a, 0x0b, - 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x77, - 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, - 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4c, 0x0a, 0x0c, - 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4c, 0x0a, 0x09, 0x54, 0x61, - 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4c, 0x0a, 0x09, 0x4b, 0x65, 0x79, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3c, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6e, 0x6f, - 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x77, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x6e, - 0x6f, 0x64, 0x65, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, - 0x4c, 0x69, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x6f, 0x64, 0x65, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x40, - 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x69, - 0x6d, 0x70, 0x6c, 0x65, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x70, 0x6d, 0x69, 0x10, 0x01, - 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x10, 0x02, 0x12, 0x08, 0x0a, - 0x04, 0x4c, 0x6f, 0x6e, 0x67, 0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x6c, 0x6c, 0x10, 0x04, - 0x22, 0x22, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, - 0x74, 0x70, 0x75, 0x74, 0x22, 0x46, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x6c, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x6c, 0x6c, - 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x22, 0x25, 0x0a, 0x0b, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x22, 0x7e, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, - 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, - 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x66, - 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x59, 0x61, 0x6d, 0x6c, 0x12, - 0x28, 0x0a, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, - 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, - 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, - 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x5d, 0x0a, - 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, - 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, - 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xc8, 0x01, 0x0a, - 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, - 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, - 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, - 0x64, 0x65, 0x73, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, - 0x64, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x1f, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x27, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, - 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x69, 0x70, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, - 0x61, 0x64, 0x64, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, - 0x22, 0x4a, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x0f, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x1c, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1e, 0x0a, - 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, - 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x0e, 0x43, 0x61, 0x6e, 0x57, 0x72, - 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x61, 0x6e, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0e, 0x63, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x32, 0xa6, 0x08, 0x0a, 0x05, 0x57, 0x57, 0x41, 0x70, 0x69, 0x12, 0x73, 0x0a, 0x0e, 0x43, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x3a, 0x01, 0x2a, - 0x12, 0x64, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, + 0x66, 0x6f, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x54, 0x61, + 0x67, 0x73, 0x12, 0x30, 0x0a, 0x04, 0x4b, 0x65, 0x79, 0x73, 0x18, 0x19, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, + 0x4b, 0x65, 0x79, 0x73, 0x1a, 0x4e, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4c, 0x0a, 0x0c, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x4e, 0x65, 0x74, 0x44, 0x65, 0x76, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x1a, 0x4c, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x1a, 0x4c, 0x0a, 0x09, 0x4b, 0x65, 0x79, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x46, 0x69, + 0x65, 0x6c, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3c, + 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x12, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0xa6, 0x01, 0x0a, + 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x77, 0x77, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, + 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x4d, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x10, 0x00, 0x12, 0x08, + 0x0a, 0x04, 0x49, 0x70, 0x6d, 0x69, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x6f, 0x6e, 0x67, 0x10, 0x03, 0x12, + 0x07, 0x0a, 0x03, 0x41, 0x6c, 0x6c, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x75, 0x6c, 0x6c, + 0x41, 0x6c, 0x6c, 0x10, 0x05, 0x22, 0x22, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x68, 0x0a, 0x0e, 0x47, 0x65, 0x74, + 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x53, + 0x68, 0x6f, 0x77, 0x41, 0x6c, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x53, 0x68, + 0x6f, 0x77, 0x41, 0x6c, 0x6c, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x68, 0x6f, 0x77, 0x46, 0x75, 0x6c, + 0x6c, 0x41, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x53, 0x68, 0x6f, 0x77, + 0x46, 0x75, 0x6c, 0x6c, 0x41, 0x6c, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x22, 0x25, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x7e, 0x0a, 0x10, 0x4e, 0x6f, + 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, + 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, + 0x6d, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, + 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x48, 0x0a, 0x08, 0x4e, 0x6f, + 0x64, 0x65, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x22, 0x5d, 0x0a, 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, + 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, + 0x61, 0x73, 0x68, 0x22, 0xa0, 0x02, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x22, 0x0a, 0x0c, + 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, 0x73, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x69, 0x73, 0x6b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, + 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, + 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xaf, 0x02, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, + 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, + 0x6d, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, 0x73, 0x6b, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x69, 0x73, 0x6b, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x2a, 0x0a, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x61, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0b, 0x61, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x14, + 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x69, 0x70, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, + 0x70, 0x61, 0x64, 0x64, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, + 0x6e, 0x22, 0x4a, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x77, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, + 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1e, + 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, + 0x0a, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, + 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x0e, 0x43, 0x61, 0x6e, 0x57, + 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x61, + 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0e, 0x63, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x32, 0x8f, 0x09, 0x0a, 0x05, 0x57, 0x57, 0x41, 0x70, 0x69, 0x12, 0x73, 0x0a, 0x0e, + 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, + 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x3a, 0x01, + 0x2a, 0x12, 0x64, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x2a, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x67, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x70, 0x79, 0x12, 0x20, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x70, + 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x63, 0x6f, 0x70, 0x79, 0x3a, 0x01, 0x2a, + 0x12, 0x70, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, + 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x3a, + 0x01, 0x2a, 0x12, 0x5f, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x77, 0x77, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x12, 0x6d, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x53, 0x68, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, + 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x68, + 0x6f, 0x77, 0x12, 0x56, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x12, 0x1a, 0x2e, + 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x22, 0x08, 0x2f, + 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x55, 0x0a, 0x0a, 0x4e, 0x6f, + 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x2a, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x70, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x22, 0x2e, 0x77, 0x77, 0x61, 0x70, - 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x5f, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x1f, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, 0x76, 0x31, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x6d, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x77, 0x77, 0x61, - 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, - 0x68, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x77, - 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x53, 0x68, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x56, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, - 0x41, 0x64, 0x64, 0x12, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, - 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, - 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x0d, 0x22, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x3a, 0x01, 0x2a, - 0x12, 0x55, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1d, - 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x2a, 0x08, 0x2f, - 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x4d, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, - 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, - 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, - 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x59, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, - 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x10, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x65, 0x74, 0x3a, 0x01, - 0x2a, 0x12, 0x57, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x1c, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, - 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, - 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x4e, 0x0a, 0x07, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, - 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, - 0x12, 0x08, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x29, 0x5a, 0x27, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, 0x77, 0x77, 0x61, 0x70, 0x69, 0x76, 0x31, 0x3b, 0x77, 0x77, - 0x61, 0x70, 0x69, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x2a, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, + 0x65, 0x12, 0x4d, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, + 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, + 0x65, 0x73, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, + 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, + 0x12, 0x59, 0x0a, 0x07, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x12, 0x1a, 0x2e, 0x77, 0x77, + 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x1a, 0x1a, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x22, 0x0b, 0x2f, 0x76, 0x31, + 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x65, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, 0x0a, 0x4e, + 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x13, 0x2e, 0x77, 0x77, 0x61, 0x70, + 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x1a, 0x1c, + 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x4e, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, + 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x29, 0x5a, 0x27, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2f, + 0x77, 0x77, 0x61, 0x70, 0x69, 0x76, 0x31, 0x3b, 0x77, 0x77, 0x61, 0x70, 0x69, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1962,84 +2192,88 @@ func file_routes_proto_rawDescGZIP() []byte { } var file_routes_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 34) var file_routes_proto_goTypes = []interface{}{ (GetNodeList_ListType)(0), // 0: wwapi.v1.GetNodeList.ListType (*NodeDBHash)(nil), // 1: wwapi.v1.NodeDBHash (*ContainerBuildParameter)(nil), // 2: wwapi.v1.ContainerBuildParameter (*ContainerDeleteParameter)(nil), // 3: wwapi.v1.ContainerDeleteParameter - (*ContainerImportParameter)(nil), // 4: wwapi.v1.ContainerImportParameter - (*ContainerInfo)(nil), // 5: wwapi.v1.ContainerInfo - (*ContainerListResponse)(nil), // 6: wwapi.v1.ContainerListResponse - (*ContainerShowParameter)(nil), // 7: wwapi.v1.ContainerShowParameter - (*ContainerShowResponse)(nil), // 8: wwapi.v1.ContainerShowResponse - (*ContainerSyncUserParameter)(nil), // 9: wwapi.v1.ContainerSyncUserParameter - (*NodeNames)(nil), // 10: wwapi.v1.NodeNames - (*NodeField)(nil), // 11: wwapi.v1.NodeField - (*NetDev)(nil), // 12: wwapi.v1.NetDev - (*NodeInfo)(nil), // 13: wwapi.v1.NodeInfo - (*NodeListResponse)(nil), // 14: wwapi.v1.NodeListResponse - (*GetNodeList)(nil), // 15: wwapi.v1.GetNodeList - (*NodeList)(nil), // 16: wwapi.v1.NodeList - (*GetProfileList)(nil), // 17: wwapi.v1.GetProfileList - (*ProfileList)(nil), // 18: wwapi.v1.ProfileList - (*NodeAddParameter)(nil), // 19: wwapi.v1.NodeAddParameter - (*NodeYaml)(nil), // 20: wwapi.v1.NodeYaml - (*NodeDeleteParameter)(nil), // 21: wwapi.v1.NodeDeleteParameter - (*NodeSetParameter)(nil), // 22: wwapi.v1.NodeSetParameter - (*NodeStatus)(nil), // 23: wwapi.v1.NodeStatus - (*NodeStatusResponse)(nil), // 24: wwapi.v1.NodeStatusResponse - (*VersionResponse)(nil), // 25: wwapi.v1.VersionResponse - (*CanWriteConfig)(nil), // 26: wwapi.v1.CanWriteConfig - nil, // 27: wwapi.v1.NetDev.FieldEntry - nil, // 28: wwapi.v1.NetDev.TagsEntry - nil, // 29: wwapi.v1.NodeInfo.FieldsEntry - nil, // 30: wwapi.v1.NodeInfo.NetDevsEntry - nil, // 31: wwapi.v1.NodeInfo.TagsEntry - nil, // 32: wwapi.v1.NodeInfo.KeysEntry - (*empty.Empty)(nil), // 33: google.protobuf.Empty + (*ContainerCopyParameter)(nil), // 4: wwapi.v1.ContainerCopyParameter + (*ContainerImportParameter)(nil), // 5: wwapi.v1.ContainerImportParameter + (*ContainerInfo)(nil), // 6: wwapi.v1.ContainerInfo + (*ContainerListResponse)(nil), // 7: wwapi.v1.ContainerListResponse + (*ContainerShowParameter)(nil), // 8: wwapi.v1.ContainerShowParameter + (*ContainerShowResponse)(nil), // 9: wwapi.v1.ContainerShowResponse + (*ContainerSyncUserParameter)(nil), // 10: wwapi.v1.ContainerSyncUserParameter + (*NodeNames)(nil), // 11: wwapi.v1.NodeNames + (*NodeField)(nil), // 12: wwapi.v1.NodeField + (*NetDev)(nil), // 13: wwapi.v1.NetDev + (*NodeInfo)(nil), // 14: wwapi.v1.NodeInfo + (*NodeListResponse)(nil), // 15: wwapi.v1.NodeListResponse + (*GetNodeList)(nil), // 16: wwapi.v1.GetNodeList + (*NodeList)(nil), // 17: wwapi.v1.NodeList + (*GetProfileList)(nil), // 18: wwapi.v1.GetProfileList + (*ProfileList)(nil), // 19: wwapi.v1.ProfileList + (*NodeAddParameter)(nil), // 20: wwapi.v1.NodeAddParameter + (*NodeYaml)(nil), // 21: wwapi.v1.NodeYaml + (*NodeDeleteParameter)(nil), // 22: wwapi.v1.NodeDeleteParameter + (*NodeSetParameter)(nil), // 23: wwapi.v1.NodeSetParameter + (*ProfileSetParameter)(nil), // 24: wwapi.v1.ProfileSetParameter + (*NodeStatus)(nil), // 25: wwapi.v1.NodeStatus + (*NodeStatusResponse)(nil), // 26: wwapi.v1.NodeStatusResponse + (*VersionResponse)(nil), // 27: wwapi.v1.VersionResponse + (*CanWriteConfig)(nil), // 28: wwapi.v1.CanWriteConfig + nil, // 29: wwapi.v1.NetDev.FieldEntry + nil, // 30: wwapi.v1.NetDev.TagsEntry + nil, // 31: wwapi.v1.NodeInfo.FieldsEntry + nil, // 32: wwapi.v1.NodeInfo.NetDevsEntry + nil, // 33: wwapi.v1.NodeInfo.TagsEntry + nil, // 34: wwapi.v1.NodeInfo.KeysEntry + (*emptypb.Empty)(nil), // 35: google.protobuf.Empty } var file_routes_proto_depIdxs = []int32{ - 5, // 0: wwapi.v1.ContainerListResponse.containers:type_name -> wwapi.v1.ContainerInfo - 27, // 1: wwapi.v1.NetDev.Field:type_name -> wwapi.v1.NetDev.FieldEntry - 28, // 2: wwapi.v1.NetDev.Tags:type_name -> wwapi.v1.NetDev.TagsEntry - 29, // 3: wwapi.v1.NodeInfo.Fields:type_name -> wwapi.v1.NodeInfo.FieldsEntry - 30, // 4: wwapi.v1.NodeInfo.NetDevs:type_name -> wwapi.v1.NodeInfo.NetDevsEntry - 31, // 5: wwapi.v1.NodeInfo.Tags:type_name -> wwapi.v1.NodeInfo.TagsEntry - 32, // 6: wwapi.v1.NodeInfo.Keys:type_name -> wwapi.v1.NodeInfo.KeysEntry - 13, // 7: wwapi.v1.NodeListResponse.nodes:type_name -> wwapi.v1.NodeInfo + 6, // 0: wwapi.v1.ContainerListResponse.containers:type_name -> wwapi.v1.ContainerInfo + 29, // 1: wwapi.v1.NetDev.Field:type_name -> wwapi.v1.NetDev.FieldEntry + 30, // 2: wwapi.v1.NetDev.Tags:type_name -> wwapi.v1.NetDev.TagsEntry + 31, // 3: wwapi.v1.NodeInfo.Fields:type_name -> wwapi.v1.NodeInfo.FieldsEntry + 32, // 4: wwapi.v1.NodeInfo.NetDevs:type_name -> wwapi.v1.NodeInfo.NetDevsEntry + 33, // 5: wwapi.v1.NodeInfo.Tags:type_name -> wwapi.v1.NodeInfo.TagsEntry + 34, // 6: wwapi.v1.NodeInfo.Keys:type_name -> wwapi.v1.NodeInfo.KeysEntry + 14, // 7: wwapi.v1.NodeListResponse.nodes:type_name -> wwapi.v1.NodeInfo 0, // 8: wwapi.v1.GetNodeList.type:type_name -> wwapi.v1.GetNodeList.ListType - 23, // 9: wwapi.v1.NodeStatusResponse.nodeStatus:type_name -> wwapi.v1.NodeStatus - 11, // 10: wwapi.v1.NetDev.FieldEntry.value:type_name -> wwapi.v1.NodeField - 11, // 11: wwapi.v1.NetDev.TagsEntry.value:type_name -> wwapi.v1.NodeField - 11, // 12: wwapi.v1.NodeInfo.FieldsEntry.value:type_name -> wwapi.v1.NodeField - 12, // 13: wwapi.v1.NodeInfo.NetDevsEntry.value:type_name -> wwapi.v1.NetDev - 11, // 14: wwapi.v1.NodeInfo.TagsEntry.value:type_name -> wwapi.v1.NodeField - 11, // 15: wwapi.v1.NodeInfo.KeysEntry.value:type_name -> wwapi.v1.NodeField + 25, // 9: wwapi.v1.NodeStatusResponse.nodeStatus:type_name -> wwapi.v1.NodeStatus + 12, // 10: wwapi.v1.NetDev.FieldEntry.value:type_name -> wwapi.v1.NodeField + 12, // 11: wwapi.v1.NetDev.TagsEntry.value:type_name -> wwapi.v1.NodeField + 12, // 12: wwapi.v1.NodeInfo.FieldsEntry.value:type_name -> wwapi.v1.NodeField + 13, // 13: wwapi.v1.NodeInfo.NetDevsEntry.value:type_name -> wwapi.v1.NetDev + 12, // 14: wwapi.v1.NodeInfo.TagsEntry.value:type_name -> wwapi.v1.NodeField + 12, // 15: wwapi.v1.NodeInfo.KeysEntry.value:type_name -> wwapi.v1.NodeField 2, // 16: wwapi.v1.WWApi.ContainerBuild:input_type -> wwapi.v1.ContainerBuildParameter 3, // 17: wwapi.v1.WWApi.ContainerDelete:input_type -> wwapi.v1.ContainerDeleteParameter - 4, // 18: wwapi.v1.WWApi.ContainerImport:input_type -> wwapi.v1.ContainerImportParameter - 33, // 19: wwapi.v1.WWApi.ContainerList:input_type -> google.protobuf.Empty - 7, // 20: wwapi.v1.WWApi.ContainerShow:input_type -> wwapi.v1.ContainerShowParameter - 19, // 21: wwapi.v1.WWApi.NodeAdd:input_type -> wwapi.v1.NodeAddParameter - 21, // 22: wwapi.v1.WWApi.NodeDelete:input_type -> wwapi.v1.NodeDeleteParameter - 10, // 23: wwapi.v1.WWApi.NodeList:input_type -> wwapi.v1.NodeNames - 22, // 24: wwapi.v1.WWApi.NodeSet:input_type -> wwapi.v1.NodeSetParameter - 10, // 25: wwapi.v1.WWApi.NodeStatus:input_type -> wwapi.v1.NodeNames - 33, // 26: wwapi.v1.WWApi.Version:input_type -> google.protobuf.Empty - 6, // 27: wwapi.v1.WWApi.ContainerBuild:output_type -> wwapi.v1.ContainerListResponse - 33, // 28: wwapi.v1.WWApi.ContainerDelete:output_type -> google.protobuf.Empty - 6, // 29: wwapi.v1.WWApi.ContainerImport:output_type -> wwapi.v1.ContainerListResponse - 6, // 30: wwapi.v1.WWApi.ContainerList:output_type -> wwapi.v1.ContainerListResponse - 8, // 31: wwapi.v1.WWApi.ContainerShow:output_type -> wwapi.v1.ContainerShowResponse - 14, // 32: wwapi.v1.WWApi.NodeAdd:output_type -> wwapi.v1.NodeListResponse - 33, // 33: wwapi.v1.WWApi.NodeDelete:output_type -> google.protobuf.Empty - 14, // 34: wwapi.v1.WWApi.NodeList:output_type -> wwapi.v1.NodeListResponse - 14, // 35: wwapi.v1.WWApi.NodeSet:output_type -> wwapi.v1.NodeListResponse - 24, // 36: wwapi.v1.WWApi.NodeStatus:output_type -> wwapi.v1.NodeStatusResponse - 25, // 37: wwapi.v1.WWApi.Version:output_type -> wwapi.v1.VersionResponse - 27, // [27:38] is the sub-list for method output_type - 16, // [16:27] is the sub-list for method input_type + 4, // 18: wwapi.v1.WWApi.ContainerCopy:input_type -> wwapi.v1.ContainerCopyParameter + 5, // 19: wwapi.v1.WWApi.ContainerImport:input_type -> wwapi.v1.ContainerImportParameter + 35, // 20: wwapi.v1.WWApi.ContainerList:input_type -> google.protobuf.Empty + 8, // 21: wwapi.v1.WWApi.ContainerShow:input_type -> wwapi.v1.ContainerShowParameter + 20, // 22: wwapi.v1.WWApi.NodeAdd:input_type -> wwapi.v1.NodeAddParameter + 22, // 23: wwapi.v1.WWApi.NodeDelete:input_type -> wwapi.v1.NodeDeleteParameter + 11, // 24: wwapi.v1.WWApi.NodeList:input_type -> wwapi.v1.NodeNames + 23, // 25: wwapi.v1.WWApi.NodeSet:input_type -> wwapi.v1.NodeSetParameter + 11, // 26: wwapi.v1.WWApi.NodeStatus:input_type -> wwapi.v1.NodeNames + 35, // 27: wwapi.v1.WWApi.Version:input_type -> google.protobuf.Empty + 7, // 28: wwapi.v1.WWApi.ContainerBuild:output_type -> wwapi.v1.ContainerListResponse + 35, // 29: wwapi.v1.WWApi.ContainerDelete:output_type -> google.protobuf.Empty + 35, // 30: wwapi.v1.WWApi.ContainerCopy:output_type -> google.protobuf.Empty + 7, // 31: wwapi.v1.WWApi.ContainerImport:output_type -> wwapi.v1.ContainerListResponse + 7, // 32: wwapi.v1.WWApi.ContainerList:output_type -> wwapi.v1.ContainerListResponse + 9, // 33: wwapi.v1.WWApi.ContainerShow:output_type -> wwapi.v1.ContainerShowResponse + 15, // 34: wwapi.v1.WWApi.NodeAdd:output_type -> wwapi.v1.NodeListResponse + 35, // 35: wwapi.v1.WWApi.NodeDelete:output_type -> google.protobuf.Empty + 15, // 36: wwapi.v1.WWApi.NodeList:output_type -> wwapi.v1.NodeListResponse + 15, // 37: wwapi.v1.WWApi.NodeSet:output_type -> wwapi.v1.NodeListResponse + 26, // 38: wwapi.v1.WWApi.NodeStatus:output_type -> wwapi.v1.NodeStatusResponse + 27, // 39: wwapi.v1.WWApi.Version:output_type -> wwapi.v1.VersionResponse + 28, // [28:40] is the sub-list for method output_type + 16, // [16:28] is the sub-list for method input_type 16, // [16:16] is the sub-list for extension type_name 16, // [16:16] is the sub-list for extension extendee 0, // [0:16] is the sub-list for field type_name @@ -2088,7 +2322,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ContainerImportParameter); i { + switch v := v.(*ContainerCopyParameter); i { case 0: return &v.state case 1: @@ -2100,7 +2334,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ContainerInfo); i { + switch v := v.(*ContainerImportParameter); i { case 0: return &v.state case 1: @@ -2112,7 +2346,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ContainerListResponse); i { + switch v := v.(*ContainerInfo); i { case 0: return &v.state case 1: @@ -2124,7 +2358,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ContainerShowParameter); i { + switch v := v.(*ContainerListResponse); i { case 0: return &v.state case 1: @@ -2136,7 +2370,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ContainerShowResponse); i { + switch v := v.(*ContainerShowParameter); i { case 0: return &v.state case 1: @@ -2148,7 +2382,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ContainerSyncUserParameter); i { + switch v := v.(*ContainerShowResponse); i { case 0: return &v.state case 1: @@ -2160,7 +2394,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeNames); i { + switch v := v.(*ContainerSyncUserParameter); i { case 0: return &v.state case 1: @@ -2172,7 +2406,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeField); i { + switch v := v.(*NodeNames); i { case 0: return &v.state case 1: @@ -2184,7 +2418,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NetDev); i { + switch v := v.(*NodeField); i { case 0: return &v.state case 1: @@ -2196,7 +2430,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeInfo); i { + switch v := v.(*NetDev); i { case 0: return &v.state case 1: @@ -2208,7 +2442,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeListResponse); i { + switch v := v.(*NodeInfo); i { case 0: return &v.state case 1: @@ -2220,7 +2454,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetNodeList); i { + switch v := v.(*NodeListResponse); i { case 0: return &v.state case 1: @@ -2232,7 +2466,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeList); i { + switch v := v.(*GetNodeList); i { case 0: return &v.state case 1: @@ -2244,7 +2478,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetProfileList); i { + switch v := v.(*NodeList); i { case 0: return &v.state case 1: @@ -2256,7 +2490,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProfileList); i { + switch v := v.(*GetProfileList); i { case 0: return &v.state case 1: @@ -2268,7 +2502,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeAddParameter); i { + switch v := v.(*ProfileList); i { case 0: return &v.state case 1: @@ -2280,7 +2514,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeYaml); i { + switch v := v.(*NodeAddParameter); i { case 0: return &v.state case 1: @@ -2292,7 +2526,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeDeleteParameter); i { + switch v := v.(*NodeYaml); i { case 0: return &v.state case 1: @@ -2304,7 +2538,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeSetParameter); i { + switch v := v.(*NodeDeleteParameter); i { case 0: return &v.state case 1: @@ -2316,7 +2550,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeStatus); i { + switch v := v.(*NodeSetParameter); i { case 0: return &v.state case 1: @@ -2328,7 +2562,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeStatusResponse); i { + switch v := v.(*ProfileSetParameter); i { case 0: return &v.state case 1: @@ -2340,7 +2574,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VersionResponse); i { + switch v := v.(*NodeStatus); i { case 0: return &v.state case 1: @@ -2352,6 +2586,30 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routes_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VersionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routes_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CanWriteConfig); i { case 0: return &v.state @@ -2370,7 +2628,7 @@ func file_routes_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_routes_proto_rawDesc, NumEnums: 1, - NumMessages: 32, + NumMessages: 34, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/pkg/api/routes/wwapiv1/routes.pb.gw.go b/internal/pkg/api/routes/wwapiv1/routes.pb.gw.go index b7591bea..fb71a9d9 100644 --- a/internal/pkg/api/routes/wwapiv1/routes.pb.gw.go +++ b/internal/pkg/api/routes/wwapiv1/routes.pb.gw.go @@ -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 diff --git a/internal/pkg/api/routes/wwapiv1/routes_grpc.pb.go b/internal/pkg/api/routes/wwapiv1/routes_grpc.pb.go index d31185f6..f361197a 100644 --- a/internal/pkg/api/routes/wwapiv1/routes_grpc.pb.go +++ b/internal/pkg/api/routes/wwapiv1/routes_grpc.pb.go @@ -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, diff --git a/internal/pkg/configure/hostfile.go b/internal/pkg/configure/hostfile.go index 27866775..89f63731 100644 --- a/internal/pkg/configure/hostfile.go +++ b/internal/pkg/configure/hostfile.go @@ -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) diff --git a/internal/pkg/container/build.go b/internal/pkg/container/build.go index cd3e45d9..e9392d82 100644 --- a/internal/pkg/container/build.go +++ b/internal/pkg/container/build.go @@ -37,7 +37,7 @@ func Build(name string, buildForce bool) error { } err := util.BuildFsImage( - "VNFS container " + name, + "VNFS container "+name, rootfsPath, imagePath, []string{"*"}, diff --git a/internal/pkg/container/syncuids.go b/internal/pkg/container/syncuids.go index 62cd8b27..a4bac584 100644 --- a/internal/pkg/container/syncuids.go +++ b/internal/pkg/container/syncuids.go @@ -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 } diff --git a/internal/pkg/container/syncuids_test.go b/internal/pkg/container/syncuids_test.go new file mode 100644 index 00000000..d4cf3f8d --- /dev/null +++ b/internal/pkg/container/syncuids_test.go @@ -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()) +} diff --git a/internal/pkg/container/util.go b/internal/pkg/container/util.go index 48211710..67ff9c48 100644 --- a/internal/pkg/container/util.go +++ b/internal/pkg/container/util.go @@ -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 */ diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index 612eacbd..d3d3bc1c 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -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) diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index ab22d3c8..b342f8f9 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -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 - } -} -*/ diff --git a/internal/pkg/node/flags.go b/internal/pkg/node/flags.go index e15b769c..2b5488d5 100644 --- a/internal/pkg/node/flags.go +++ b/internal/pkg/node/flags.go @@ -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) { diff --git a/internal/pkg/node/ignition.go b/internal/pkg/node/ignition.go new file mode 100644 index 00000000..3d8e756f --- /dev/null +++ b/internal/pkg/node/ignition.go @@ -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 +} diff --git a/internal/pkg/node/list.go b/internal/pkg/node/list.go new file mode 100644 index 00000000..4a728ece --- /dev/null +++ b/internal/pkg/node/list.go @@ -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 +} diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index c3ed61e2..4d768ddb 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -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 diff --git a/internal/pkg/node/methods_test.go b/internal/pkg/node/methods_test.go index d17f1529..56e33356 100644 --- a/internal/pkg/node/methods_test.go +++ b/internal/pkg/node/methods_test.go @@ -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") } }) diff --git a/internal/pkg/node/modifiers.go b/internal/pkg/node/modifiers.go index 361e2365..455ea514 100644 --- a/internal/pkg/node/modifiers.go +++ b/internal/pkg/node/modifiers.go @@ -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) { diff --git a/internal/pkg/node/node_test.go b/internal/pkg/node/node_test.go index 988b4144..13003a06 100644 --- a/internal/pkg/node/node_test.go +++ b/internal/pkg/node/node_test.go @@ -37,4 +37,45 @@ nodes: }, ) assert.NoError(t, err) -} \ No newline at end of file +} + + +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()) +} diff --git a/internal/pkg/node/transformer_test.go b/internal/pkg/node/transformer_test.go index 204b44a4..0f8746fe 100644 --- a/internal/pkg/node/transformer_test.go +++ b/internal/pkg/node/transformer_test.go @@ -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) diff --git a/internal/pkg/node/transformers.go b/internal/pkg/node/transformers.go index 9eceac5d..1ea8fb7a 100644 --- a/internal/pkg/node/transformers.go +++ b/internal/pkg/node/transformers.go @@ -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", diff --git a/internal/pkg/overlay/config.go b/internal/pkg/overlay/config.go index d2a9e924..8cc09d95 100644 --- a/internal/pkg/overlay/config.go +++ b/internal/pkg/overlay/config.go @@ -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) } diff --git a/internal/pkg/overlay/datastructure.go b/internal/pkg/overlay/datastructure.go index 6af7adb2..f9806802 100644 --- a/internal/pkg/overlay/datastructure.go +++ b/internal/pkg/overlay/datastructure.go @@ -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 diff --git a/internal/pkg/overlay/funcmap.go b/internal/pkg/overlay/funcmap.go index 4acf6705..dc688dc2 100644 --- a/internal/pkg/overlay/funcmap.go +++ b/internal/pkg/overlay/funcmap.go @@ -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) +} diff --git a/internal/pkg/overlay/funcmap_test.go b/internal/pkg/overlay/funcmap_test.go new file mode 100644 index 00000000..ab1a4be2 --- /dev/null +++ b/internal/pkg/overlay/funcmap_test.go @@ -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)) +} diff --git a/internal/pkg/overlay/overlay.go b/internal/pkg/overlay/overlay.go index 0d441cc7..969ea291 100644 --- a/internal/pkg/overlay/overlay.go +++ b/internal/pkg/overlay/overlay.go @@ -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 { diff --git a/internal/pkg/warewulfd/daemon.go b/internal/pkg/warewulfd/daemon.go index bfa16f9d..6833b2e9 100644 --- a/internal/pkg/warewulfd/daemon.go +++ b/internal/pkg/warewulfd/daemon.go @@ -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 } diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go index 60457208..d83fd70d 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go @@ -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) diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go index 6b2275fe..4e497c6f 100644 --- a/internal/pkg/warewulfd/util.go +++ b/internal/pkg/warewulfd/util.go @@ -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) diff --git a/internal/pkg/wwlog/wwlog.go b/internal/pkg/wwlog/wwlog.go index 7879c728..98f67f2a 100644 --- a/internal/pkg/wwlog/wwlog.go +++ b/internal/pkg/wwlog/wwlog.go @@ -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...) diff --git a/internal/pkg/wwlog/wwlog_test.go b/internal/pkg/wwlog/wwlog_test.go new file mode 100644 index 00000000..72d61899 --- /dev/null +++ b/internal/pkg/wwlog/wwlog_test.go @@ -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() + } + } + } + } +} diff --git a/overlays/wwinit/etc/fstab.ww b/overlays/wwinit/etc/fstab.ww index 58fc3767..555d6b89 100644 --- a/overlays/wwinit/etc/fstab.ww +++ b/overlays/wwinit/etc/fstab.ww @@ -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 diff --git a/overlays/wwinit/etc/systemd/system/ignition-ww4-disks.service b/overlays/wwinit/etc/systemd/system/ignition-ww4-disks.service new file mode 100644 index 00000000..ce377b79 --- /dev/null +++ b/overlays/wwinit/etc/systemd/system/ignition-ww4-disks.service @@ -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 diff --git a/overlays/wwinit/etc/systemd/system/local-fs.target.wants/ignition-ww4-disks.service b/overlays/wwinit/etc/systemd/system/local-fs.target.wants/ignition-ww4-disks.service new file mode 120000 index 00000000..726d662c --- /dev/null +++ b/overlays/wwinit/etc/systemd/system/local-fs.target.wants/ignition-ww4-disks.service @@ -0,0 +1 @@ +/etc/systemd/system/ignition-ww4-disks.service \ No newline at end of file diff --git a/overlays/wwinit/etc/systemd/system/local-fs.target.wants/ww4-disks.target b/overlays/wwinit/etc/systemd/system/local-fs.target.wants/ww4-disks.target new file mode 120000 index 00000000..6bebd069 --- /dev/null +++ b/overlays/wwinit/etc/systemd/system/local-fs.target.wants/ww4-disks.target @@ -0,0 +1 @@ +/etc/systemd/system/ww4-disks.target \ No newline at end of file diff --git a/overlays/wwinit/etc/systemd/system/ww4-disks.target.ww b/overlays/wwinit/etc/systemd/system/ww4-disks.target.ww new file mode 100644 index 00000000..b8e9d429 --- /dev/null +++ b/overlays/wwinit/etc/systemd/system/ww4-disks.target.ww @@ -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 }} \ No newline at end of file diff --git a/overlays/wwinit/etc/systemd/system/ww4-mounts.ww b/overlays/wwinit/etc/systemd/system/ww4-mounts.ww new file mode 100644 index 00000000..39a5fb9a --- /dev/null +++ b/overlays/wwinit/etc/systemd/system/ww4-mounts.ww @@ -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 }} diff --git a/overlays/wwinit/init b/overlays/wwinit/init index a6999991..af754f94 100755 --- a/overlays/wwinit/init +++ b/overlays/wwinit/init @@ -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 \ No newline at end of file +echo b > /proc/sysrq-trigger || /sbin/reboot diff --git a/overlays/wwinit/warewulf/ignition.json.ww b/overlays/wwinit/warewulf/ignition.json.ww new file mode 100644 index 00000000..7b800e85 --- /dev/null +++ b/overlays/wwinit/warewulf/ignition.json.ww @@ -0,0 +1 @@ +{{ IgnitionJson }} diff --git a/pkg/hostlist/hostlist_test.go b/pkg/hostlist/hostlist_test.go new file mode 100644 index 00000000..bd89d1ff --- /dev/null +++ b/pkg/hostlist/hostlist_test.go @@ -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"})) +// } diff --git a/userdocs/contents/containers.rst b/userdocs/contents/containers.rst index 412378af..73c3f890 100644 --- a/userdocs/contents/containers.rst +++ b/userdocs/contents/containers.rst @@ -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. \ No newline at end of file diff --git a/userdocs/contents/disks.rst b/userdocs/contents/disks.rst new file mode 100644 index 00000000..36152178 --- /dev/null +++ b/userdocs/contents/disks.rst @@ -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. diff --git a/userdocs/contents/overlays.rst b/userdocs/contents/overlays.rst index 84f6acd0..cd1fe5be 100644 --- a/userdocs/contents/overlays.rst +++ b/userdocs/contents/overlays.rst @@ -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 ----- diff --git a/userdocs/contents/provisioning.rst b/userdocs/contents/provisioning.rst index 8c69f620..c1ba2c14 100644 --- a/userdocs/contents/provisioning.rst +++ b/userdocs/contents/provisioning.rst @@ -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. \ No newline at end of file diff --git a/userdocs/contents/templating.rst b/userdocs/contents/templating.rst index 692adcbc..98114144 100644 --- a/userdocs/contents/templating.rst +++ b/userdocs/contents/templating.rst @@ -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 }} + diff --git a/userdocs/contents/wwctl.rst b/userdocs/contents/wwctl.rst new file mode 100644 index 00000000..5fa8ee48 --- /dev/null +++ b/userdocs/contents/wwctl.rst @@ -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`` diff --git a/userdocs/contributing/debugging.rst b/userdocs/contributing/debugging.rst index cf1c256d..d32affe0 100644 --- a/userdocs/contributing/debugging.rst +++ b/userdocs/contributing/debugging.rst @@ -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 diff --git a/userdocs/contributing/development-environment-vagrant.rst b/userdocs/contributing/development-environment-vagrant.rst new file mode 100644 index 00000000..e437f080 --- /dev/null +++ b/userdocs/contributing/development-environment-vagrant.rst @@ -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 + diff --git a/userdocs/index.rst b/userdocs/index.rst index 125a5dc0..f18504d1 100644 --- a/userdocs/index.rst +++ b/userdocs/index.rst @@ -14,6 +14,7 @@ Welcome to the Warewulf User Guide! Control Server Setup Warewulf Installation Warewulf Configuration + Controlling Warewulf (wwctl) Warewulf Initialization Container Management Kernel Management @@ -22,6 +23,7 @@ Welcome to the Warewulf User Guide! Warewulf Overlays Node Provisioning IPMI + Disk Management Security Templating @@ -31,6 +33,7 @@ Welcome to the Warewulf User Guide! EL7 (CentOS and RHEL) EL8 (Rocky Linux and RHEL) + EL9 (Rocky Linux and RHEL) openSUSE Leap and SLES 15 .. toctree:: @@ -40,6 +43,7 @@ Welcome to the Warewulf User Guide! Contributing Debugging Documentation + Development Environment (Vagrant) Development Environment (KVM) Development Environment (VirtualBox) Glossary diff --git a/userdocs/quickstart/el8.rst b/userdocs/quickstart/el8.rst index acd0a848..f2bdae40 100644 --- a/userdocs/quickstart/el8.rst +++ b/userdocs/quickstart/el8.rst @@ -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 diff --git a/userdocs/quickstart/el9.rst b/userdocs/quickstart/el9.rst new file mode 100644 index 00000000..0ad66d77 --- /dev/null +++ b/userdocs/quickstart/el9.rst @@ -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! diff --git a/userdocs/quickstart/suse15.rst b/userdocs/quickstart/suse15.rst index 7c0c58e2..ad6f8786 100644 --- a/userdocs/quickstart/suse15.rst +++ b/userdocs/quickstart/suse15.rst @@ -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 diff --git a/warewulf.spec.in b/warewulf.spec.in index fd0c32c0..725db9a7 100644 --- a/warewulf.spec.in +++ b/warewulf.spec.in @@ -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 - 4.2.0-1 +* Fri Sep 24 2021 Michael L. Young - 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 - 4.0.0 +* Tue Jan 26 2021 14:46:24 JST Brian Clemens - 4.0.0 - Initial release